"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LegacySkipPlugin = void 0;
const node_path_1 = __importDefault(require("node:path"));
const node_core_library_1 = require("@rushstack/node-core-library");
const terminal_1 = require("@rushstack/terminal");
const OperationStatus_1 = require("./OperationStatus");
const PLUGIN_NAME = 'LegacySkipPlugin';
function _areShallowEqual(object1, object2) {
    for (const n in object1) {
        if (!(n in object2) || object1[n] !== object2[n]) {
            return false;
        }
    }
    for (const n in object2) {
        if (!(n in object1)) {
            return false;
        }
    }
    return true;
}
/**
 * Core phased command plugin that implements the legacy skip detection logic, used when build cache is disabled.
 */
class LegacySkipPlugin {
    constructor(options) {
        this._options = options;
    }
    apply(hooks) {
        const stateMap = new WeakMap();
        const { terminal, changedProjectsOnly, isIncrementalBuildAllowed, allowWarningsInSuccessfulBuild } = this._options;
        hooks.beforeExecuteOperations.tap(PLUGIN_NAME, (operations, context) => {
            let logGitWarning = false;
            const { inputsSnapshot } = context;
            for (const record of operations.values()) {
                const { operation } = record;
                const { associatedProject, associatedPhase, runner, logFilenameIdentifier } = operation;
                if (!runner) {
                    continue;
                }
                if (!runner.cacheable) {
                    stateMap.set(operation, {
                        allowSkip: true,
                        packageDeps: undefined,
                        packageDepsPath: ''
                    });
                    continue;
                }
                const packageDepsFilename = `package-deps_${logFilenameIdentifier}.json`;
                const packageDepsPath = node_path_1.default.join(associatedProject.projectRushTempFolder, packageDepsFilename);
                let packageDeps;
                try {
                    const fileHashes = inputsSnapshot === null || inputsSnapshot === void 0 ? void 0 : inputsSnapshot.getTrackedFileHashesForOperation(associatedProject, associatedPhase.name);
                    if (!fileHashes) {
                        logGitWarning = true;
                        continue;
                    }
                    const files = {};
                    for (const [filePath, fileHash] of fileHashes) {
                        files[filePath] = fileHash;
                    }
                    packageDeps = {
                        files,
                        arguments: runner.getConfigHash()
                    };
                }
                catch (error) {
                    // To test this code path:
                    // Delete a project's ".rush/temp/shrinkwrap-deps.json" then run "rush build --verbose"
                    terminal.writeLine(`Unable to calculate incremental state for ${record.operation.name}: ` +
                        error.toString());
                    terminal.writeLine(terminal_1.Colorize.cyan('Rush will proceed without incremental execution and change detection.'));
                }
                stateMap.set(operation, {
                    packageDepsPath,
                    packageDeps,
                    allowSkip: isIncrementalBuildAllowed
                });
            }
            if (logGitWarning) {
                // To test this code path:
                // Remove the `.git` folder then run "rush build --verbose"
                terminal.writeLine(terminal_1.Colorize.cyan(terminal_1.PrintUtilities.wrapWords('This workspace does not appear to be tracked by Git. ' +
                    'Rush will proceed without incremental execution, caching, and change detection.')));
            }
        });
        hooks.beforeExecuteOperation.tapPromise(PLUGIN_NAME, async (record) => {
            const { operation } = record;
            const skipRecord = stateMap.get(operation);
            if (!skipRecord) {
                // This operation doesn't support skip detection.
                return;
            }
            if (!operation.runner.cacheable) {
                // This operation doesn't support skip detection.
                return;
            }
            const { associatedProject } = operation;
            const { packageDepsPath, packageDeps, allowSkip } = skipRecord;
            let lastProjectDeps = undefined;
            try {
                const lastDepsContents = await node_core_library_1.FileSystem.readFileAsync(packageDepsPath);
                lastProjectDeps = JSON.parse(lastDepsContents);
            }
            catch (e) {
                if (!node_core_library_1.FileSystem.isNotExistError(e)) {
                    // Warn and ignore - treat failing to load the file as the operation being not built.
                    // TODO: Update this to be the terminal specific to the operation.
                    terminal.writeWarningLine(`Warning: error parsing ${packageDepsPath}: ${e}. Ignoring and treating this operation as not run.`);
                }
            }
            if (allowSkip) {
                const isPackageUnchanged = !!(lastProjectDeps &&
                    packageDeps &&
                    packageDeps.arguments === lastProjectDeps.arguments &&
                    _areShallowEqual(packageDeps.files, lastProjectDeps.files));
                if (isPackageUnchanged) {
                    return OperationStatus_1.OperationStatus.Skipped;
                }
            }
            // TODO: Remove legacyDepsPath with the next major release of Rush
            const legacyDepsPath = node_path_1.default.join(associatedProject.projectFolder, 'package-deps.json');
            await Promise.all([
                // Delete the legacy package-deps.json
                node_core_library_1.FileSystem.deleteFileAsync(legacyDepsPath),
                // If the deps file exists, remove it before starting execution.
                node_core_library_1.FileSystem.deleteFileAsync(packageDepsPath)
            ]);
        });
        hooks.afterExecuteOperation.tapPromise(PLUGIN_NAME, async (record) => {
            const { status, operation } = record;
            const skipRecord = stateMap.get(operation);
            if (!skipRecord) {
                return;
            }
            const blockSkip = !skipRecord.allowSkip ||
                (!changedProjectsOnly &&
                    (status === OperationStatus_1.OperationStatus.Success || status === OperationStatus_1.OperationStatus.SuccessWithWarning));
            if (blockSkip) {
                for (const consumer of operation.consumers) {
                    const consumerSkipRecord = stateMap.get(consumer);
                    if (consumerSkipRecord) {
                        consumerSkipRecord.allowSkip = false;
                    }
                }
            }
            if (!record.operation.runner.cacheable) {
                // This operation doesn't support skip detection.
                return;
            }
            const { packageDeps, packageDepsPath } = skipRecord;
            if (status === OperationStatus_1.OperationStatus.NoOp ||
                (packageDeps &&
                    (status === OperationStatus_1.OperationStatus.Success ||
                        (status === OperationStatus_1.OperationStatus.SuccessWithWarning &&
                            record.operation.runner.warningsAreAllowed &&
                            allowWarningsInSuccessfulBuild)))) {
                // Write deps on success.
                await node_core_library_1.JsonFile.saveAsync(packageDeps, packageDepsPath, {
                    ensureFolderExists: true
                });
            }
        });
    }
}
exports.LegacySkipPlugin = LegacySkipPlugin;
//# sourceMappingURL=LegacySkipPlugin.js.map