// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
/* eslint max-lines: off */
import * as path from 'node:path';
import * as semver from 'semver';
import { trueCasePathSync } from 'true-case-path';
import { JsonFile, JsonSchema, Path, FileSystem, InternalError } from '@rushstack/node-core-library';
import { LookupByPath } from '@rushstack/lookup-by-path';
import { Rush } from './Rush';
import { RushConfigurationProject } from './RushConfigurationProject';
import { RushConstants } from '../logic/RushConstants';
import { ApprovedPackagesPolicy } from './ApprovedPackagesPolicy';
import { EventHooks } from './EventHooks';
import { VersionPolicyConfiguration } from './VersionPolicyConfiguration';
import { EnvironmentConfiguration } from './EnvironmentConfiguration';
import { Utilities } from '../utilities/Utilities';
import { NpmPackageManager } from './packageManager/NpmPackageManager';
import { YarnPackageManager } from './packageManager/YarnPackageManager';
import { PnpmPackageManager } from './packageManager/PnpmPackageManager';
import { ExperimentsConfiguration } from './ExperimentsConfiguration';
import { PackageNameParsers } from './PackageNameParsers';
import { RushPluginsConfiguration } from './RushPluginsConfiguration';
import { PnpmOptionsConfiguration } from '../logic/pnpm/PnpmOptionsConfiguration';
import { NpmOptionsConfiguration } from '../logic/npm/NpmOptionsConfiguration';
import { YarnOptionsConfiguration } from '../logic/yarn/YarnOptionsConfiguration';
import schemaJson from '../schemas/rush.schema.json';
import { CustomTipsConfiguration } from './CustomTipsConfiguration';
import { SubspacesConfiguration } from './SubspacesConfiguration';
import { Subspace } from './Subspace';
const MINIMUM_SUPPORTED_RUSH_JSON_VERSION = '0.0.0';
const DEFAULT_BRANCH = 'main';
const DEFAULT_REMOTE = 'origin';
/**
 * A list of known config filenames that are expected to appear in the "./common/config/rush" folder.
 * To avoid confusion/mistakes, any extra files will be reported as an error.
 */
const knownRushConfigFilenames = [
    '.npmrc-publish',
    '.npmrc',
    'deploy.json',
    RushConstants.artifactoryFilename,
    RushConstants.browserApprovedPackagesFilename,
    RushConstants.buildCacheFilename,
    RushConstants.cobuildFilename,
    RushConstants.commandLineFilename,
    RushConstants.commonVersionsFilename,
    RushConstants.customTipsFilename,
    RushConstants.experimentsFilename,
    RushConstants.nonbrowserApprovedPackagesFilename,
    RushConstants.pinnedVersionsFilename,
    RushConstants.repoStateFilename,
    RushConstants.versionPoliciesFilename,
    RushConstants.rushPluginsConfigFilename,
    RushConstants.pnpmConfigFilename,
    RushConstants.subspacesConfigFilename,
    RushConstants.rushAlertsConfigFilename
];
/**
 * This represents the Rush configuration for a repository, based on the "rush.json"
 * configuration file.
 * @public
 */
export class RushConfiguration {
    /**
     * Use RushConfiguration.loadFromConfigurationFile() or Use RushConfiguration.loadFromDefaultLocation()
     * instead.
     */
    constructor(rushConfigurationJson, rushJsonFilename) {
        var _a, _b;
        this._subspaces = [];
        this.rushConfigurationJson = rushConfigurationJson;
        EnvironmentConfiguration.validate();
        if (rushConfigurationJson.nodeSupportedVersionRange) {
            if (!semver.validRange(rushConfigurationJson.nodeSupportedVersionRange)) {
                throw new Error('Error parsing the node-semver expression in the "nodeSupportedVersionRange"' +
                    ` field from ${RushConstants.rushJsonFilename}: "${rushConfigurationJson.nodeSupportedVersionRange}"`);
            }
            if (!semver.satisfies(process.version, rushConfigurationJson.nodeSupportedVersionRange)) {
                let message = `Your dev environment is running Node.js version ${process.version} which does` +
                    ` not meet the requirements for building this repository.  (The ${RushConstants.rushJsonFilename} configuration` +
                    ` requires nodeSupportedVersionRange="${rushConfigurationJson.nodeSupportedVersionRange}")`;
                if (rushConfigurationJson.nodeSupportedVersionInstructions) {
                    message += '\n\n' + rushConfigurationJson.nodeSupportedVersionInstructions;
                }
                if (EnvironmentConfiguration.allowUnsupportedNodeVersion) {
                    // eslint-disable-next-line no-console
                    console.warn(message);
                }
                else {
                    throw new Error(message);
                }
            }
        }
        this.rushJsonFile = rushJsonFilename;
        this.rushJsonFolder = path.dirname(rushJsonFilename);
        this.commonFolder = path.resolve(path.join(this.rushJsonFolder, RushConstants.commonFolderName));
        this.commonRushConfigFolder = path.join(this.commonFolder, 'config', 'rush');
        this.commonTempFolder =
            EnvironmentConfiguration.rushTempFolderOverride ||
                path.join(this.commonFolder, RushConstants.rushTempFolderName);
        this.commonScriptsFolder = path.join(this.commonFolder, 'scripts');
        this.npmCacheFolder = path.resolve(path.join(this.commonTempFolder, 'npm-cache'));
        this.npmTmpFolder = path.resolve(path.join(this.commonTempFolder, 'npm-tmp'));
        this.yarnCacheFolder = path.resolve(path.join(this.commonTempFolder, 'yarn-cache'));
        this.changesFolder = path.join(this.commonFolder, RushConstants.changeFilesFolderName);
        this.currentVariantJsonFilePath = path.join(this.commonTempFolder, RushConstants.currentVariantsFilename);
        this.suppressNodeLtsWarning = !!rushConfigurationJson.suppressNodeLtsWarning;
        this._ensureConsistentVersionsJsonValue = rushConfigurationJson.ensureConsistentVersions;
        this.ensureConsistentVersions = !!rushConfigurationJson.ensureConsistentVersions;
        // Try getting a subspace configuration
        this.subspacesConfiguration = SubspacesConfiguration.tryLoadFromDefaultLocation(this);
        this.subspacesFeatureEnabled = !!((_a = this.subspacesConfiguration) === null || _a === void 0 ? void 0 : _a.subspacesEnabled);
        this._subspacesByName = new Map();
        const experimentsConfigFile = path.join(this.commonRushConfigFolder, RushConstants.experimentsFilename);
        this.experimentsConfiguration = new ExperimentsConfiguration(experimentsConfigFile);
        const rushPluginsConfigFilename = path.join(this.commonRushConfigFolder, RushConstants.rushPluginsConfigFilename);
        this._rushPluginsConfiguration = new RushPluginsConfiguration(rushPluginsConfigFilename);
        this.npmOptions = new NpmOptionsConfiguration(rushConfigurationJson.npmOptions || {});
        this.yarnOptions = new YarnOptionsConfiguration(rushConfigurationJson.yarnOptions || {});
        try {
            this.pnpmOptions = PnpmOptionsConfiguration.loadFromJsonFileOrThrow(`${this.commonRushConfigFolder}/${RushConstants.pnpmConfigFilename}`, this.commonTempFolder);
            if (rushConfigurationJson.pnpmOptions) {
                throw new Error('Because the new config file "common/config/rush/pnpm-config.json" is being used, ' +
                    `you must remove the old setting "pnpmOptions" from ${RushConstants.rushJsonFilename}`);
            }
        }
        catch (error) {
            if (FileSystem.isNotExistError(error)) {
                this.pnpmOptions = PnpmOptionsConfiguration.loadFromJsonObject(rushConfigurationJson.pnpmOptions || {}, this.commonTempFolder);
            }
            else {
                throw error;
            }
        }
        // TODO: Add an actual "packageManager" field in rush.json
        const packageManagerFields = [];
        this.isPnpm = false;
        if (rushConfigurationJson.npmVersion) {
            this.packageManager = 'npm';
            this.packageManagerOptions = this.npmOptions;
            packageManagerFields.push('npmVersion');
        }
        if (rushConfigurationJson.pnpmVersion) {
            this.packageManager = 'pnpm';
            this.isPnpm = true;
            this.packageManagerOptions = this.pnpmOptions;
            packageManagerFields.push('pnpmVersion');
        }
        if (rushConfigurationJson.yarnVersion) {
            this.packageManager = 'yarn';
            this.packageManagerOptions = this.yarnOptions;
            packageManagerFields.push('yarnVersion');
        }
        if (packageManagerFields.length === 0) {
            throw new Error(`The ${RushConstants.rushJsonFilename} configuration must specify one of: npmVersion, pnpmVersion, or yarnVersion`);
        }
        if (packageManagerFields.length > 1) {
            throw new Error(`The ${RushConstants.rushJsonFilename} configuration cannot specify both ${packageManagerFields[0]}` +
                ` and ${packageManagerFields[1]} `);
        }
        if (this.packageManager === 'npm') {
            this.packageManagerToolVersion = rushConfigurationJson.npmVersion;
            this.packageManagerWrapper = new NpmPackageManager(this.packageManagerToolVersion);
        }
        else if (this.packageManager === 'pnpm') {
            this.packageManagerToolVersion = rushConfigurationJson.pnpmVersion;
            this.packageManagerWrapper = new PnpmPackageManager(this.packageManagerToolVersion);
        }
        else {
            this.packageManagerToolVersion = rushConfigurationJson.yarnVersion;
            this.packageManagerWrapper = new YarnPackageManager(this.packageManagerToolVersion);
        }
        this.shrinkwrapFilename = this.packageManagerWrapper.shrinkwrapFilename;
        this.packageManagerToolFilename = path.resolve(path.join(this.commonTempFolder, `${this.packageManager}-local`, 'node_modules', '.bin', `${this.packageManager}`));
        RushConfiguration._validateCommonRushConfigFolder(this.commonRushConfigFolder, this.packageManagerWrapper, this.experimentsConfiguration, this.subspacesFeatureEnabled);
        this.projectFolderMinDepth =
            rushConfigurationJson.projectFolderMinDepth !== undefined
                ? rushConfigurationJson.projectFolderMinDepth
                : 1;
        if (this.projectFolderMinDepth < 1) {
            throw new Error('Invalid projectFolderMinDepth; the minimum possible value is 1');
        }
        this.projectFolderMaxDepth =
            rushConfigurationJson.projectFolderMaxDepth !== undefined
                ? rushConfigurationJson.projectFolderMaxDepth
                : 2;
        if (this.projectFolderMaxDepth < this.projectFolderMinDepth) {
            throw new Error('The projectFolderMaxDepth cannot be smaller than the projectFolderMinDepth');
        }
        this.allowMostlyStandardPackageNames = !!rushConfigurationJson.allowMostlyStandardPackageNames;
        this.packageNameParser = this.allowMostlyStandardPackageNames
            ? PackageNameParsers.mostlyStandard
            : PackageNameParsers.rushDefault;
        this.approvedPackagesPolicy = new ApprovedPackagesPolicy(this, rushConfigurationJson);
        this.gitAllowedEmailRegExps = [];
        this.gitSampleEmail = '';
        if (rushConfigurationJson.gitPolicy) {
            if (rushConfigurationJson.gitPolicy.sampleEmail) {
                this.gitSampleEmail = rushConfigurationJson.gitPolicy.sampleEmail;
            }
            if (rushConfigurationJson.gitPolicy.allowedEmailRegExps) {
                this.gitAllowedEmailRegExps = rushConfigurationJson.gitPolicy.allowedEmailRegExps;
                if (this.gitSampleEmail.trim().length < 1) {
                    throw new Error(`The ${RushConstants.rushJsonFilename} file is missing the "sampleEmail" option, ` +
                        'which is required when using "allowedEmailRegExps"');
                }
            }
            if (rushConfigurationJson.gitPolicy.versionBumpCommitMessage) {
                this.gitVersionBumpCommitMessage = rushConfigurationJson.gitPolicy.versionBumpCommitMessage;
            }
            if (rushConfigurationJson.gitPolicy.changeLogUpdateCommitMessage) {
                this.gitChangeLogUpdateCommitMessage = rushConfigurationJson.gitPolicy.changeLogUpdateCommitMessage;
            }
            if (rushConfigurationJson.gitPolicy.changefilesCommitMessage) {
                this.gitChangefilesCommitMessage = rushConfigurationJson.gitPolicy.changefilesCommitMessage;
            }
            if (rushConfigurationJson.gitPolicy.tagSeparator) {
                this.gitTagSeparator = rushConfigurationJson.gitPolicy.tagSeparator;
            }
        }
        this.hotfixChangeEnabled = false;
        if (rushConfigurationJson.hotfixChangeEnabled) {
            this.hotfixChangeEnabled = rushConfigurationJson.hotfixChangeEnabled;
        }
        if (!rushConfigurationJson.repository) {
            rushConfigurationJson.repository = {};
        }
        this.repositoryDefaultBranch = rushConfigurationJson.repository.defaultBranch || DEFAULT_BRANCH;
        this.repositoryDefaultRemote = rushConfigurationJson.repository.defaultRemote || DEFAULT_REMOTE;
        const repositoryFieldWithMultipleUrls = rushConfigurationJson.repository;
        const repositoryFieldWithSingleUrl = rushConfigurationJson.repository;
        if (repositoryFieldWithMultipleUrls.urls) {
            if (repositoryFieldWithSingleUrl.url) {
                throw new Error("The 'repository.url' field cannot be used when 'repository.urls' is present");
            }
            this.repositoryUrls = repositoryFieldWithMultipleUrls.urls;
        }
        else if (repositoryFieldWithSingleUrl.url) {
            this.repositoryUrls = [repositoryFieldWithSingleUrl.url];
        }
        else {
            this.repositoryUrls = [];
        }
        this.telemetryEnabled = !!rushConfigurationJson.telemetryEnabled;
        this.eventHooks = new EventHooks(rushConfigurationJson.eventHooks || {});
        this.versionPolicyConfigurationFilePath = path.join(this.commonRushConfigFolder, RushConstants.versionPoliciesFilename);
        this.versionPolicyConfiguration = new VersionPolicyConfiguration(this.versionPolicyConfigurationFilePath);
        this.customTipsConfigurationFilePath = path.join(this.commonRushConfigFolder, RushConstants.customTipsFilename);
        this.customTipsConfiguration = new CustomTipsConfiguration(this.customTipsConfigurationFilePath);
        const variants = new Set();
        for (const variantOptions of (_b = rushConfigurationJson.variants) !== null && _b !== void 0 ? _b : []) {
            const { variantName } = variantOptions;
            if (variants.has(variantName)) {
                throw new Error(`Duplicate variant named '${variantName}' specified in configuration.`);
            }
            variants.add(variantName);
        }
        this.variants = variants;
        this._pathTrees = new Map();
    }
    _initializeAndValidateLocalProjects() {
        var _a;
        this._projects = [];
        this._projectsByName = new Map();
        this._subspacesByName.clear();
        this._subspaces.length = 0;
        // Build the subspaces map
        const subspaceNames = [];
        let splitWorkspaceCompatibility = false;
        if ((_a = this.subspacesConfiguration) === null || _a === void 0 ? void 0 : _a.subspacesEnabled) {
            splitWorkspaceCompatibility = this.subspacesConfiguration.splitWorkspaceCompatibility;
            subspaceNames.push(...this.subspacesConfiguration.subspaceNames);
        }
        if (subspaceNames.indexOf(RushConstants.defaultSubspaceName) < 0) {
            subspaceNames.push(RushConstants.defaultSubspaceName);
        }
        // Sort the subspaces in alphabetical order.  This ensures that they are processed
        // in a deterministic order by the various Rush algorithms.
        subspaceNames.sort();
        for (const subspaceName of subspaceNames) {
            const subspace = new Subspace({
                subspaceName,
                rushConfiguration: this,
                splitWorkspaceCompatibility
            });
            this._subspacesByName.set(subspaceName, subspace);
            this._subspaces.push(subspace);
        }
        const defaultSubspace = this._subspacesByName.get(RushConstants.defaultSubspaceName);
        if (!defaultSubspace) {
            throw new InternalError('The default subspace was not created');
        }
        // Sort the projects array in alphabetical order.  This ensures that the packages
        // are processed in a deterministic order by the various Rush algorithms.
        const sortedProjectJsons = this.rushConfigurationJson.projects.slice(0);
        sortedProjectJsons.sort((a, b) => a.packageName.localeCompare(b.packageName));
        const allowedProjectTags = this.rushConfigurationJson.allowedProjectTags
            ? new Set(this.rushConfigurationJson.allowedProjectTags)
            : undefined;
        const usedTempNames = new Set();
        for (let i = 0, len = sortedProjectJsons.length; i < len; i++) {
            const projectJson = sortedProjectJsons[i];
            const tempProjectName = RushConfiguration._generateTempNameForProject(projectJson, usedTempNames);
            let subspace = undefined;
            if (this.subspacesFeatureEnabled) {
                if (projectJson.subspaceName) {
                    subspace = this._subspacesByName.get(projectJson.subspaceName);
                    if (subspace === undefined) {
                        throw new Error(`The project "${projectJson.packageName}" in ${RushConstants.rushJsonFilename} references` +
                            ` a nonexistent subspace "${projectJson.subspaceName}"`);
                    }
                }
            }
            if (subspace === undefined) {
                subspace = defaultSubspace;
            }
            const project = new RushConfigurationProject({
                projectJson,
                rushConfiguration: this,
                tempProjectName,
                allowedProjectTags,
                subspace
            });
            subspace._addProject(project);
            this._projects.push(project);
            if (this._projectsByName.has(project.packageName)) {
                throw new Error(`The project name "${project.packageName}" was specified more than once` +
                    ` in the ${RushConstants.rushJsonFilename} configuration file.`);
            }
            this._projectsByName.set(project.packageName, project);
        }
        for (const project of this._projects) {
            project.decoupledLocalDependencies.forEach((decoupledLocalDependency) => {
                if (!this.getProjectByName(decoupledLocalDependency)) {
                    throw new Error(`In ${RushConstants.rushJsonFilename}, the "${decoupledLocalDependency}" project does not exist,` +
                        ` but was referenced by the decoupledLocalDependencies (previously cyclicDependencyProjects) for ${project.packageName}`);
                }
            });
            this.versionPolicyConfiguration.validate(this.projectsByName);
            // Consumer relationships will be established the first time one is requested
        }
    }
    /**
     * Loads the configuration data from an Rush.json configuration file and returns
     * an RushConfiguration object.
     */
    static loadFromConfigurationFile(rushJsonFilename) {
        let resolvedRushJsonFilename = path.resolve(rushJsonFilename);
        // Load the rush.json before we fix the casing. If the case is wrong on a case-sensitive filesystem,
        // the next line show throw.
        const rushConfigurationJson = JsonFile.load(resolvedRushJsonFilename);
        try {
            resolvedRushJsonFilename = trueCasePathSync(resolvedRushJsonFilename);
        }
        catch (error) {
            /* ignore errors from true-case-path */
        }
        // Check the Rush version *before* we validate the schema, since if the version is outdated
        // then the schema may have changed. This should no longer be a problem after Rush 4.0 and the C2R wrapper,
        // but we'll validate anyway.
        const expectedRushVersion = rushConfigurationJson.rushVersion;
        const rushJsonBaseName = path.basename(resolvedRushJsonFilename);
        // If the version is missing or malformed, fall through and let the schema handle it.
        if (expectedRushVersion && semver.valid(expectedRushVersion)) {
            // Make sure the requested version isn't too old
            if (semver.lt(expectedRushVersion, MINIMUM_SUPPORTED_RUSH_JSON_VERSION)) {
                throw new Error(`${rushJsonBaseName} is version ${expectedRushVersion}, which is too old for this tool. ` +
                    `The minimum supported version is ${MINIMUM_SUPPORTED_RUSH_JSON_VERSION}.`);
            }
            // Make sure the requested version isn't too new.
            //
            // If the major/minor versions are the same, then we consider the file to be compatible.
            // This is somewhat lax, e.g. "5.0.2-dev.3" will be assumed to be loadable by rush-lib 5.0.0.
            //
            // IMPORTANT: Whenever a breaking change is introduced for one of the config files, we must
            // increment the minor version number for Rush.
            if (semver.major(Rush.version) !== semver.major(expectedRushVersion) ||
                semver.minor(Rush.version) !== semver.minor(expectedRushVersion)) {
                // If the major/minor are different, then make sure it's an older version.
                if (semver.lt(Rush.version, expectedRushVersion)) {
                    throw new Error(`Unable to load ${rushJsonBaseName} because its RushVersion is` +
                        ` ${rushConfigurationJson.rushVersion}, whereas @microsoft/rush-lib is version ${Rush.version}.` +
                        ` Consider upgrading the library.`);
                }
            }
        }
        RushConfiguration._jsonSchema.validateObject(rushConfigurationJson, resolvedRushJsonFilename);
        return new RushConfiguration(rushConfigurationJson, resolvedRushJsonFilename);
    }
    static tryLoadFromDefaultLocation(options) {
        const rushJsonLocation = RushConfiguration.tryFindRushJsonLocation(options);
        if (rushJsonLocation) {
            return RushConfiguration.loadFromConfigurationFile(rushJsonLocation);
        }
    }
    static loadFromDefaultLocation(options) {
        const rushConfiguration = RushConfiguration.tryLoadFromDefaultLocation(options);
        if (rushConfiguration) {
            return rushConfiguration;
        }
        else {
            throw Utilities.getRushConfigNotFoundError();
        }
    }
    /**
     * Find the rush.json location and return the path, or undefined if a rush.json can't be found.
     *
     * @privateRemarks
     * Keep this in sync with `findRushJsonLocation` in `rush-sdk/src/index.ts`.
     */
    static tryFindRushJsonLocation(options) {
        const optionsIn = options || {};
        const verbose = optionsIn.showVerbose || false;
        let currentFolder = optionsIn.startingFolder || process.cwd();
        let parentFolder = path.dirname(currentFolder);
        // look upwards at parent folders until we find a folder containing rush.json,
        // or we reach the root directory without finding a rush.json file
        while (parentFolder && parentFolder !== currentFolder) {
            const rushJsonFilename = path.join(currentFolder, RushConstants.rushJsonFilename);
            if (FileSystem.exists(rushJsonFilename)) {
                if (currentFolder !== optionsIn.startingFolder && verbose) {
                    // eslint-disable-next-line no-console
                    console.log('Found configuration in ' + rushJsonFilename);
                }
                if (verbose) {
                    // eslint-disable-next-line no-console
                    console.log('');
                }
                return rushJsonFilename;
            }
            currentFolder = parentFolder;
            parentFolder = path.dirname(currentFolder);
        }
        // no match
        return undefined;
    }
    /**
     * This generates the unique names that are used to create temporary projects
     * in the Rush common folder.
     * NOTE: sortedProjectJsons is sorted by the caller.
     */
    static _generateTempNameForProject(projectJson, usedTempNames) {
        // If the name is "@ms/MyProject", extract the "MyProject" part
        const unscopedName = PackageNameParsers.permissive.getUnscopedName(projectJson.packageName);
        // Generate a unique like name "@rush-temp/MyProject", or "@rush-temp/MyProject-2" if
        // there is a naming conflict
        let counter = 0;
        let tempProjectName = `${RushConstants.rushTempNpmScope}/${unscopedName}`;
        while (usedTempNames.has(tempProjectName)) {
            ++counter;
            tempProjectName = `${RushConstants.rushTempNpmScope}/${unscopedName}-${counter}`;
        }
        usedTempNames.add(tempProjectName);
        return tempProjectName;
    }
    /**
     * If someone adds a config file in the "common/rush/config" folder, it would be a bad
     * experience for Rush to silently ignore their file simply because they misspelled the
     * filename, or maybe it's an old format that's no longer supported.  The
     * _validateCommonRushConfigFolder() function makes sure that this folder only contains
     * recognized config files.
     */
    static _validateCommonRushConfigFolder(commonRushConfigFolder, packageManagerWrapper, experiments, subspacesFeatureEnabled) {
        if (!FileSystem.exists(commonRushConfigFolder)) {
            // eslint-disable-next-line no-console
            console.log(`Creating folder: ${commonRushConfigFolder}`);
            FileSystem.ensureFolder(commonRushConfigFolder);
            return;
        }
        for (const filename of FileSystem.readFolderItemNames(commonRushConfigFolder)) {
            // Ignore things that aren't actual files
            const stat = FileSystem.getLinkStatistics(path.join(commonRushConfigFolder, filename));
            if (!stat.isFile() && !stat.isSymbolicLink()) {
                continue;
            }
            // Ignore harmless file extensions
            const fileExtension = path.extname(filename);
            if (['.bak', '.disabled', '.md', '.old', '.orig'].indexOf(fileExtension) >= 0) {
                continue;
            }
            // Check if there are prohibited files when subspaces is enabled
            if (subspacesFeatureEnabled) {
                if (filename === RushConstants.pnpmfileV6Filename || filename === RushConstants.pnpmfileV1Filename) {
                    throw new Error('When the subspaces feature is enabled, a separate lockfile is stored in each subspace folder. ' +
                        `To avoid confusion, remove this file: ${commonRushConfigFolder}/${filename}`);
                }
            }
            // Ignore hidden files such as ".DS_Store"
            if (filename.startsWith('.')) {
                continue;
            }
            if (filename.startsWith('deploy-') && fileExtension === '.json') {
                // Ignore "rush deploy" files, which use the naming pattern "deploy-<scenario-name>.json".
                continue;
            }
            const knownSet = new Set(knownRushConfigFilenames.map((x) => x.toUpperCase()));
            // Add the shrinkwrap filename for the package manager to the known set.
            knownSet.add(packageManagerWrapper.shrinkwrapFilename.toUpperCase());
            // If the package manager is pnpm, then also add the pnpm file to the known set.
            if (packageManagerWrapper.packageManager === 'pnpm') {
                const pnpmPackageManager = packageManagerWrapper;
                knownSet.add(pnpmPackageManager.pnpmfileFilename.toUpperCase());
            }
            // Is the filename something we know?  If not, report an error.
            if (!knownSet.has(filename.toUpperCase())) {
                throw new Error(`An unrecognized file "${filename}" was found in the Rush config folder:` +
                    ` ${commonRushConfigFolder}`);
            }
        }
        const pinnedVersionsFilename = path.join(commonRushConfigFolder, RushConstants.pinnedVersionsFilename);
        if (FileSystem.exists(pinnedVersionsFilename)) {
            throw new Error('The "pinned-versions.json" config file is no longer supported;' +
                ' please move your settings to the "preferredVersions" field of a "common-versions.json" config file.' +
                ` (See the ${RushConstants.rushWebSiteUrl} documentation for details.)\n\n` +
                pinnedVersionsFilename);
        }
    }
    /**
     * The fully resolved path for the "autoinstallers" folder.
     * Example: `C:\MyRepo\common\autoinstallers`
     */
    get commonAutoinstallersFolder() {
        return path.join(this.commonFolder, 'autoinstallers');
    }
    /**
     * The folder where rush-plugin options json files are stored.
     * Example: `C:\MyRepo\common\config\rush-plugins`
     */
    get rushPluginOptionsFolder() {
        return path.join(this.commonFolder, 'config', 'rush-plugins');
    }
    /**
     * The full path of the temporary shrinkwrap file that is used during "rush install".
     * This file may get rewritten by the package manager during installation.
     * @remarks
     * This property merely reports the filename; the file itself may not actually exist.
     * Example: `C:\MyRepo\common\temp\npm-shrinkwrap.json` or `C:\MyRepo\common\temp\pnpm-lock.yaml`
     *
     * @deprecated Introduced with subspaces is subspace specific tempShrinkwrapFilename accessible from the Subspace class.
     */
    get tempShrinkwrapFilename() {
        if (this.subspacesFeatureEnabled) {
            throw new Error('tempShrinkwrapFilename() is not available when using subspaces. Use the subspace specific temp shrinkwrap filename.');
        }
        return path.join(this.commonTempFolder, this.shrinkwrapFilename);
    }
    /**
     * The full path of a backup copy of tempShrinkwrapFilename. This backup copy is made
     * before installation begins, and can be compared to determine how the package manager
     * modified tempShrinkwrapFilename.
     * @remarks
     * This property merely reports the filename; the file itself may not actually exist.
     * Example: `C:\MyRepo\common\temp\npm-shrinkwrap-preinstall.json`
     * or `C:\MyRepo\common\temp\pnpm-lock-preinstall.yaml`
     *
     * @deprecated Introduced with subspaces is subspace specific tempShrinkwrapPreinstallFilename accessible from the Subspace class.
     */
    get tempShrinkwrapPreinstallFilename() {
        if (this.subspacesFeatureEnabled) {
            throw new Error('tempShrinkwrapPreinstallFilename() is not available when using subspaces. Use the subspace specific temp shrinkwrap preinstall filename.');
        }
        const parsedPath = path.parse(this.tempShrinkwrapFilename);
        return path.join(parsedPath.dir, parsedPath.name + '-preinstall' + parsedPath.ext);
    }
    /**
     * Returns an English phrase such as "shrinkwrap file" that can be used in logging messages
     * to refer to the shrinkwrap file using appropriate terminology for the currently selected
     * package manager.
     */
    get shrinkwrapFilePhrase() {
        return `shrinkwrap file (${this.shrinkwrapFilename})`;
    }
    /**
     * The filename of the build dependency data file.  By default this is
     * called 'rush-link.json' resides in the Rush common folder.
     * Its data structure is defined by IRushLinkJson.
     *
     * Example: `C:\MyRepo\common\temp\rush-link.json`
     *
     * @deprecated The "rush-link.json" file was removed in Rush 5.30.0.
     * Use `RushConfigurationProject.localDependencyProjects` instead.
     */
    get rushLinkJsonFilename() {
        throw new Error('The "rush-link.json" file was removed in Rush 5.30.0. Use ' +
            'RushConfigurationProject.localDependencyProjects instead.');
    }
    /**
     * The default fully-qualified git remote branch of the repository. This helps "rush change" find the right branch to compare against.
     */
    get repositoryDefaultFullyQualifiedRemoteBranch() {
        return `${this.repositoryDefaultRemote}/${this.repositoryDefaultBranch}`;
    }
    get projects() {
        if (!this._projects) {
            this._initializeAndValidateLocalProjects();
        }
        return this._projects;
    }
    /**
     * @beta
     */
    get defaultSubspace() {
        // TODO: Enable the default subspace to be obtained without initializing the full set of all projects
        if (!this._projects) {
            this._initializeAndValidateLocalProjects();
        }
        const defaultSubspace = this.tryGetSubspace(RushConstants.defaultSubspaceName);
        if (!defaultSubspace) {
            throw new InternalError('Default subspace was not created');
        }
        return defaultSubspace;
    }
    /**
     * A list of all the available subspaces in this workspace.
     * @beta
     */
    get subspaces() {
        if (!this._projects) {
            this._initializeAndValidateLocalProjects();
        }
        return this._subspaces;
    }
    /**
     * @beta
     */
    tryGetSubspace(subspaceName) {
        var _a;
        if (!this._projects) {
            this._initializeAndValidateLocalProjects();
        }
        const subspace = this._subspacesByName.get(subspaceName);
        if (!subspace) {
            // If the name is not even valid, that is more important information than if the subspace doesn't exist
            SubspacesConfiguration.requireValidSubspaceName(subspaceName, (_a = this.subspacesConfiguration) === null || _a === void 0 ? void 0 : _a.splitWorkspaceCompatibility);
        }
        return subspace;
    }
    /**
     * @beta
     */
    getSubspace(subspaceName) {
        const subspace = this.tryGetSubspace(subspaceName);
        if (!subspace) {
            throw new Error(`The specified subspace "${subspaceName}" does not exist`);
        }
        return subspace;
    }
    /**
     * Returns the set of subspaces that the given projects belong to
     * @beta
     */
    getSubspacesForProjects(projects) {
        if (!this._projects) {
            this._initializeAndValidateLocalProjects();
        }
        const subspaceSet = new Set();
        for (const project of projects) {
            subspaceSet.add(project.subspace);
        }
        return subspaceSet;
    }
    /**
     * @beta
     */
    get projectsByName() {
        if (!this._projectsByName) {
            this._initializeAndValidateLocalProjects();
        }
        return this._projectsByName;
    }
    /**
     * Obtains the mapping from custom tags to projects.
     * @beta
     */
    get projectsByTag() {
        if (!this._projectsByTag) {
            const projectsByTag = new Map();
            for (const project of this.projects) {
                for (const tag of project.tags) {
                    let collection = projectsByTag.get(tag);
                    if (!collection) {
                        projectsByTag.set(tag, (collection = new Set()));
                    }
                    collection.add(project);
                }
            }
            this._projectsByTag = projectsByTag;
        }
        return this._projectsByTag;
    }
    /**
     * Settings from the common-versions.json config file.
     * @remarks
     * If the common-versions.json file is missing, this property will not be undefined.
     * Instead it will be initialized in an empty state, and calling CommonVersionsConfiguration.save()
     * will create the file.
     *
     * @deprecated Use `getCommonVersions` instead, which gets the correct common version data
     * for a given active variant.
     */
    get commonVersions() {
        return this.defaultSubspace.getCommonVersions(undefined);
    }
    /**
     * Gets the currently-installed variant, if an installation has occurred.
     * For Rush operations which do not take a --variant parameter, this method
     * determines which variant, if any, was last specified when performing "rush install"
     * or "rush update".
     */
    async getCurrentlyInstalledVariantAsync() {
        var _a, _b;
        if (!this._currentVariantJsonLoadingPromise) {
            this._currentVariantJsonLoadingPromise = this._loadCurrentVariantJsonAsync();
        }
        return (_b = (_a = (await this._currentVariantJsonLoadingPromise)) === null || _a === void 0 ? void 0 : _a.variant) !== null && _b !== void 0 ? _b : undefined;
    }
    /**
     * @deprecated Use {@link Subspace.getCommonVersionsFilePath} instead
     */
    getCommonVersionsFilePath(subspace, variant) {
        return (subspace !== null && subspace !== void 0 ? subspace : this.defaultSubspace).getCommonVersionsFilePath(variant);
    }
    /**
     * @deprecated Use {@link Subspace.getCommonVersions} instead
     */
    getCommonVersions(subspace, variant) {
        return (subspace !== null && subspace !== void 0 ? subspace : this.defaultSubspace).getCommonVersions(variant);
    }
    /**
     * Returns a map of all direct dependencies that only have a single semantic version specifier.
     *
     * @param subspace - The subspace to use
     * @param variant - The name of the current variant in use by the active command.
     *
     * @returns A map of dependency name --\> version specifier for implicitly preferred versions.
     */
    getImplicitlyPreferredVersions(subspace, variant) {
        // TODO: During the next major release of Rush, replace this `require` call with a dynamic import, and
        // change this function to be async.
        const DependencyAnalyzerModule = require('../logic/DependencyAnalyzer');
        const dependencyAnalyzer = DependencyAnalyzerModule.DependencyAnalyzer.forRushConfiguration(this);
        const dependencyAnalysis = dependencyAnalyzer.getAnalysis(subspace, variant, false);
        return dependencyAnalysis.implicitlyPreferredVersionByPackageName;
    }
    /**
     * @deprecated Use {@link Subspace.getRepoStateFilePath} instead
     */
    getRepoStateFilePath(subspace) {
        return (subspace !== null && subspace !== void 0 ? subspace : this.defaultSubspace).getRepoStateFilePath();
    }
    /**
     * @deprecated Use {@link Subspace.getRepoState} instead
     */
    getRepoState(subspace) {
        return (subspace !== null && subspace !== void 0 ? subspace : this.defaultSubspace).getRepoState();
    }
    /**
     * @deprecated Use {@link Subspace.getCommittedShrinkwrapFilePath} instead
     */
    getCommittedShrinkwrapFilename(subspace, variant) {
        return (subspace !== null && subspace !== void 0 ? subspace : this.defaultSubspace).getCommittedShrinkwrapFilePath(variant);
    }
    /**
     * @deprecated Use {@link Subspace.getPnpmfilePath} instead
     */
    getPnpmfilePath(subspace, variant) {
        return (subspace !== null && subspace !== void 0 ? subspace : this.defaultSubspace).getPnpmfilePath(variant);
    }
    /**
     * Looks up a project in the projectsByName map.  If the project is not found,
     * then undefined is returned.
     */
    getProjectByName(projectName) {
        return this.projectsByName.get(projectName);
    }
    /**
     * This is used e.g. by command-line interfaces such as "rush build --to example".
     * If "example" is not a project name, then it also looks for a scoped name
     * like `@something/example`.  If exactly one project matches this heuristic, it
     * is returned.  Otherwise, undefined is returned.
     */
    findProjectByShorthandName(shorthandProjectName) {
        // Is there an exact match?
        let result = this.projectsByName.get(shorthandProjectName);
        if (result) {
            return result;
        }
        // Is there an approximate match?
        for (const project of this.projects) {
            if (this.packageNameParser.getUnscopedName(project.packageName) === shorthandProjectName) {
                if (result) {
                    // Ambiguous -- there is more than one match
                    return undefined;
                }
                else {
                    result = project;
                }
            }
        }
        return result;
    }
    /**
     * Looks up a project by its RushConfigurationProject.tempProjectName field.
     * @returns The found project, or undefined if no match was found.
     */
    findProjectByTempName(tempProjectName) {
        // Is there an approximate match?
        for (const project of this.projects) {
            if (project.tempProjectName === tempProjectName) {
                return project;
            }
        }
        return undefined;
    }
    /**
     * @returns An optimized lookup engine to find a project by its path relative to the specified root.
     * @beta
     */
    getProjectLookupForRoot(rootPath) {
        let pathTree = this._pathTrees.get(rootPath);
        if (!pathTree) {
            this._pathTrees.set(rootPath, (pathTree = new LookupByPath()));
            for (const project of this.projects) {
                const relativePath = path.relative(rootPath, project.projectFolder);
                pathTree.setItemFromSegments(LookupByPath.iteratePathSegments(relativePath, path.sep), project);
            }
        }
        return pathTree;
    }
    /**
     * Returns the project for which the specified path is underneath that project's folder.
     * If the path is not under any project's folder, returns undefined.
     */
    tryGetProjectForPath(currentFolderPath) {
        // TODO: Improve the method in which a package is found, perhaps without having to sort / loop though the entire package list
        const resolvedPath = path.resolve(currentFolderPath);
        const sortedProjects = this.projects.sort((a, b) => b.projectFolder.length - a.projectFolder.length);
        for (const project of sortedProjects) {
            if (Path.isUnderOrEqual(resolvedPath, project.projectFolder)) {
                return project;
            }
        }
        return undefined;
    }
    async _loadCurrentVariantJsonAsync() {
        try {
            return await JsonFile.loadAsync(this.currentVariantJsonFilePath);
        }
        catch (e) {
            if (!FileSystem.isNotExistError(e)) {
                throw e;
            }
        }
    }
}
RushConfiguration._jsonSchema = JsonSchema.fromLoadedObject(schemaJson);
//# sourceMappingURL=RushConfiguration.js.map