"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.SetupChecks = void 0;
const path = __importStar(require("node:path"));
const semver = __importStar(require("semver"));
const node_core_library_1 = require("@rushstack/node-core-library");
const terminal_1 = require("@rushstack/terminal");
const RushConstants_1 = require("./RushConstants");
// Refuses to run at all if the PNPM version is older than this, because there
// are known bugs or missing features in earlier releases.
const MINIMUM_SUPPORTED_NPM_VERSION = '4.5.0';
// Refuses to run at all if the PNPM version is older than this, because there
// are known bugs or missing features in earlier releases.
const MINIMUM_SUPPORTED_PNPM_VERSION = '5.0.0';
/**
 * Validate that the developer's setup is good.
 *
 * These checks are invoked prior to the following commands:
 * - rush install
 * - rush update
 * - rush build
 * - rush rebuild
 */
class SetupChecks {
    static validate(rushConfiguration) {
        // NOTE: The Node.js version is also checked in rush/src/start.ts
        const errorMessage = SetupChecks._validate(rushConfiguration);
        if (errorMessage) {
            // eslint-disable-next-line no-console
            console.error(terminal_1.Colorize.red(terminal_1.PrintUtilities.wrapWords(errorMessage)));
            throw new node_core_library_1.AlreadyReportedError();
        }
    }
    static _validate(rushConfiguration) {
        // Check for outdated tools
        if (rushConfiguration.isPnpm) {
            if (semver.lt(rushConfiguration.packageManagerToolVersion, MINIMUM_SUPPORTED_PNPM_VERSION)) {
                return (`The ${RushConstants_1.RushConstants.rushJsonFilename} file requests PNPM version ` +
                    rushConfiguration.packageManagerToolVersion +
                    `, but PNPM ${MINIMUM_SUPPORTED_PNPM_VERSION} is the minimum supported by Rush.`);
            }
        }
        else if (rushConfiguration.packageManager === 'npm') {
            if (semver.lt(rushConfiguration.packageManagerToolVersion, MINIMUM_SUPPORTED_NPM_VERSION)) {
                return (`The ${RushConstants_1.RushConstants.rushJsonFilename} file requests NPM version ` +
                    rushConfiguration.packageManagerToolVersion +
                    `, but NPM ${MINIMUM_SUPPORTED_NPM_VERSION} is the minimum supported by Rush.`);
            }
        }
        SetupChecks._checkForPhantomFolders(rushConfiguration);
    }
    static _checkForPhantomFolders(rushConfiguration) {
        const phantomFolders = [];
        const seenFolders = new Set();
        // Check from the real parent of the common/temp folder
        const commonTempParent = path.dirname(node_core_library_1.FileSystem.getRealPath(rushConfiguration.commonTempFolder));
        SetupChecks._collectPhantomFoldersUpwards(commonTempParent, phantomFolders, seenFolders);
        // Check from the real folder containing rush.json
        const realRushJsonFolder = node_core_library_1.FileSystem.getRealPath(rushConfiguration.rushJsonFolder);
        SetupChecks._collectPhantomFoldersUpwards(realRushJsonFolder, phantomFolders, seenFolders);
        if (phantomFolders.length > 0) {
            if (phantomFolders.length === 1) {
                // eslint-disable-next-line no-console
                console.log(terminal_1.Colorize.yellow(terminal_1.PrintUtilities.wrapWords('Warning: A phantom "node_modules" folder was found. This defeats Rush\'s protection against' +
                    ' NPM phantom dependencies and may cause confusing build errors. It is recommended to' +
                    ' delete this folder:')));
            }
            else {
                // eslint-disable-next-line no-console
                console.log(terminal_1.Colorize.yellow(terminal_1.PrintUtilities.wrapWords('Warning: Phantom "node_modules" folders were found. This defeats Rush\'s protection against' +
                    ' NPM phantom dependencies and may cause confusing build errors. It is recommended to' +
                    ' delete these folders:')));
            }
            for (const folder of phantomFolders) {
                // eslint-disable-next-line no-console
                console.log(terminal_1.Colorize.yellow(`"${folder}"`));
            }
            // eslint-disable-next-line no-console
            console.log(); // add a newline
        }
    }
    /**
     * Checks "folder" and each of its parents to see if it contains a node_modules folder.
     * The bad folders will be added to phantomFolders.
     * The seenFolders set is used to avoid duplicates.
     */
    static _collectPhantomFoldersUpwards(folder, phantomFolders, seenFolders) {
        // Stop if we reached a folder that we already analyzed
        while (!seenFolders.has(folder)) {
            seenFolders.add(folder);
            // If there is a node_modules folder under this folder, add it to the list of bad folders
            const nodeModulesFolder = path.join(folder, RushConstants_1.RushConstants.nodeModulesFolderName);
            if (node_core_library_1.FileSystem.exists(nodeModulesFolder)) {
                // Collect the names of files/folders in that node_modules folder
                const filenames = node_core_library_1.FileSystem.readFolderItemNames(nodeModulesFolder).filter((x) => !x.startsWith('.'));
                let ignore = false;
                if (filenames.length === 0) {
                    // If the node_modules folder is completely empty, then it's not a concern
                    ignore = true;
                }
                else if (filenames.length === 1 && filenames[0] === 'vso-task-lib') {
                    // Special case:  The Azure DevOps build agent installs the "vso-task-lib" NPM package
                    // in a top-level path such as:
                    //
                    //   /home/vsts/work/node_modules/vso-task-lib
                    //
                    // It is always the only package in that node_modules folder.  The "vso-task-lib" package
                    // is now deprecated, so it is unlikely to be a real dependency of any modern project.
                    // To avoid false alarms, we ignore this specific case.
                    ignore = true;
                }
                if (!ignore) {
                    phantomFolders.push(nodeModulesFolder);
                }
            }
            // Walk upwards
            const parentFolder = path.dirname(folder);
            if (!parentFolder || parentFolder === folder) {
                // If path.dirname() returns its own input, then means we reached the root
                break;
            }
            folder = parentFolder;
        }
    }
}
exports.SetupChecks = SetupChecks;
//# sourceMappingURL=SetupChecks.js.map