"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.Subspace = void 0;
const path = __importStar(require("node:path"));
const crypto = __importStar(require("node:crypto"));
const node_core_library_1 = require("@rushstack/node-core-library");
const EnvironmentConfiguration_1 = require("./EnvironmentConfiguration");
const RushConstants_1 = require("../logic/RushConstants");
const CommonVersionsConfiguration_1 = require("./CommonVersionsConfiguration");
const RepoStateFile_1 = require("../logic/RepoStateFile");
const PnpmOptionsConfiguration_1 = require("../logic/pnpm/PnpmOptionsConfiguration");
const SubspacePnpmfileConfiguration_1 = require("../logic/pnpm/SubspacePnpmfileConfiguration");
/**
 * This represents the subspace configurations for a repository, based on the "subspaces.json"
 * configuration file.
 * @public
 */
class Subspace {
    constructor(options) {
        this._projects = [];
        this._commonVersionsConfiguration = undefined;
        this._cachedPnpmOptions = undefined;
        // If true, then _cachedPnpmOptions has been initialized.
        this._cachedPnpmOptionsInitialized = false;
        this.subspaceName = options.subspaceName;
        this._rushConfiguration = options.rushConfiguration;
        this._splitWorkspaceCompatibility = options.splitWorkspaceCompatibility;
    }
    /**
     * Returns the list of projects belonging to this subspace.
     * @beta
     */
    getProjects() {
        return this._projects;
    }
    /**
     * Returns the parsed contents of the pnpm-config.json config file.
     * @beta
     */
    getPnpmOptions() {
        if (!this._cachedPnpmOptionsInitialized) {
            // Calculate these outside the try/catch block since their error messages shouldn't be annotated:
            const subspaceTempFolder = this.getSubspaceTempFolderPath();
            try {
                this._cachedPnpmOptions = PnpmOptionsConfiguration_1.PnpmOptionsConfiguration.loadFromJsonFileOrThrow(this.getPnpmConfigFilePath(), subspaceTempFolder);
                this._cachedPnpmOptionsInitialized = true;
            }
            catch (e) {
                if (node_core_library_1.FileSystem.isNotExistError(e)) {
                    this._cachedPnpmOptions = undefined;
                    this._cachedPnpmOptionsInitialized = true;
                }
                else {
                    throw new Error(`The subspace "${this.subspaceName}" has an invalid pnpm-config.json file:\n` + e.message);
                }
            }
        }
        return this._cachedPnpmOptions;
    }
    _ensureDetail() {
        if (!this._detail) {
            const rushConfiguration = this._rushConfiguration;
            let subspaceConfigFolderPath;
            let subspacePnpmPatchesFolderPath;
            if (rushConfiguration.subspacesFeatureEnabled) {
                if (!rushConfiguration.pnpmOptions.useWorkspaces) {
                    throw new Error(`The Rush subspaces feature is enabled.  You must set useWorkspaces=true in pnpm-config.json.`);
                }
                // If this subspace doesn't have a configuration folder, check if it is in the project folder itself
                // if the splitWorkspaceCompatibility option is enabled in the subspace configuration
                // Example: C:\MyRepo\common\config\subspaces\my-subspace
                const standardSubspaceConfigFolder = `${rushConfiguration.commonFolder}/config/subspaces/${this.subspaceName}`;
                subspaceConfigFolderPath = standardSubspaceConfigFolder;
                if (this._splitWorkspaceCompatibility && this.subspaceName.startsWith('split_')) {
                    if (node_core_library_1.FileSystem.exists(standardSubspaceConfigFolder + '/pnpm-lock.yaml')) {
                        throw new Error(`The split workspace subspace "${this.subspaceName}" cannot use a common/config folder: ` +
                            standardSubspaceConfigFolder);
                    }
                    if (this._projects.length !== 1) {
                        throw new Error(`The split workspace subspace "${this.subspaceName}" contains ${this._projects.length}` +
                            ` projects; there must be exactly one project.`);
                    }
                    const project = this._projects[0];
                    subspaceConfigFolderPath = `${project.projectFolder}/subspace/${this.subspaceName}`;
                    // Ensure that this project does not have it's own pnpmfile.cjs or .npmrc file
                    if (node_core_library_1.FileSystem.exists(`${project.projectFolder}/.npmrc`)) {
                        throw new Error(`The project level configuration file ${project.projectFolder}/.npmrc is no longer valid. Please use a ${subspaceConfigFolderPath}/.npmrc file instead.`);
                    }
                    if (node_core_library_1.FileSystem.exists(`${project.projectFolder}/.pnpmfile.cjs`)) {
                        throw new Error(`The project level configuration file ${project.projectFolder}/.pnpmfile.cjs is no longer valid. Please use a ${subspaceConfigFolderPath}/.pnpmfile.cjs file instead.`);
                    }
                }
                if (!node_core_library_1.FileSystem.exists(subspaceConfigFolderPath)) {
                    throw new Error(`The configuration folder for the "${this.subspaceName}" subspace does not exist: ` +
                        subspaceConfigFolderPath);
                }
                subspacePnpmPatchesFolderPath = `${subspaceConfigFolderPath}/${RushConstants_1.RushConstants.pnpmPatchesCommonFolderName}`;
            }
            else {
                // Example: C:\MyRepo\common\config\rush
                subspaceConfigFolderPath = rushConfiguration.commonRushConfigFolder;
                // Example: C:\MyRepo\common\pnpm-patches
                subspacePnpmPatchesFolderPath = `${rushConfiguration.commonFolder}/${RushConstants_1.RushConstants.pnpmPatchesCommonFolderName}`;
            }
            // Example: C:\MyRepo\common\temp
            const commonTempFolder = EnvironmentConfiguration_1.EnvironmentConfiguration.rushTempFolderOverride || rushConfiguration.commonTempFolder;
            let subspaceTempFolderPath;
            if (rushConfiguration.subspacesFeatureEnabled) {
                // Example: C:\MyRepo\common\temp\my-subspace
                subspaceTempFolderPath = `${commonTempFolder}/${this.subspaceName}`;
            }
            else {
                // Example: C:\MyRepo\common\temp
                subspaceTempFolderPath = commonTempFolder;
            }
            // Example: C:\MyRepo\common\temp\my-subspace\pnpm-lock.yaml
            const tempShrinkwrapFilePath = `${subspaceTempFolderPath}/${rushConfiguration.shrinkwrapFilename}`;
            /// From "C:\MyRepo\common\temp\pnpm-lock.yaml" --> "C:\MyRepo\common\temp\pnpm-lock-preinstall.yaml"
            const parsedPath = path.parse(tempShrinkwrapFilePath);
            const tempShrinkwrapPreinstallFilePath = `${parsedPath.dir}/${parsedPath.name}-preinstall${parsedPath.ext}`;
            this._detail = {
                subspaceConfigFolderPath,
                subspacePnpmPatchesFolderPath,
                subspaceTempFolderPath,
                tempShrinkwrapFilePath,
                tempShrinkwrapPreinstallFilePath
            };
        }
        return this._detail;
    }
    /**
     * Returns the full path of the folder containing this subspace's variant-dependent configuration files
     * such as `pnpm-lock.yaml`.
     *
     * Example (variants):               `C:\MyRepo\common\config\rush\variants\my-variant`
     * Example (variants and subspaces): `C:\MyRepo\common\config\subspaces\my-subspace\variants\my-variant`
     * Example (subspaces):              `C:\MyRepo\common\config\subspaces\my-subspace`
     * Example (neither):                `C:\MyRepo\common\config\rush`
     * @beta
     *
     * @remarks
     * The following files may be variant-dependent:
     * - Lockfiles: (i.e. - `pnpm-lock.yaml`, `npm-shrinkwrap.json`, `yarn.lock`, etc)
     * - 'common-versions.json'
     * - 'pnpmfile.js'/'.pnpmfile.cjs'
     */
    getVariantDependentSubspaceConfigFolderPath(variant) {
        const subspaceConfigFolderPath = this.getSubspaceConfigFolderPath();
        if (!variant) {
            return subspaceConfigFolderPath;
        }
        else {
            return `${subspaceConfigFolderPath}/${RushConstants_1.RushConstants.rushVariantsFolderName}/${variant}`;
        }
    }
    /**
     * Returns the full path of the folder containing this subspace's configuration files such as `pnpm-lock.yaml`.
     *
     * Example (subspaces feature enabled):   `C:\MyRepo\common\config\subspaces\my-subspace`
     * Example (subspaces feature disabled):  `C:\MyRepo\common\config\rush`
     * @beta
     */
    getSubspaceConfigFolderPath() {
        return this._ensureDetail().subspaceConfigFolderPath;
    }
    /**
     * Returns the full path of the folder containing this subspace's configuration files such as `pnpm-lock.yaml`.
     *
     * Example (subspaces feature enabled):   `C:\MyRepo\common\config\subspaces\my-subspace\pnpm-patches`
     * Example (subspaces feature disabled):  `C:\MyRepo\common\pnpm-patches`
     * @beta
     */
    getSubspacePnpmPatchesFolderPath() {
        return this._ensureDetail().subspacePnpmPatchesFolderPath;
    }
    /**
     * The full path of the folder where the subspace's node_modules and other temporary files will be stored.
     *
     * Example (subspaces feature enabled):   `C:\MyRepo\common\temp\subspaces\my-subspace`
     * Example (subspaces feature disabled):  `C:\MyRepo\common\temp`
     * @beta
     */
    getSubspaceTempFolderPath() {
        return this._ensureDetail().subspaceTempFolderPath;
    }
    /**
     * Returns full path of the temporary shrinkwrap file for a specific subspace and returns the common workspace
     * shrinkwrap if no subspaceName is provided.
     * @remarks
     * This function takes the subspace name, and returns the full path for the subspace's shrinkwrap file.
     * This function also consults the deprecated option to allow for shrinkwraps to be stored under a package folder.
     * This shrinkwrap file is used during "rush install", and may be rewritten by the package manager during installation
     * This property merely reports the filename, the file itself may not actually exist.
     * example: `C:\MyRepo\common\<subspace_name>\pnpm-lock.yaml`
     * @beta
     */
    getTempShrinkwrapFilename() {
        return this._ensureDetail().tempShrinkwrapFilePath;
    }
    /**
     * @deprecated - Use {@link Subspace.getTempShrinkwrapPreinstallFilePath} instead.
     */
    getTempShrinkwrapPreinstallFilename(subspaceName) {
        return this.getTempShrinkwrapPreinstallFilePath();
    }
    /**
     * 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`
     * @beta
     */
    getTempShrinkwrapPreinstallFilePath() {
        return this._ensureDetail().tempShrinkwrapPreinstallFilePath;
    }
    /**
     * Gets the full path to the common-versions.json config file for this subspace.
     *
     * Example (subspaces feature enabled):   `C:\MyRepo\common\config\subspaces\my-subspace\common-versions.json`
     * Example (subspaces feature disabled):  `C:\MyRepo\common\config\rush\common-versions.json`
     * @beta
     */
    getCommonVersionsFilePath(variant) {
        return (this.getVariantDependentSubspaceConfigFolderPath(variant) + '/' + RushConstants_1.RushConstants.commonVersionsFilename);
    }
    /**
     * Gets the full path to the pnpm-config.json config file for this subspace.
     *
     * Example (subspaces feature enabled):   `C:\MyRepo\common\config\subspaces\my-subspace\pnpm-config.json`
     * Example (subspaces feature disabled):  `C:\MyRepo\common\config\rush\pnpm-config.json`
     * @beta
     */
    getPnpmConfigFilePath() {
        return this.getSubspaceConfigFolderPath() + '/' + RushConstants_1.RushConstants.pnpmConfigFilename;
    }
    /**
     * Gets the settings from the common-versions.json config file.
     * @beta
     */
    getCommonVersions(variant) {
        const commonVersionsFilePath = this.getCommonVersionsFilePath(variant);
        if (!this._commonVersionsConfiguration) {
            this._commonVersionsConfiguration = CommonVersionsConfiguration_1.CommonVersionsConfiguration.loadFromFile(commonVersionsFilePath, this._rushConfiguration);
        }
        return this._commonVersionsConfiguration;
    }
    /**
     * Gets the ensureConsistentVersions property from the common-versions.json config file,
     * or from the rush.json file if it isn't defined in common-versions.json
     * @beta
     */
    shouldEnsureConsistentVersions(variant) {
        // If the ensureConsistentVersions field is defined, return the value of the field
        const commonVersions = this.getCommonVersions(variant);
        if (commonVersions.ensureConsistentVersions !== undefined) {
            return commonVersions.ensureConsistentVersions;
        }
        // Fallback to ensureConsistentVersions in rush.json if the setting is not defined in
        // the common-versions.json file
        return this._rushConfiguration.ensureConsistentVersions;
    }
    /**
     * Gets the path to the repo-state.json file.
     * @beta
     */
    getRepoStateFilePath() {
        return this.getSubspaceConfigFolderPath() + '/' + RushConstants_1.RushConstants.repoStateFilename;
    }
    /**
     * Gets the contents from the repo-state.json file.
     * @param subspaceName - The name of the subspace in use by the active command.
     * @beta
     */
    getRepoState() {
        const repoStateFilePath = this.getRepoStateFilePath();
        return RepoStateFile_1.RepoStateFile.loadFromFile(repoStateFilePath);
    }
    /**
     * @deprecated - Use {@link Subspace.getCommittedShrinkwrapFilePath} instead.
     */
    getCommittedShrinkwrapFilename() {
        return this.getCommittedShrinkwrapFilePath(undefined);
    }
    /**
     * Gets the committed shrinkwrap file name for a specific variant.
     * @param variant - The name of the current variant in use by the active command.
     * @beta
     */
    getCommittedShrinkwrapFilePath(variant) {
        const subspaceConfigFolderPath = this.getVariantDependentSubspaceConfigFolderPath(variant);
        return `${subspaceConfigFolderPath}/${this._rushConfiguration.shrinkwrapFilename}`;
    }
    /**
     * Gets the absolute path for "pnpmfile.js" for a specific subspace.
     * @param subspace - The name of the current subspace in use by the active command.
     * @remarks
     * The file path is returned even if PNPM is not configured as the package manager.
     * @beta
     */
    getPnpmfilePath(variant) {
        const subspaceConfigFolderPath = this.getVariantDependentSubspaceConfigFolderPath(variant);
        const pnpmFilename = this._rushConfiguration.packageManagerWrapper
            .pnpmfileFilename;
        return `${subspaceConfigFolderPath}/${pnpmFilename}`;
    }
    /**
     * Returns true if the specified project belongs to this subspace.
     * @beta
     */
    contains(project) {
        return project.subspace.subspaceName === this.subspaceName;
    }
    /** @internal */
    _addProject(project) {
        this._projects.push(project);
    }
    /**
     * Computes a hash of the PNPM catalog definitions for this subspace.
     * Returns undefined if no catalogs are defined.
     */
    getPnpmCatalogsHash() {
        const pnpmOptions = this.getPnpmOptions();
        if (!pnpmOptions) {
            return undefined;
        }
        const catalogData = {};
        if (pnpmOptions.globalCatalogs && Object.keys(pnpmOptions.globalCatalogs).length !== 0) {
            Object.assign(catalogData, pnpmOptions.globalCatalogs);
        }
        // If no catalogs are defined, return undefined
        if (Object.keys(catalogData).length === 0) {
            return undefined;
        }
        const hash = crypto.createHash('sha1');
        hash.update(JSON.stringify(catalogData));
        return hash.digest('hex');
    }
    /**
     * Returns hash value of injected dependencies in related package.json.
     * @beta
     */
    getPackageJsonInjectedDependenciesHash(variant) {
        var _a;
        const allPackageJson = [];
        const relatedProjects = [];
        const subspacePnpmfileShimSettings = SubspacePnpmfileConfiguration_1.SubspacePnpmfileConfiguration.getSubspacePnpmfileShimSettings(this._rushConfiguration, this, variant);
        for (const rushProject of this.getProjects()) {
            const injectedDependencies = ((_a = subspacePnpmfileShimSettings === null || subspacePnpmfileShimSettings === void 0 ? void 0 : subspacePnpmfileShimSettings.subspaceProjects[rushProject.packageName]) === null || _a === void 0 ? void 0 : _a.injectedDependencies) || [];
            if (injectedDependencies.length === 0) {
                continue;
            }
            const injectedDependencySet = new Set(injectedDependencies);
            for (const dependencyProject of rushProject.dependencyProjects) {
                if (injectedDependencySet.has(dependencyProject.packageName)) {
                    relatedProjects.push(dependencyProject);
                }
            }
        }
        // this means no injected dependencies found for current subspace
        if (relatedProjects.length === 0) {
            return undefined;
        }
        const allWorkspaceProjectSet = new Set(this._rushConfiguration.projects.map((rushProject) => rushProject.packageName));
        // get all related package.json
        while (relatedProjects.length > 0) {
            const rushProject = relatedProjects.pop();
            // collect fields that could update the `pnpm-lock.yaml`
            const { name, bin, dependencies, devDependencies, peerDependencies, optionalDependencies, dependenciesMeta, peerDependenciesMeta, resolutions } = rushProject.packageJson;
            // special handing for peerDependencies
            // for workspace packages, the version range is meaningless here.
            if (peerDependencies) {
                for (const packageName of Object.keys(peerDependencies)) {
                    if (allWorkspaceProjectSet.has(packageName)) {
                        peerDependencies[packageName] = 'workspace:*';
                    }
                }
            }
            allPackageJson.push({
                name,
                bin,
                dependencies,
                devDependencies,
                peerDependencies,
                optionalDependencies,
                dependenciesMeta,
                peerDependenciesMeta,
                resolutions
            });
            relatedProjects.push(...rushProject.dependencyProjects);
        }
        const collator = new Intl.Collator('en');
        allPackageJson.sort((pa, pb) => collator.compare(pa.name, pb.name));
        const hash = crypto.createHash('sha1');
        for (const packageFile of allPackageJson) {
            hash.update(JSON.stringify(packageFile));
        }
        const packageJsonInjectedDependenciesHash = hash.digest('hex');
        return packageJsonInjectedDependenciesHash;
    }
}
exports.Subspace = Subspace;
//# sourceMappingURL=Subspace.js.map