"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Autoinstaller = void 0;
const path = __importStar(require("node:path"));
const node_core_library_1 = require("@rushstack/node-core-library");
const terminal_1 = require("@rushstack/terminal");
const Utilities_1 = require("../utilities/Utilities");
const PackageJsonEditor_1 = require("../api/PackageJsonEditor");
const InstallHelpers_1 = require("./installManager/InstallHelpers");
const RushConstants_1 = require("./RushConstants");
const LastInstallFlag_1 = require("../api/LastInstallFlag");
const RushCommandLineParser_1 = require("../cli/RushCommandLineParser");
class Autoinstaller {
    constructor(options) {
        var _a;
        this.name = options.autoinstallerName;
        this._rushConfiguration = options.rushConfiguration;
        this._rushGlobalFolder = options.rushGlobalFolder;
        this._restrictConsoleOutput =
            (_a = options.restrictConsoleOutput) !== null && _a !== void 0 ? _a : RushCommandLineParser_1.RushCommandLineParser.shouldRestrictConsoleOutput();
        Autoinstaller.validateName(this.name);
    }
    // Example: .../common/autoinstallers/my-task
    get folderFullPath() {
        return path.join(this._rushConfiguration.commonAutoinstallersFolder, this.name);
    }
    // Example: .../common/autoinstallers/my-task/package-lock.yaml
    get shrinkwrapFilePath() {
        return path.join(this._rushConfiguration.commonAutoinstallersFolder, this.name, this._rushConfiguration.shrinkwrapFilename);
    }
    // Example: .../common/autoinstallers/my-task/package.json
    get packageJsonPath() {
        return path.join(this._rushConfiguration.commonAutoinstallersFolder, this.name, 'package.json');
    }
    static validateName(autoinstallerName) {
        const nameOrError = node_core_library_1.PackageName.tryParse(autoinstallerName);
        if (nameOrError.error) {
            throw new Error(`The specified name "${autoinstallerName}" is invalid: ` + nameOrError.error);
        }
        if (nameOrError.scope) {
            throw new Error(`The specified name "${autoinstallerName}" must not contain an NPM scope`);
        }
    }
    async prepareAsync() {
        const autoinstallerFullPath = this.folderFullPath;
        if (!node_core_library_1.FileSystem.exists(autoinstallerFullPath)) {
            throw new Error(`The autoinstaller ${this.name} does not exist, Please run\nrush init-autoinstaller --name ${this.name}\n`);
        }
        await InstallHelpers_1.InstallHelpers.ensureLocalPackageManagerAsync(this._rushConfiguration, this._rushGlobalFolder, RushConstants_1.RushConstants.defaultMaxInstallAttempts, this._restrictConsoleOutput);
        // Example: common/autoinstallers/my-task/package.json
        const relativePathForLogs = path.relative(this._rushConfiguration.rushJsonFolder, autoinstallerFullPath);
        this._logIfConsoleOutputIsNotRestricted(`Acquiring lock for "${relativePathForLogs}" folder...`);
        const lock = await node_core_library_1.LockFile.acquireAsync(autoinstallerFullPath, 'autoinstaller');
        try {
            // Example: .../common/autoinstallers/my-task/.rush/temp
            const lastInstallFlagPath = path.join(autoinstallerFullPath, RushConstants_1.RushConstants.projectRushFolderName, 'temp');
            const packageJsonPath = path.join(autoinstallerFullPath, 'package.json');
            const packageJson = node_core_library_1.JsonFile.load(packageJsonPath);
            const lastInstallFlag = new LastInstallFlag_1.LastInstallFlag(lastInstallFlagPath, {
                node: process.versions.node,
                packageManager: this._rushConfiguration.packageManager,
                packageManagerVersion: this._rushConfiguration.packageManagerToolVersion,
                packageJson: packageJson,
                rushJsonFolder: this._rushConfiguration.rushJsonFolder
            });
            // Example: ../common/autoinstallers/my-task/node_modules
            const nodeModulesFolder = `${autoinstallerFullPath}/${RushConstants_1.RushConstants.nodeModulesFolderName}`;
            const flagPath = `${nodeModulesFolder}/rush-autoinstaller.flag`;
            const isLastInstallFlagDirty = !(await lastInstallFlag.isValidAsync()) || !node_core_library_1.FileSystem.exists(flagPath);
            if (isLastInstallFlagDirty || lock.dirtyWhenAcquired) {
                if (node_core_library_1.FileSystem.exists(nodeModulesFolder)) {
                    this._logIfConsoleOutputIsNotRestricted('Deleting old files from ' + nodeModulesFolder);
                    node_core_library_1.FileSystem.ensureEmptyFolder(nodeModulesFolder);
                }
                // Copy: .../common/autoinstallers/my-task/.npmrc
                Utilities_1.Utilities.syncNpmrc({
                    sourceNpmrcFolder: this._rushConfiguration.commonRushConfigFolder,
                    targetNpmrcFolder: autoinstallerFullPath,
                    supportEnvVarFallbackSyntax: this._rushConfiguration.isPnpm
                });
                this._logIfConsoleOutputIsNotRestricted(`Installing dependencies under ${autoinstallerFullPath}...\n`);
                await Utilities_1.Utilities.executeCommandAsync({
                    command: this._rushConfiguration.packageManagerToolFilename,
                    args: ['install', '--frozen-lockfile'],
                    workingDirectory: autoinstallerFullPath,
                    keepEnvironment: true
                });
                // Create file: ../common/autoinstallers/my-task/.rush/temp/last-install.flag
                await lastInstallFlag.createAsync();
                node_core_library_1.FileSystem.writeFile(flagPath, 'If this file is deleted, Rush will assume that the node_modules folder has been cleaned and will reinstall it.');
                this._logIfConsoleOutputIsNotRestricted('Auto install completed successfully\n');
            }
            else {
                this._logIfConsoleOutputIsNotRestricted('Autoinstaller folder is already up to date\n');
            }
        }
        finally {
            // Ensure the lockfile is released when we are finished.
            lock.release();
        }
    }
    async updateAsync() {
        await InstallHelpers_1.InstallHelpers.ensureLocalPackageManagerAsync(this._rushConfiguration, this._rushGlobalFolder, RushConstants_1.RushConstants.defaultMaxInstallAttempts, this._restrictConsoleOutput);
        const autoinstallerPackageJsonPath = path.join(this.folderFullPath, 'package.json');
        if (!(await node_core_library_1.FileSystem.existsAsync(autoinstallerPackageJsonPath))) {
            throw new Error(`The specified autoinstaller path does not exist: ` + autoinstallerPackageJsonPath);
        }
        this._logIfConsoleOutputIsNotRestricted(`Updating autoinstaller package: ${autoinstallerPackageJsonPath}`);
        let oldFileContents = '';
        if (await node_core_library_1.FileSystem.existsAsync(this.shrinkwrapFilePath)) {
            oldFileContents = node_core_library_1.FileSystem.readFile(this.shrinkwrapFilePath, { convertLineEndings: node_core_library_1.NewlineKind.Lf });
            this._logIfConsoleOutputIsNotRestricted('Deleting ' + this.shrinkwrapFilePath);
            await node_core_library_1.FileSystem.deleteFileAsync(this.shrinkwrapFilePath);
            if (this._rushConfiguration.isPnpm) {
                // Workaround for https://github.com/pnpm/pnpm/issues/1890
                //
                // When "rush update-autoinstaller" is run, Rush deletes "common/autoinstallers/my-task/pnpm-lock.yaml"
                // so that a new lockfile will be generated. However "pnpm install" by design will try to recover
                // "pnpm-lock.yaml" from "my-task/node_modules/.pnpm/lock.yaml", which may prevent a full upgrade.
                // Deleting both files ensures that a new lockfile will always be generated.
                const pnpmPackageManager = this._rushConfiguration
                    .packageManagerWrapper;
                await node_core_library_1.FileSystem.deleteFileAsync(path.join(this.folderFullPath, pnpmPackageManager.internalShrinkwrapRelativePath));
            }
        }
        // Detect a common mistake where PNPM prints "Already up-to-date" without creating a shrinkwrap file
        const packageJsonEditor = PackageJsonEditor_1.PackageJsonEditor.load(this.packageJsonPath);
        if (packageJsonEditor.dependencyList.length === 0) {
            throw new Error('You must add at least one dependency to the autoinstaller package' +
                ' before invoking this command:\n' +
                this.packageJsonPath);
        }
        this._logIfConsoleOutputIsNotRestricted();
        Utilities_1.Utilities.syncNpmrc({
            sourceNpmrcFolder: this._rushConfiguration.commonRushConfigFolder,
            targetNpmrcFolder: this.folderFullPath,
            supportEnvVarFallbackSyntax: this._rushConfiguration.isPnpm
        });
        await Utilities_1.Utilities.executeCommandAsync({
            command: this._rushConfiguration.packageManagerToolFilename,
            args: ['install'],
            workingDirectory: this.folderFullPath,
            keepEnvironment: true
        });
        this._logIfConsoleOutputIsNotRestricted();
        if (this._rushConfiguration.packageManager === 'npm') {
            this._logIfConsoleOutputIsNotRestricted(terminal_1.Colorize.bold('Running "npm shrinkwrap"...'));
            await Utilities_1.Utilities.executeCommandAsync({
                command: this._rushConfiguration.packageManagerToolFilename,
                args: ['shrinkwrap'],
                workingDirectory: this.folderFullPath,
                keepEnvironment: true
            });
            this._logIfConsoleOutputIsNotRestricted('"npm shrinkwrap" completed');
            this._logIfConsoleOutputIsNotRestricted();
        }
        if (!(await node_core_library_1.FileSystem.existsAsync(this.shrinkwrapFilePath))) {
            throw new Error('The package manager did not create the expected shrinkwrap file: ' + this.shrinkwrapFilePath);
        }
        const newFileContents = await node_core_library_1.FileSystem.readFileAsync(this.shrinkwrapFilePath, {
            convertLineEndings: node_core_library_1.NewlineKind.Lf
        });
        if (oldFileContents !== newFileContents) {
            this._logIfConsoleOutputIsNotRestricted(terminal_1.Colorize.green('The shrinkwrap file has been updated.') + '  Please commit the updated file:');
            this._logIfConsoleOutputIsNotRestricted(`\n  ${this.shrinkwrapFilePath}`);
        }
        else {
            this._logIfConsoleOutputIsNotRestricted(terminal_1.Colorize.green('Already up to date.'));
        }
    }
    _logIfConsoleOutputIsNotRestricted(message) {
        if (!this._restrictConsoleOutput) {
            // eslint-disable-next-line no-console
            console.log(message !== null && message !== void 0 ? message : '');
        }
    }
}
exports.Autoinstaller = Autoinstaller;
//# sourceMappingURL=Autoinstaller.js.map