From 77a266aebfcfee2e5e9d25202b65d28561ec1628 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Fri, 26 Jan 2024 21:27:38 -0800 Subject: [PATCH] Refactor to replace "subspaceName" with a Subspace class --- common/reviews/api/rush-lib.api.md | 95 +++-- .../src/api/ApprovedPackagesPolicy.ts | 4 +- .../src/api/BuildCacheConfiguration.ts | 2 +- .../rush-lib/src/api/CobuildConfiguration.ts | 2 +- libraries/rush-lib/src/api/LastInstallFlag.ts | 5 +- libraries/rush-lib/src/api/LastLinkFlag.ts | 9 +- .../rush-lib/src/api/RushConfiguration.ts | 384 ++++++------------ .../src/api/RushConfigurationProject.ts | 27 +- libraries/rush-lib/src/api/Subspace.ts | 271 ++++++++++++ .../src/api/SubspacesConfiguration.ts | 68 ++-- .../src/api/VersionPolicyConfiguration.ts | 2 +- .../src/api/test/RushConfiguration.test.ts | 10 +- .../rush-lib/src/cli/RushCommandLineParser.ts | 2 +- .../src/cli/RushPnpmCommandLineParser.ts | 20 +- .../rush-lib/src/cli/RushXCommandLine.ts | 2 +- .../src/cli/actions/BaseInstallAction.ts | 30 +- .../src/cli/actions/BaseRushAction.ts | 4 +- .../rush-lib/src/cli/actions/DeployAction.ts | 4 +- .../rush-lib/src/cli/actions/InstallAction.ts | 7 +- .../rush-lib/src/cli/actions/PublishAction.ts | 22 +- .../rush-lib/src/cli/actions/UpdateAction.ts | 9 +- .../rush-lib/src/cli/actions/VersionAction.ts | 12 +- .../cli/scriptActions/GlobalScriptAction.ts | 2 +- .../cli/scriptActions/PhasedScriptAction.ts | 6 +- .../cli/test/RushCommandLineParser.test.ts | 5 +- .../src/cli/test/RushXCommandLine.test.ts | 4 +- .../rush-mock-flush-telemetry-plugin/index.ts | 2 +- libraries/rush-lib/src/index.ts | 1 + libraries/rush-lib/src/logic/Autoinstaller.ts | 6 +- libraries/rush-lib/src/logic/ChangeManager.ts | 4 +- .../rush-lib/src/logic/ChangelogGenerator.ts | 4 +- .../rush-lib/src/logic/EventHooksManager.ts | 2 +- .../rush-lib/src/logic/PackageJsonUpdater.ts | 31 +- .../rush-lib/src/logic/ProjectWatcher.ts | 2 +- .../rush-lib/src/logic/PublishUtilities.ts | 18 +- libraries/rush-lib/src/logic/PurgeManager.ts | 8 +- libraries/rush-lib/src/logic/RepoStateFile.ts | 25 +- libraries/rush-lib/src/logic/SetupChecks.ts | 4 +- libraries/rush-lib/src/logic/Telemetry.ts | 2 +- .../rush-lib/src/logic/TempProjectHelper.ts | 29 +- libraries/rush-lib/src/logic/UnlinkManager.ts | 2 +- .../src/logic/base/BaseInstallManager.ts | 168 ++++---- .../src/logic/base/BaseInstallManagerTypes.ts | 7 +- .../src/logic/base/BaseLinkManager.ts | 8 +- .../src/logic/base/BaseShrinkwrapFile.ts | 5 +- .../FileSystemBuildCacheProvider.ts | 2 +- .../deploy/DeployScenarioConfiguration.ts | 2 +- .../logic/installManager/InstallHelpers.ts | 14 +- .../installManager/RushInstallManager.ts | 81 ++-- .../installManager/WorkspaceInstallManager.ts | 65 ++- .../installManager/doBasicInstallAsync.ts | 3 +- .../rush-lib/src/logic/npm/NpmLinkManager.ts | 12 +- .../src/logic/npm/NpmShrinkwrapFile.ts | 2 + .../logic/operations/ShellOperationRunner.ts | 2 +- .../src/logic/pnpm/PnpmLinkManager.ts | 35 +- .../logic/pnpm/PnpmProjectShrinkwrapFile.ts | 8 +- .../src/logic/pnpm/PnpmShrinkwrapFile.ts | 20 +- .../src/logic/pnpm/PnpmfileConfiguration.ts | 16 +- .../pnpm/SubspacePnpmfileConfiguration.ts | 52 +-- .../pnpm/test/PnpmShrinkwrapFile.test.ts | 63 ++- .../src/logic/policy/PolicyValidator.ts | 7 +- .../src/logic/policy/ShrinkwrapFilePolicy.ts | 9 +- .../logic/selectors/SubspaceSelectorParser.ts | 16 +- .../src/logic/setup/SetupPackageRegistry.ts | 8 +- .../src/logic/test/BaseInstallManager.test.ts | 11 +- .../src/logic/test/InstallHelpers.test.ts | 6 +- .../logic/test/ProjectChangeAnalyzer.test.ts | 4 +- .../src/logic/test/PublishUtilities.test.ts | 83 ++-- .../src/logic/test/ShrinkwrapFile.test.ts | 5 +- .../versionMismatch/VersionMismatchFinder.ts | 13 +- .../src/logic/yarn/YarnShrinkwrapFile.ts | 2 + libraries/rush-lib/src/utilities/Utilities.ts | 4 +- 72 files changed, 1041 insertions(+), 840 deletions(-) create mode 100644 libraries/rush-lib/src/api/Subspace.ts diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index bd8a7745fbf..f97a513c3f9 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1033,10 +1033,10 @@ export class ProjectChangeAnalyzer { export class RepoStateFile { readonly filePath: string; get isValid(): boolean; - static loadFromFile(jsonFilename: string, subspaceName: string | undefined): RepoStateFile; + static loadFromFile(jsonFilename: string): RepoStateFile; get pnpmShrinkwrapHash(): string | undefined; get preferredVersionsHash(): string | undefined; - refreshState(rushConfiguration: RushConfiguration): boolean; + refreshState(rushConfiguration: RushConfiguration, commonVersions: CommonVersionsConfiguration | undefined): boolean; } // @public @@ -1069,6 +1069,8 @@ export class RushConfiguration { readonly customTipsConfiguration: CustomTipsConfiguration; // @beta readonly customTipsConfigurationFilePath: string; + // @beta (undocumented) + get defaultSubspace(): Subspace; readonly ensureConsistentVersions: boolean; // @beta readonly eventHooks: EventHooks; @@ -1076,32 +1078,32 @@ export class RushConfiguration { readonly experimentsConfiguration: ExperimentsConfiguration; findProjectByShorthandName(shorthandProjectName: string): RushConfigurationProject | undefined; findProjectByTempName(tempProjectName: string): RushConfigurationProject | undefined; - getCommittedShrinkwrapFilename(subspaceName?: string | undefined): string; - getCommonRushConfigFolder(subspaceName?: string | undefined): string; - getCommonTempFolder(subspaceName?: string | undefined): string; - getCommonVersions(subspaceName?: string | undefined): CommonVersionsConfiguration; - getCommonVersionsFilePath(subspaceName?: string | undefined): string; + // @deprecated (undocumented) + getCommittedShrinkwrapFilename(subspace?: Subspace): string; + // @deprecated (undocumented) + getCommonVersions(subspace?: Subspace): CommonVersionsConfiguration; + // @deprecated (undocumented) + getCommonVersionsFilePath(subspace?: Subspace): string; getImplicitlyPreferredVersions(variant?: string | undefined): Map; - getPnpmfilePath(subspaceName?: string | undefined): string; + // @deprecated (undocumented) + getPnpmfilePath(subspace?: Subspace): string; getProjectByName(projectName: string): RushConfigurationProject | undefined; // @beta (undocumented) getProjectLookupForRoot(rootPath: string): LookupByPath; - getProjectsSubspaceSet(projects: Set): string[]; - getProjectSubspace(project: RushConfigurationProject): string | undefined; - getRepoState(subspaceName: string | undefined): RepoStateFile; - getRepoStateFilePath(subspaceName?: string | undefined): string; - getSubspaceProjects(subspaceName: string): RushConfigurationProject[]; // @beta - getTempShrinkwrapFilename(subspaceName?: string | undefined): string; - getTempShrinkwrapPreinstallFilename(subspaceName?: string | undefined): string; + getProjectsSubspaceSet(projects: ReadonlySet): ReadonlySet; + // @deprecated (undocumented) + getRepoState(subspace?: Subspace): RepoStateFile; + // @deprecated (undocumented) + getRepoStateFilePath(subspace?: Subspace): string; + // @beta (undocumented) + getSubspace(subspaceName: string): Subspace; readonly gitAllowedEmailRegExps: string[]; readonly gitChangefilesCommitMessage: string | undefined; readonly gitChangeLogUpdateCommitMessage: string | undefined; readonly gitSampleEmail: string; readonly gitTagSeparator: string | undefined; readonly gitVersionBumpCommitMessage: string | undefined; - // @beta - get hasSubspaces(): boolean; readonly hotfixChangeEnabled: boolean; static loadFromConfigurationFile(rushJsonFilename: string): RushConfiguration; // (undocumented) @@ -1122,7 +1124,7 @@ export class RushConfiguration { // (undocumented) get projects(): RushConfigurationProject[]; // @beta (undocumented) - get projectsByName(): Map; + get projectsByName(): ReadonlyMap; // @beta get projectsByTag(): ReadonlyMap>; readonly repositoryDefaultBranch: string; @@ -1143,20 +1145,19 @@ export class RushConfiguration { readonly shrinkwrapFilename: string; get shrinkwrapFilePhrase(): string; // @beta - get subspaceNames(): Iterable; + get subspaces(): readonly Subspace[]; // @beta - readonly subspacesConfiguration?: SubspacesConfiguration; - // @beta - subspaceShrinkwrapFilenames(subspaceName: string): string; + readonly subspacesConfiguration: SubspacesConfiguration | undefined; + readonly subspacesFeatureEnabled: boolean; readonly suppressNodeLtsWarning: boolean; // @beta readonly telemetryEnabled: boolean; static tryFindRushJsonLocation(options?: ITryFindRushJsonLocationOptions): string | undefined; tryGetProjectForPath(currentFolderPath: string): RushConfigurationProject | undefined; + // @beta (undocumented) + tryGetSubspace(subspaceName: string): Subspace | undefined; // (undocumented) static tryLoadFromDefaultLocation(options?: ITryFindRushJsonLocationOptions): RushConfiguration | undefined; - // (undocumented) - validateSubspaceName(subspaceName: string): void; // @beta (undocumented) readonly versionPolicyConfiguration: VersionPolicyConfiguration; // @beta (undocumented) @@ -1171,6 +1172,8 @@ export class RushConfigurationProject { // // @internal constructor(options: IRushConfigurationProjectOptions); + // @beta + readonly configuredSubspaceName: string | undefined; get consumingProjects(): ReadonlySet; // @deprecated get cyclicDependencyProjects(): Set; @@ -1195,8 +1198,7 @@ export class RushConfigurationProject { readonly rushConfiguration: RushConfiguration; get shouldPublish(): boolean; readonly skipRushCheck: boolean; - // @beta - readonly subspaceName: string | undefined; + readonly subspace: Subspace; // @beta readonly tags: ReadonlySet; readonly tempProjectName: string; @@ -1331,15 +1333,50 @@ export class RushUserConfiguration { static initializeAsync(): Promise; } +// @public +export class Subspace { + // Warning: (ae-forgotten-export) The symbol "ISubspaceOptions" needs to be exported by the entry point index.d.ts + constructor(options: ISubspaceOptions); + // @internal (undocumented) + _addProject(project: RushConfigurationProject): void; + // @beta + contains(project: RushConfigurationProject): boolean; + // @beta + getCommittedShrinkwrapFilename(): string; + // @beta + getCommonVersions(): CommonVersionsConfiguration; + // @beta + getCommonVersionsFilePath(): string; + // @beta + getPnpmfilePath(): string; + // @beta + getProjects(): RushConfigurationProject[]; + // @beta + getRepoState(): RepoStateFile; + // @beta + getRepoStateFilePath(): string; + // @beta + getSubspaceConfigFolder(): string; + // @beta + getSubspaceTempFolder(): string; + // @beta + getTempShrinkwrapFilename(): string; + // @beta + getTempShrinkwrapPreinstallFilename(subspaceName?: string | undefined): string; + // (undocumented) + readonly subspaceName: string; +} + // @beta export class SubspacesConfiguration { // (undocumented) static belongsInSubspace(rushProject: RushConfigurationProject, subspaceName: string): boolean; readonly enabled: boolean; - isValidSubspaceName(subspaceName: string): boolean; + static explainIfInvalidSubspaceName(subspaceName: string): string | undefined; + static requireValidSubspaceName(subspaceName: string): void; readonly splitWorkspaceCompatibility: boolean; readonly subspaceJsonFilePath: string; - readonly subspaceNames: Set; + readonly subspaceNames: ReadonlySet; // (undocumented) static tryLoadFromConfigurationFile(subspaceJsonFilePath: string): SubspacesConfiguration | undefined; // (undocumented) @@ -1375,7 +1412,7 @@ export class VersionPolicyConfiguration { bump(versionPolicyName?: string, bumpType?: BumpType, identifier?: string, shouldCommit?: boolean): void; getVersionPolicy(policyName: string): VersionPolicy; update(versionPolicyName: string, newVersion: string, shouldCommit?: boolean): void; - validate(projectsByName: Map): void; + validate(projectsByName: ReadonlyMap): void; readonly versionPolicies: Map; } diff --git a/libraries/rush-lib/src/api/ApprovedPackagesPolicy.ts b/libraries/rush-lib/src/api/ApprovedPackagesPolicy.ts index d6fa8c65cbd..beb61a32137 100644 --- a/libraries/rush-lib/src/api/ApprovedPackagesPolicy.ts +++ b/libraries/rush-lib/src/api/ApprovedPackagesPolicy.ts @@ -84,7 +84,7 @@ export class ApprovedPackagesPolicy { // Load browser-approved-packages.json const browserApprovedPackagesPath: string = path.join( - rushConfiguration.getCommonRushConfigFolder(), + rushConfiguration.commonRushConfigFolder, RushConstants.browserApprovedPackagesFilename ); this.browserApprovedPackages = new ApprovedPackagesConfiguration(browserApprovedPackagesPath); @@ -92,7 +92,7 @@ export class ApprovedPackagesPolicy { // Load nonbrowser-approved-packages.json const nonbrowserApprovedPackagesPath: string = path.join( - rushConfiguration.getCommonRushConfigFolder(), + rushConfiguration.commonRushConfigFolder, RushConstants.nonbrowserApprovedPackagesFilename ); this.nonbrowserApprovedPackages = new ApprovedPackagesConfiguration(nonbrowserApprovedPackagesPath); diff --git a/libraries/rush-lib/src/api/BuildCacheConfiguration.ts b/libraries/rush-lib/src/api/BuildCacheConfiguration.ts index e8cfc242cd6..c092e2ba4ce 100644 --- a/libraries/rush-lib/src/api/BuildCacheConfiguration.ts +++ b/libraries/rush-lib/src/api/BuildCacheConfiguration.ts @@ -163,7 +163,7 @@ export class BuildCacheConfiguration { * Gets the absolute path to the build-cache.json file in the specified rush workspace. */ public static getBuildCacheConfigFilePath(rushConfiguration: RushConfiguration): string { - return path.resolve(rushConfiguration.getCommonRushConfigFolder(), RushConstants.buildCacheFilename); + return path.resolve(rushConfiguration.commonRushConfigFolder, RushConstants.buildCacheFilename); } private static async _loadAsync( diff --git a/libraries/rush-lib/src/api/CobuildConfiguration.ts b/libraries/rush-lib/src/api/CobuildConfiguration.ts index 23b442d8434..bb8b6c01da7 100644 --- a/libraries/rush-lib/src/api/CobuildConfiguration.ts +++ b/libraries/rush-lib/src/api/CobuildConfiguration.ts @@ -105,7 +105,7 @@ export class CobuildConfiguration { } public static getCobuildConfigFilePath(rushConfiguration: RushConfiguration): string { - return `${rushConfiguration.getCommonRushConfigFolder()}/${RushConstants.cobuildFilename}`; + return `${rushConfiguration.commonRushConfigFolder}/${RushConstants.cobuildFilename}`; } private static async _loadAsync( diff --git a/libraries/rush-lib/src/api/LastInstallFlag.ts b/libraries/rush-lib/src/api/LastInstallFlag.ts index 1044a93b991..a8ebb66c042 100644 --- a/libraries/rush-lib/src/api/LastInstallFlag.ts +++ b/libraries/rush-lib/src/api/LastInstallFlag.ts @@ -8,6 +8,7 @@ import { FileSystem, JsonFile, type JsonObject, Path } from '@rushstack/node-cor import type { PackageManagerName } from './packageManager/PackageManager'; import type { RushConfiguration } from './RushConfiguration'; import { objectsAreDeepEqual } from '../utilities/objectUtilities'; +import type { Subspace } from './Subspace'; export const LAST_INSTALL_FLAG_FILE_NAME: string = 'last-install.flag'; @@ -162,7 +163,7 @@ export class LastInstallFlagFactory { */ public static getCommonTempFlag( rushConfiguration: RushConfiguration, - subspaceName: string | undefined, + subspace: Subspace, extraState: Record = {} ): LastInstallFlag { const currentState: JsonObject = { @@ -180,6 +181,6 @@ export class LastInstallFlagFactory { } } - return new LastInstallFlag(rushConfiguration.getCommonTempFolder(subspaceName), currentState); + return new LastInstallFlag(subspace.getSubspaceTempFolder(), currentState); } } diff --git a/libraries/rush-lib/src/api/LastLinkFlag.ts b/libraries/rush-lib/src/api/LastLinkFlag.ts index 37cd9e7ac73..9cb749b5bd2 100644 --- a/libraries/rush-lib/src/api/LastLinkFlag.ts +++ b/libraries/rush-lib/src/api/LastLinkFlag.ts @@ -3,7 +3,7 @@ import { LastInstallFlag } from './LastInstallFlag'; import { type JsonObject, JsonFile, InternalError } from '@rushstack/node-core-library'; -import type { RushConfiguration } from './RushConfiguration'; +import type { Subspace } from './Subspace'; export const LAST_LINK_FLAG_FILE_NAME: string = 'last-link.flag'; @@ -57,10 +57,7 @@ export class LastLinkFlagFactory { * * @internal */ - public static getCommonTempFlag( - rushConfiguration: RushConfiguration, - subspaceName?: string | undefined - ): LastLinkFlag { - return new LastLinkFlag(rushConfiguration.getCommonTempFolder(subspaceName), {}); + public static getCommonTempFlag(subspace: Subspace): LastLinkFlag { + return new LastLinkFlag(subspace.getSubspaceTempFolder(), {}); } } diff --git a/libraries/rush-lib/src/api/RushConfiguration.ts b/libraries/rush-lib/src/api/RushConfiguration.ts index 59de6a47ff6..49f84590084 100644 --- a/libraries/rush-lib/src/api/RushConfiguration.ts +++ b/libraries/rush-lib/src/api/RushConfiguration.ts @@ -12,7 +12,8 @@ import { Path, FileSystem, type PackageNameParser, - type FileSystemStats + type FileSystemStats, + InternalError } from '@rushstack/node-core-library'; import { trueCasePathSync } from 'true-case-path'; @@ -23,7 +24,7 @@ import { ApprovedPackagesPolicy } from './ApprovedPackagesPolicy'; import { EventHooks } from './EventHooks'; import { VersionPolicyConfiguration } from './VersionPolicyConfiguration'; import { EnvironmentConfiguration } from './EnvironmentConfiguration'; -import { CommonVersionsConfiguration } from './CommonVersionsConfiguration'; +import type { CommonVersionsConfiguration } from './CommonVersionsConfiguration'; import { Utilities } from '../utilities/Utilities'; import type { PackageManagerName, PackageManager } from './packageManager/PackageManager'; import { NpmPackageManager } from './packageManager/NpmPackageManager'; @@ -31,7 +32,7 @@ import { YarnPackageManager } from './packageManager/YarnPackageManager'; import { PnpmPackageManager } from './packageManager/PnpmPackageManager'; import { ExperimentsConfiguration } from './ExperimentsConfiguration'; import { PackageNameParsers } from './PackageNameParsers'; -import { RepoStateFile } from '../logic/RepoStateFile'; +import type { RepoStateFile } from '../logic/RepoStateFile'; import { LookupByPath } from '../logic/LookupByPath'; import { RushPluginsConfiguration } from './RushPluginsConfiguration'; import { type IPnpmOptionsJson, PnpmOptionsConfiguration } from '../logic/pnpm/PnpmOptionsConfiguration'; @@ -43,6 +44,7 @@ import type * as DependencyAnalyzerModuleType from '../logic/DependencyAnalyzer' import type { PackageManagerOptionsConfigurationBase } from '../logic/base/BasePackageManagerOptionsConfiguration'; import { CustomTipsConfiguration } from './CustomTipsConfiguration'; import { SubspacesConfiguration } from './SubspacesConfiguration'; +import { Subspace } from './Subspace'; const MINIMUM_SUPPORTED_RUSH_JSON_VERSION: string = '0.0.0'; const DEFAULT_BRANCH: string = 'main'; @@ -234,17 +236,11 @@ export class RushConfiguration { // Lazily loaded when the projectsByTag() getter is called. private _projectsByTag: ReadonlyMap> | undefined; - // Subspace projects - private _rushProjectsBySubspaceName: Map; - // variant -> common-versions configuration private _commonVersionsConfigurationsByVariant: Map | undefined; - // subspace -> common-versions configuration - private _commonVersionsConfigurationsBySubspace: Map | undefined; - - // subspaceName -> subspaceConfigFolder - private _subspaceConfigFolderBySubspace: Map; + // subspaceName -> subspace + private _subspacesByName: Map; /** * The name of the package manager being used to install dependencies @@ -353,7 +349,12 @@ export class RushConfiguration { * The object that specifies subspace configurations if they are provided in the rush workspace. * @beta */ - public readonly subspacesConfiguration?: SubspacesConfiguration; + public readonly subspacesConfiguration: SubspacesConfiguration | undefined; + + /** + * Returns true if subspaces.json is present with "enabled=true". + */ + public readonly subspacesFeatureEnabled: boolean; /** * The filename of the variant dependency data file. By default this is @@ -631,9 +632,9 @@ export class RushConfiguration { // Try getting a subspace configuration this.subspacesConfiguration = SubspacesConfiguration.tryLoadFromDefaultLocation(this); + this.subspacesFeatureEnabled = !!this.subspacesConfiguration?.enabled; - this._rushProjectsBySubspaceName = new Map(); - this._subspaceConfigFolderBySubspace = new Map(); + this._subspacesByName = new Map(); const experimentsConfigFile: string = path.join( this.commonRushConfigFolder, @@ -853,7 +854,37 @@ export class RushConfiguration { this._projects = []; this._projectsByName = new Map(); - // We sort the projects array in alphabetical order. This ensures that the packages + // Build the subspaces map + const subspaceNames: string[] = []; + let splitWorkspaceCompatibility: boolean = false; + if (this.subspacesConfiguration?.enabled) { + 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: Subspace = new Subspace({ + subspaceName, + rushConfiguration: this, + splitWorkspaceCompatibility + }); + this._subspacesByName.set(subspaceName, subspace); + } + const defaultSubspace: Subspace | undefined = 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: IRushConfigurationProjectJson[] = this.rushConfigurationJson.projects.slice(0); sortedProjectJsons.sort((a: IRushConfigurationProjectJson, b: IRushConfigurationProjectJson) => @@ -870,12 +901,31 @@ export class RushConfiguration { projectJson, usedTempNames ); + + let subspace: Subspace | undefined = undefined; + if (this.subspacesFeatureEnabled) { + if (projectJson.subspaceName) { + subspace = this._subspacesByName.get(projectJson.subspaceName); + if (subspace === undefined) { + throw new Error( + `The project "${projectJson.packageName}" in rush.json references` + + ` a nonexistent subspace "${projectJson.subspaceName}"` + ); + } + } + } + if (subspace === undefined) { + subspace = defaultSubspace; + } + const project: RushConfigurationProject = new RushConfigurationProject({ projectJson, rushConfiguration: this, tempProjectName, - allowedProjectTags + allowedProjectTags, + subspace }); + subspace._addProject(project); this._projects.push(project); if (this._projectsByName.has(project.packageName)) { @@ -885,22 +935,6 @@ export class RushConfiguration { ); } this._projectsByName.set(project.packageName, project); - if (this.subspacesConfiguration?.enabled) { - let subspaceName: string; - if (projectJson.subspaceName) { - subspaceName = projectJson.subspaceName; - } else { - // Default subspace - subspaceName = RushConstants.defaultSubspaceName; - } - const projectsForSubspace: RushConfigurationProject[] | undefined = - this._rushProjectsBySubspaceName.get(subspaceName); - if (projectsForSubspace) { - projectsForSubspace.push(project); - } else { - this._rushProjectsBySubspaceName.set(subspaceName, [project]); - } - } } for (const project of this._projects) { @@ -1156,134 +1190,6 @@ export class RushConfiguration { return path.join(this.commonFolder, 'config', 'rush-plugins'); } - /** - * The folder where Rush's additional config files are stored. This folder is always a - * subfolder called `config\rush` inside the common folder. (The `common\config` folder - * is reserved for configuration files used by other tools.) To avoid confusion or mistakes, - * Rush will report an error if this this folder contains any unrecognized files. - * - * Example: `C:\MyRepo\common\config\rush` - */ - public getCommonRushConfigFolder(subspaceName?: string | undefined): string { - if (subspaceName) { - if (this._subspaceConfigFolderBySubspace.has(subspaceName)) { - return this._subspaceConfigFolderBySubspace.get(subspaceName) as string; - } - this.validateSubspaceName(subspaceName); - - // 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 - let subspaceConfigFolder: string = `${this.commonFolder}/config/subspaces/${subspaceName}`; - if ( - !FileSystem.exists(subspaceConfigFolder) && - this.subspacesConfiguration?.splitWorkspaceCompatibility - ) { - // Look in the project folder - const rushProjectsInSubspace: RushConfigurationProject[] | undefined = - this._rushProjectsBySubspaceName.get(subspaceName); - if (!rushProjectsInSubspace || rushProjectsInSubspace.length > 1) { - throw new Error( - `The subspace contains more than one package and no configuration folder was found in common/config/subspaces/${subspaceName}` - ); - } - const rushProject: RushConfigurationProject = rushProjectsInSubspace[0]; - subspaceConfigFolder = `${rushProject.projectFolder}/subspace/${subspaceName}`; - if (!FileSystem.exists(subspaceConfigFolder)) { - throw new Error(`The configuration folder for the ${subspaceName} subspace was not found.`); - } - } - - this._subspaceConfigFolderBySubspace.set(subspaceName, subspaceConfigFolder); - - return subspaceConfigFolder; - } else { - return path.join(this.commonFolder, 'config', 'rush'); - } - } - - /** - * The folder where temporary files will be stored. This is always a subfolder called "temp" - * under the common folder. - * Example: `C:\MyRepo\common\temp` - */ - public getCommonTempFolder(subspaceName?: string | undefined): string { - const commonTempFolder: string = - EnvironmentConfiguration.rushTempFolderOverride || - path.join(this.commonFolder, RushConstants.rushTempFolderName); - if (subspaceName) { - this.validateSubspaceName(subspaceName); - - return `${commonTempFolder}/${subspaceName}`; - } else { - return commonTempFolder; - } - } - - /** - * 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\\pnpm-lock.yaml` - * @beta - */ - public getTempShrinkwrapFilename(subspaceName?: string | undefined): string { - if (subspaceName) { - const fullSubspacePath: string = `${this.getCommonTempFolder(subspaceName)}/${this.shrinkwrapFilename}`; - return fullSubspacePath; - } else { - return path.join(this.getCommonTempFolder(subspaceName), 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` - */ - public getTempShrinkwrapPreinstallFilename(subspaceName?: string | undefined): string { - /// From "C:\repo\common\temp\pnpm-lock.yaml" --> "C:\repo\common\temp\pnpm-lock-preinstall.yaml" - const parsedPath: path.ParsedPath = path.parse(this.getTempShrinkwrapFilename(subspaceName)); - return path.join(parsedPath.dir, parsedPath.name + '-preinstall' + parsedPath.ext); - } - - public validateSubspaceName(subspaceName: string): void { - // Validate that subspace configuration has been initialized - if (!this._projects) { - this._initializeAndValidateLocalProjects(); - } - if (!this.subspacesConfiguration) { - throw new Error(`Subspaces is not enabled!`); - } - if (!this.subspacesConfiguration.isValidSubspaceName(subspaceName)) { - throw new Error(`Received an invalid subspace name: ${subspaceName}`); - } - } - - /** - * The filename (without any path) of the shrinkwrap file used for individual subspaces, used by the package manager. - * @remarks - * This property merely reports the filename; The file itself may not actually exist. - * Example: From "pnpm-lock.yaml" to "subspace-pnpm-lock.yaml" - * @beta - */ - public subspaceShrinkwrapFilenames(subspaceName: string): string { - const lastSlashIndex: number = Math.max( - this.shrinkwrapFilename.lastIndexOf('/'), - this.shrinkwrapFilename.lastIndexOf('\\') - ); - return `${this.shrinkwrapFilename.substring( - 0, - lastSlashIndex - )}/${subspaceName}-${this.shrinkwrapFilename.substring(lastSlashIndex + 1)}`; - } - /** * 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 @@ -1326,68 +1232,76 @@ export class RushConfiguration { } /** - * A list of all the available subspaces in this workspace. * @beta */ - public get subspaceNames(): Iterable { + public get defaultSubspace(): Subspace { + // TODO: Enable the default subspace to be obtained without initializing the full set of all projects if (!this._projects) { this._initializeAndValidateLocalProjects(); } - return this._rushProjectsBySubspaceName.keys(); + const defaultSubspace: Subspace | undefined = this.tryGetSubspace(RushConstants.defaultSubspaceName); + if (!defaultSubspace) { + throw new InternalError('Default subspace was not created'); + } + return defaultSubspace; } /** - * Returns a list of rush projects that belong to a subspace + * A list of all the available subspaces in this workspace. + * @beta */ - public getSubspaceProjects(subspaceName: string): RushConfigurationProject[] { + public get subspaces(): readonly Subspace[] { if (!this._projects) { this._initializeAndValidateLocalProjects(); } - return this._rushProjectsBySubspaceName.get(subspaceName) || []; + return Array.from(this._subspacesByName.values()); } /** - * Returns the subspace name that a project belongs to. Returns undefined if subspaces is not enabled. + * @beta */ - public getProjectSubspace(project: RushConfigurationProject): string | undefined { - if (!this.subspacesConfiguration?.enabled) { - return undefined; - } + public tryGetSubspace(subspaceName: string): Subspace | undefined { if (!this._projects) { this._initializeAndValidateLocalProjects(); } - if (project.subspaceName) { - return project.subspaceName; - } else { - return RushConstants.defaultSubspaceName; + const subspace: Subspace | undefined = 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); } + return subspace; } /** - * Returns a list of unique subspace names that the given projects belong to + * @beta */ - public getProjectsSubspaceSet(projects: Set): string[] { - if (!this.subspacesConfiguration?.enabled) { - return []; + public getSubspace(subspaceName: string): Subspace { + const subspace: Subspace | undefined = 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 + */ + public getProjectsSubspaceSet(projects: ReadonlySet): ReadonlySet { if (!this._projects) { this._initializeAndValidateLocalProjects(); } - const subspaceSet: Set = new Set(); + const subspaceSet: Set = new Set(); for (const project of projects) { - if (project.subspaceName) { - subspaceSet.add(project.subspaceName); - } else { - subspaceSet.add(RushConstants.defaultSubspaceName); - } + subspaceSet.add(project.subspace); } - return Array.from(subspaceSet); + return subspaceSet; } /** * @beta */ - public get projectsByName(): Map { + public get projectsByName(): ReadonlyMap { if (!this._projectsByName) { this._initializeAndValidateLocalProjects(); } @@ -1427,7 +1341,7 @@ export class RushConfiguration { * for a given active variant. */ public get commonVersions(): CommonVersionsConfiguration { - return this.getCommonVersions(); + return this.defaultSubspace.getCommonVersions(); } /** @@ -1449,56 +1363,17 @@ export class RushConfiguration { } /** - * Gets the path to the common-versions.json config file for a specific variant. - * @param variant - The name of the current variant in use by the active command. - */ - public getCommonVersionsFilePath(subspaceName?: string | undefined): string { - const commonVersionsFilename: string = path.join( - this.getCommonRushConfigFolder(subspaceName), - RushConstants.commonVersionsFilename - ); - return commonVersionsFilename; - } - - /** - * Returns `true` if the subspaces feature is enabled and at least one subspaces is defined - * in the `subspaces.json` config file. - * @beta + * @deprecated Use {@link Subspace.getCommonVersionsFilePath} instead */ - public get hasSubspaces(): boolean { - if (!this._projects) { - this._initializeAndValidateLocalProjects(); - } - return this._rushProjectsBySubspaceName.size > 0; + public getCommonVersionsFilePath(subspace?: Subspace): string { + return (subspace ?? this.defaultSubspace).getCommonVersionsFilePath(); } /** - * Gets the settings from the common-versions.json config file for a specific variant. - * @param variant - The name of the current variant in use by the active command. + * @deprecated Use {@link Subspace.getCommonVersions} instead */ - public getCommonVersions(subspaceName?: string | undefined): CommonVersionsConfiguration { - if (!this._commonVersionsConfigurationsByVariant) { - this._commonVersionsConfigurationsByVariant = new Map(); - } - if (!this._commonVersionsConfigurationsBySubspace) { - this._commonVersionsConfigurationsBySubspace = new Map(); - } - - // Use an empty string as the key when no variant provided. Anything else would possibly conflict - // with a variant created by the user - const subspaceKey: string = subspaceName || ''; - let commonVersionsConfiguration: CommonVersionsConfiguration | undefined = - this._commonVersionsConfigurationsBySubspace.get(subspaceKey); - - if (!commonVersionsConfiguration) { - const commonVersionsFilename: string = this.getCommonVersionsFilePath(subspaceName); - commonVersionsConfiguration = CommonVersionsConfiguration.loadFromFile(commonVersionsFilename); - if (subspaceName) { - this._commonVersionsConfigurationsBySubspace.set(subspaceName, commonVersionsConfiguration); - } - } - - return commonVersionsConfiguration; + public getCommonVersions(subspace?: Subspace): CommonVersionsConfiguration { + return (subspace ?? this.defaultSubspace).getCommonVersions(); } /** @@ -1519,50 +1394,31 @@ export class RushConfiguration { } /** - * Gets the path to the repo-state.json file for a specific variant. - * @param variant - The name of the current variant in use by the active command. + * @deprecated Use {@link Subspace.getRepoStateFilePath} instead */ - public getRepoStateFilePath(subspaceName?: string | undefined): string { - const repoStateFilename: string = path.join( - this.getCommonRushConfigFolder(subspaceName), - RushConstants.repoStateFilename - ); - return repoStateFilename; + public getRepoStateFilePath(subspace?: Subspace): string { + return (subspace ?? this.defaultSubspace).getRepoStateFilePath(); } /** - * Gets the contents from the repo-state.json file for a specific variant. - * @param subspaceName - The name of the subspace in use by the active command. - * @param variant - The name of the current variant in use by the active command. + * @deprecated Use {@link Subspace.getRepoState} instead */ - public getRepoState(subspaceName: string | undefined): RepoStateFile { - const repoStateFilename: string = this.getRepoStateFilePath(subspaceName); - return RepoStateFile.loadFromFile(repoStateFilename, subspaceName); + public getRepoState(subspace?: Subspace): RepoStateFile { + return (subspace ?? this.defaultSubspace).getRepoState(); } /** - * Gets the committed shrinkwrap file name for a specific variant. - * @param variant - The name of the current variant in use by the active command. + * @deprecated Use {@link Subspace.getCommittedShrinkwrapFilename} instead */ - public getCommittedShrinkwrapFilename(subspaceName?: string | undefined): string { - const subspaceConfigFolderPath: string = this.getCommonRushConfigFolder(subspaceName); - - return path.join(subspaceConfigFolderPath, this.shrinkwrapFilename); + public getCommittedShrinkwrapFilename(subspace?: Subspace): string { + return (subspace ?? this.defaultSubspace).getCommittedShrinkwrapFilename(); } /** - * 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. + * @deprecated Use {@link Subspace.getRepoStateFilePath} instead */ - public getPnpmfilePath(subspaceName?: string | undefined): string { - const subspaceConfigFolderPath: string = this.getCommonRushConfigFolder(subspaceName); - - return path.join( - subspaceConfigFolderPath, - (this.packageManagerWrapper as PnpmPackageManager).pnpmfileFilename - ); + public getPnpmfilePath(subspace?: Subspace): string { + return (subspace ?? this.defaultSubspace).getPnpmfilePath(); } /** diff --git a/libraries/rush-lib/src/api/RushConfigurationProject.ts b/libraries/rush-lib/src/api/RushConfigurationProject.ts index 665dda6d453..4bebc4a22ca 100644 --- a/libraries/rush-lib/src/api/RushConfigurationProject.ts +++ b/libraries/rush-lib/src/api/RushConfigurationProject.ts @@ -12,6 +12,7 @@ import { RushConstants } from '../logic/RushConstants'; import { PackageNameParsers } from './PackageNameParsers'; import { DependencySpecifier, DependencySpecifierType } from '../logic/DependencySpecifier'; import { SaveCallbackPackageJsonEditor } from './SaveCallbackPackageJsonEditor'; +import type { Subspace } from './Subspace'; /** * This represents the JSON data object for a project entry in the rush.json configuration file. @@ -50,6 +51,11 @@ export interface IRushConfigurationProjectOptions { * If specified, validate project tags against this list. */ allowedProjectTags: Set | undefined; + + /** + * The containing subspace. + */ + subspace: Subspace; } /** @@ -106,6 +112,12 @@ export class RushConfigurationProject { */ public readonly rushConfiguration: RushConfiguration; + /** + * Returns the subspace name that a project belongs to. + * If subspaces is not enabled, returns the default subspace. + */ + public readonly subspace: Subspace; + /** * The review category name, or undefined if no category was assigned. * This name must be one of the valid choices listed in RushConfiguration.reviewCategories. @@ -186,17 +198,13 @@ export class RushConfigurationProject { public readonly tags: ReadonlySet; /** - * Returns the name of the subspace that this project belongs to, as assigned by the `"subspaceName"` - * property in `rush.json`. - * - * @remarks - * If the Rush subspaces feature is disabled, the value is still return. - * When the Rush subspaces feature is enabled, an undefined `subspaceName` specifies that - * the project belongs to the default subspace (whose name is `"default"`). + * Returns the subspace name specified in the `"subspaceName"` field in `rush.json`. + * Note that this field may be undefined, if the `default` subspace is being used, + * and this field may be ignored if the subspaces feature is disabled. * * @beta */ - public readonly subspaceName: string | undefined; + public readonly configuredSubspaceName: string | undefined; /** @internal */ public constructor(options: IRushConfigurationProjectOptions) { @@ -339,7 +347,8 @@ export class RushConfigurationProject { this.tags = new Set(projectJson.tags); } - this.subspaceName = projectJson.subspaceName; + this.configuredSubspaceName = projectJson.subspaceName; + this.subspace = options.subspace; } /** diff --git a/libraries/rush-lib/src/api/Subspace.ts b/libraries/rush-lib/src/api/Subspace.ts new file mode 100644 index 00000000000..f012e0cde06 --- /dev/null +++ b/libraries/rush-lib/src/api/Subspace.ts @@ -0,0 +1,271 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as path from 'path'; + +import { FileSystem } from '@rushstack/node-core-library'; +import type { RushConfiguration } from './RushConfiguration'; +import type { RushConfigurationProject } from './RushConfigurationProject'; +import { EnvironmentConfiguration } from './EnvironmentConfiguration'; +import { RushConstants } from '../logic/RushConstants'; +import { CommonVersionsConfiguration } from './CommonVersionsConfiguration'; +import { RepoStateFile } from '../logic/RepoStateFile'; +import type { PnpmPackageManager } from './packageManager/PnpmPackageManager'; + +/** + * The allowed naming convention for subspace names. + * Allows for names to be formed of letters, numbers, and hyphens (-) + */ +export const SUBSPACE_NAME_REGEXP: RegExp = /^[a-z0-9]*([-+_a-z0-9]+)*$/; + +/** + * @internal + */ +export interface ISubspaceOptions { + subspaceName: string; + rushConfiguration: RushConfiguration; + splitWorkspaceCompatibility: boolean; +} + +interface ISubspaceDetail { + subspaceConfigFolder: string; + subspaceTempFolder: string; + tempShrinkwrapFilename: string; + tempShrinkwrapPreinstallFilename: string; +} + +/** + * This represents the subspace configurations for a repository, based on the "subspaces.json" + * configuration file. + * @public + */ +export class Subspace { + public readonly subspaceName: string; + private readonly _rushConfiguration: RushConfiguration; + private readonly _projects: RushConfigurationProject[] = []; + private readonly _splitWorkspaceCompatibility: boolean; + private _commonVersionsConfiguration: CommonVersionsConfiguration | undefined = undefined; + + private _detail: ISubspaceDetail | undefined; + + public constructor(options: ISubspaceOptions) { + this.subspaceName = options.subspaceName; + this._rushConfiguration = options.rushConfiguration; + this._splitWorkspaceCompatibility = options.splitWorkspaceCompatibility; + } + + /** + * Returns the list of projects belonging to this subspace. + * @beta + */ + public getProjects(): RushConfigurationProject[] { + return this._projects; + } + + private _ensureDetail(): ISubspaceDetail { + if (!this._detail) { + const rushConfiguration: RushConfiguration = this._rushConfiguration; + let subspaceConfigFolder: string; + + if (rushConfiguration.subspacesFeatureEnabled) { + // 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: string = `${rushConfiguration.commonFolder}/config/subspaces/${this.subspaceName}`; + + subspaceConfigFolder = standardSubspaceConfigFolder; + + if (this._splitWorkspaceCompatibility && this.subspaceName.startsWith('split-')) { + if (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: RushConfigurationProject = this._projects[0]; + subspaceConfigFolder = project.projectFolder; + } + + if (!FileSystem.exists(subspaceConfigFolder)) { + throw new Error( + `The configuration folder for the "${this.subspaceName}" subspace does not exist: ` + + subspaceConfigFolder + ); + } + } else { + // Example: C:\MyRepo\common\config\rush + subspaceConfigFolder = rushConfiguration.commonRushConfigFolder; + } + + // Example: C:\MyRepo\common\temp + const commonTempFolder: string = + EnvironmentConfiguration.rushTempFolderOverride || rushConfiguration.commonTempFolder; + + let subspaceTempFolder: string; + if (rushConfiguration.subspacesFeatureEnabled) { + // Example: C:\MyRepo\common\temp\my-subspace + subspaceTempFolder = path.join(commonTempFolder, RushConstants.rushTempFolderName, this.subspaceName); + } else { + // Example: C:\MyRepo\common\temp + subspaceTempFolder = commonTempFolder; + } + + // Example: C:\MyRepo\common\temp\my-subspace\pnpm-lock.yaml + const tempShrinkwrapFilename: string = subspaceTempFolder + `/${rushConfiguration.shrinkwrapFilename}`; + + /// From "C:\MyRepo\common\temp\pnpm-lock.yaml" --> "C:\MyRepo\common\temp\pnpm-lock-preinstall.yaml" + const parsedPath: path.ParsedPath = path.parse(tempShrinkwrapFilename); + const tempShrinkwrapPreinstallFilename: string = path.join( + parsedPath.dir, + parsedPath.name + '-preinstall' + parsedPath.ext + ); + + this._detail = { + subspaceConfigFolder, + subspaceTempFolder, + tempShrinkwrapFilename, + tempShrinkwrapPreinstallFilename + }; + } + return this._detail; + } + + /** + * Returns the full path of the folder containing this subspace's configuration files such as `pnpm-lock.yaml`. + * + * Example: `common/config/subspaces/my-subspace` + * @beta + */ + public getSubspaceConfigFolder(): string { + return this._ensureDetail().subspaceConfigFolder; + } + + /** + * The folder where the subspace's node_modules and other temporary files will be stored. + * + * Example: `common/temp/subspaces/my-subspace` + * @beta + */ + public getSubspaceTempFolder(): string { + return this._ensureDetail().subspaceTempFolder; + } + + /** + * 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\\pnpm-lock.yaml` + * @beta + */ + public getTempShrinkwrapFilename(): string { + return this._ensureDetail().tempShrinkwrapFilename; + } + + /** + * 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 + */ + public getTempShrinkwrapPreinstallFilename(subspaceName?: string | undefined): string { + return this._ensureDetail().tempShrinkwrapPreinstallFilename; + } + + /** + * Gets the path to the common-versions.json config file for this subspace. + * + * Example: `C:\MyRepo\common\subspaces\my-subspace\common-versions.json` + * @beta + */ + public getCommonVersionsFilePath(): string { + return this._ensureDetail().subspaceConfigFolder + '/' + RushConstants.commonVersionsFilename; + } + + /** + * Gets the settings from the common-versions.json config file for a specific variant. + * @param variant - The name of the current variant in use by the active command. + * @beta + */ + public getCommonVersions(): CommonVersionsConfiguration { + const commonVersionsFilename: string = this.getCommonVersionsFilePath(); + if (!this._commonVersionsConfiguration) { + this._commonVersionsConfiguration = CommonVersionsConfiguration.loadFromFile(commonVersionsFilename); + } + return this._commonVersionsConfiguration; + } + + /** + * Gets the path to the repo-state.json file for a specific variant. + * @param variant - The name of the current variant in use by the active command. + * @beta + */ + public getRepoStateFilePath(): string { + return this._ensureDetail().subspaceConfigFolder + '/' + RushConstants.repoStateFilename; + } + + /** + * Gets the contents from the repo-state.json file for a specific variant. + * @param subspaceName - The name of the subspace in use by the active command. + * @param variant - The name of the current variant in use by the active command. + * @beta + */ + public getRepoState(): RepoStateFile { + const repoStateFilename: string = this.getRepoStateFilePath(); + return RepoStateFile.loadFromFile(repoStateFilename); + } + + /** + * 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 + */ + public getCommittedShrinkwrapFilename(): string { + const subspaceConfigFolderPath: string = this.getSubspaceConfigFolder(); + return path.join(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 + */ + public getPnpmfilePath(): string { + const subspaceConfigFolderPath: string = this.getSubspaceConfigFolder(); + + return path.join( + subspaceConfigFolderPath, + (this._rushConfiguration.packageManagerWrapper as PnpmPackageManager).pnpmfileFilename + ); + } + + /** + * Returns true if the specified project belongs to this subspace. + * @beta + */ + public contains(project: RushConfigurationProject): boolean { + return project.subspace.subspaceName === this.subspaceName; + } + + /** @internal */ + public _addProject(project: RushConfigurationProject): void { + this._projects.push(project); + } +} diff --git a/libraries/rush-lib/src/api/SubspacesConfiguration.ts b/libraries/rush-lib/src/api/SubspacesConfiguration.ts index 9779573fe4b..969a8f2e1c3 100644 --- a/libraries/rush-lib/src/api/SubspacesConfiguration.ts +++ b/libraries/rush-lib/src/api/SubspacesConfiguration.ts @@ -10,9 +10,11 @@ import { RushConstants } from '../logic/RushConstants'; /** * The allowed naming convention for subspace names. - * Allows for names to be formed of letters, numbers, and hyphens (-) + * Allows for names to be formed of identifiers separated by hyphens (-) + * + * Example: "my-subspace" */ -export const SUBSPACE_NAME_REGEXP: RegExp = /^[a-z0-9]*([-+_a-z0-9]+)*$/; +export const SUBSPACE_NAME_REGEXP: RegExp = /^[a-z][a-z0-9]*([-][a-z0-9]+)*$/; /** * This represents the JSON data structure for the "subspaces.json" configuration file. @@ -50,31 +52,53 @@ export class SubspacesConfiguration { /** * A set of the available subspaces */ - public readonly subspaceNames: Set; + public readonly subspaceNames: ReadonlySet; private constructor(configuration: Readonly, subspaceJsonFilePath: string) { this.subspaceJsonFilePath = subspaceJsonFilePath; this.enabled = configuration.enabled; this.splitWorkspaceCompatibility = !!configuration.splitWorkspaceCompatibility; - this.subspaceNames = new Set(); + const subspaceNames: Set = new Set(); for (const subspaceName of configuration.subspaceNames) { - if (SUBSPACE_NAME_REGEXP.test(subspaceName)) { - this.subspaceNames.add(subspaceName); - } else { - throw new Error( - `Invalid subspace name: ${subspaceName}. Subspace names must only consist of lowercase letters, numbers, hyphens (-), plus-signs (+), or underscores (_).` - ); - } + SubspacesConfiguration.requireValidSubspaceName(subspaceName); + subspaceNames.add(subspaceName); + } + // Add the default subspace if it wasn't explicitly declared + subspaceNames.add(RushConstants.defaultSubspaceName); + this.subspaceNames = subspaceNames; + } + + /** + * Checks whether the provided string could be used as a subspace name. + * Returns `undefined` if the name is valid; otherwise returns an error message. + * @remarks + * This is a syntax check only; it does not test whether the subspace is actually defined in the Rush configuration. + */ + public static explainIfInvalidSubspaceName(subspaceName: string): string | undefined { + if (subspaceName.length === 0) { + return `The subspace name cannot be empty`; + } + if (SUBSPACE_NAME_REGEXP.test(subspaceName)) { + return ( + `Invalid name "${subspaceName}". ` + + `Subspace names must consist of lowercase letters and numbers separated by hyphens.` + ); } - // Add the default subspace - this.subspaceNames.add(RushConstants.defaultSubspaceName); + + return undefined; // name is okay } /** - * Checks if the given subspace name is a registered subspace in the subspaces.json file. + * Checks whether the provided string could be used as a subspace name. + * If not, an exception is thrown. + * @remarks + * This is a syntax check only; it does not test whether the subspace is actually defined in the Rush configuration. */ - public isValidSubspaceName(subspaceName: string): boolean { - return this.subspaceNames.has(subspaceName); + public static requireValidSubspaceName(subspaceName: string): void { + const message: string | undefined = SubspacesConfiguration.explainIfInvalidSubspaceName(subspaceName); + if (message) { + throw new Error(message); + } } public static tryLoadFromConfigurationFile( @@ -96,17 +120,15 @@ export class SubspacesConfiguration { public static tryLoadFromDefaultLocation( rushConfiguration: RushConfiguration ): SubspacesConfiguration | undefined { - const commonRushConfigFolder: string = rushConfiguration.getCommonRushConfigFolder(); - if (commonRushConfigFolder) { - const subspaceJsonLocation: string = `${commonRushConfigFolder}/${RushConstants.subspacesConfigFilename}`; - return SubspacesConfiguration.tryLoadFromConfigurationFile(subspaceJsonLocation); - } + const commonRushConfigFolder: string = rushConfiguration.commonRushConfigFolder; + const subspaceJsonLocation: string = `${commonRushConfigFolder}/${RushConstants.subspacesConfigFilename}`; + return SubspacesConfiguration.tryLoadFromConfigurationFile(subspaceJsonLocation); } public static belongsInSubspace(rushProject: RushConfigurationProject, subspaceName: string): boolean { return ( - rushProject.subspaceName === subspaceName || - (!rushProject.subspaceName && subspaceName === RushConstants.defaultSubspaceName) + rushProject.configuredSubspaceName === subspaceName || + (!rushProject.configuredSubspaceName && subspaceName === RushConstants.defaultSubspaceName) ); } } diff --git a/libraries/rush-lib/src/api/VersionPolicyConfiguration.ts b/libraries/rush-lib/src/api/VersionPolicyConfiguration.ts index 862b91041a7..01382f02852 100644 --- a/libraries/rush-lib/src/api/VersionPolicyConfiguration.ts +++ b/libraries/rush-lib/src/api/VersionPolicyConfiguration.ts @@ -68,7 +68,7 @@ export class VersionPolicyConfiguration { /** * Validate the version policy configuration against the rush config */ - public validate(projectsByName: Map): void { + public validate(projectsByName: ReadonlyMap): void { if (!this.versionPolicies) { return; } diff --git a/libraries/rush-lib/src/api/test/RushConfiguration.test.ts b/libraries/rush-lib/src/api/test/RushConfiguration.test.ts index 1eebc7085d6..b20596e935f 100644 --- a/libraries/rush-lib/src/api/test/RushConfiguration.test.ts +++ b/libraries/rush-lib/src/api/test/RushConfiguration.test.ts @@ -51,10 +51,10 @@ describe(RushConfiguration.name, () => { assertPathProperty('commonFolder', rushConfiguration.commonFolder, './repo/common'); assertPathProperty( 'commonRushConfigFolder', - rushConfiguration.getCommonRushConfigFolder(), + rushConfiguration.commonRushConfigFolder, './repo/common/config/rush' ); - assertPathProperty('commonTempFolder', rushConfiguration.getCommonTempFolder(), './repo/common/temp'); + assertPathProperty('commonTempFolder', rushConfiguration.commonTempFolder, './repo/common/temp'); assertPathProperty('npmCacheFolder', rushConfiguration.npmCacheFolder, './repo/common/temp/npm-cache'); assertPathProperty('npmTmpFolder', rushConfiguration.npmTmpFolder, './repo/common/temp/npm-tmp'); expect(rushConfiguration.pnpmOptions.pnpmStore).toEqual('local'); @@ -124,10 +124,10 @@ describe(RushConfiguration.name, () => { assertPathProperty('commonFolder', rushConfiguration.commonFolder, './repo/common'); assertPathProperty( 'commonRushConfigFolder', - rushConfiguration.getCommonRushConfigFolder(), + rushConfiguration.commonRushConfigFolder, './repo/common/config/rush' ); - assertPathProperty('commonTempFolder', rushConfiguration.getCommonTempFolder(), './repo/common/temp'); + assertPathProperty('commonTempFolder', rushConfiguration.commonTempFolder, './repo/common/temp'); assertPathProperty('npmCacheFolder', rushConfiguration.npmCacheFolder, './repo/common/temp/npm-cache'); assertPathProperty('npmTmpFolder', rushConfiguration.npmTmpFolder, './repo/common/temp/npm-tmp'); expect(rushConfiguration.pnpmOptions.pnpmStore).toEqual('local'); @@ -198,7 +198,7 @@ describe(RushConfiguration.name, () => { const rushFilename: string = path.resolve(__dirname, 'repo', 'rush-pnpm.json'); const rushConfiguration: RushConfiguration = RushConfiguration.loadFromConfigurationFile(rushFilename); - assertPathProperty('commonTempFolder', rushConfiguration.getCommonTempFolder(), expectedValue); + assertPathProperty('commonTempFolder', rushConfiguration.commonTempFolder, expectedValue); assertPathProperty( 'npmCacheFolder', rushConfiguration.npmCacheFolder, diff --git a/libraries/rush-lib/src/cli/RushCommandLineParser.ts b/libraries/rush-lib/src/cli/RushCommandLineParser.ts index fa36b1d05ce..f2408e6c6bd 100644 --- a/libraries/rush-lib/src/cli/RushCommandLineParser.ts +++ b/libraries/rush-lib/src/cli/RushCommandLineParser.ts @@ -282,7 +282,7 @@ export class RushCommandLineParser extends CommandLineParser { let commandLineConfigFilePath: string | undefined; if (this.rushConfiguration) { commandLineConfigFilePath = path.join( - this.rushConfiguration.getCommonRushConfigFolder(), + this.rushConfiguration.commonRushConfigFolder, RushConstants.commandLineFilename ); } diff --git a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts index 5fb5cdb1ae2..d7890950baa 100644 --- a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts +++ b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts @@ -102,7 +102,7 @@ export class RushPnpmCommandLineParser { ); } - const workspaceFolder: string = rushConfiguration.getCommonTempFolder(); + const workspaceFolder: string = rushConfiguration.commonTempFolder; const workspaceFilePath: string = path.join(workspaceFolder, 'pnpm-workspace.yaml'); if (!FileSystem.exists(workspaceFilePath)) { @@ -292,7 +292,7 @@ export class RushPnpmCommandLineParser { } case 'patch-commit': { const pnpmOptionsJsonFilename: string = path.join( - this._rushConfiguration.getCommonRushConfigFolder(), + this._rushConfiguration.commonRushConfigFolder, RushConstants.pnpmConfigFilename ); if (this._rushConfiguration.rushConfigurationJson.pnpmOptions) { @@ -347,7 +347,7 @@ export class RushPnpmCommandLineParser { private _execute(): void { const rushConfiguration: RushConfiguration = this._rushConfiguration; - const workspaceFolder: string = rushConfiguration.getCommonTempFolder(); + const workspaceFolder: string = rushConfiguration.commonTempFolder; const pnpmEnvironmentMap: EnvironmentMap = new EnvironmentMap(process.env); pnpmEnvironmentMap.set('NPM_CONFIG_WORKSPACE_DIR', workspaceFolder); @@ -397,9 +397,7 @@ export class RushPnpmCommandLineParser { switch (commandName) { case 'patch-commit': { // Example: "C:\MyRepo\common\temp\package.json" - const commonPackageJsonFilename: string = `${this._rushConfiguration.getCommonTempFolder()}/${ - FileConstants.PackageJson - }`; + const commonPackageJsonFilename: string = `${this._rushConfiguration.commonTempFolder}/${FileConstants.PackageJson}`; const commonPackageJson: JsonObject = JsonFile.load(commonPackageJsonFilename); const newGlobalPatchedDependencies: Record | undefined = commonPackageJson?.pnpm?.patchedDependencies; @@ -407,9 +405,7 @@ export class RushPnpmCommandLineParser { this._rushConfiguration.pnpmOptions.globalPatchedDependencies; if (!objectsAreDeepEqual(currentGlobalPatchedDependencies, newGlobalPatchedDependencies)) { - const commonTempPnpmPatchesFolder: string = `${this._rushConfiguration.getCommonTempFolder()}/${ - RushConstants.pnpmPatchesFolderName - }`; + const commonTempPnpmPatchesFolder: string = `${this._rushConfiguration.commonTempFolder}/${RushConstants.pnpmPatchesFolderName}`; const rushPnpmPatchesFolder: string = `${this._rushConfiguration.commonFolder}/pnpm-${RushConstants.pnpmPatchesFolderName}`; // Copy (or delete) common\temp\patches\ --> common\pnpm-patches\ if (FileSystem.exists(commonTempPnpmPatchesFolder)) { @@ -447,7 +443,7 @@ export class RushPnpmCommandLineParser { } private async _doRushUpdateAsync(): Promise { - if (this._rushConfiguration.subspacesConfiguration?.enabled) { + if (this._rushConfiguration.subspacesFeatureEnabled) { this._terminal.writeLine(Colors.red('Rush Pnpm is currently unsupported with subspaces.')); throw new AlreadyReportedError(); } @@ -471,7 +467,9 @@ export class RushPnpmCommandLineParser { variant: undefined, maxInstallAttempts: RushConstants.defaultMaxInstallAttempts, pnpmFilterArguments: [], - checkOnly: false + checkOnly: false, + // TODO: Support subspaces + selectedSubspace: undefined }; const installManagerFactoryModule: typeof import('../logic/InstallManagerFactory') = await import( diff --git a/libraries/rush-lib/src/cli/RushXCommandLine.ts b/libraries/rush-lib/src/cli/RushXCommandLine.ts index dc2a20d02a6..df4a7da07e3 100644 --- a/libraries/rush-lib/src/cli/RushXCommandLine.ts +++ b/libraries/rush-lib/src/cli/RushXCommandLine.ts @@ -200,7 +200,7 @@ export class RushXCommandLine { workingDirectory: packageFolder, // If there is a rush.json then use its .npmrc from the temp folder. // Otherwise look for npmrc in the project folder. - initCwd: rushConfiguration ? rushConfiguration.getCommonTempFolder() : packageFolder, + initCwd: rushConfiguration ? rushConfiguration.commonTempFolder : packageFolder, handleOutput: false, environmentPathOptions: { includeProjectBin: true diff --git a/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts b/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts index 98ef0ea3616..ea7a7e7218a 100644 --- a/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts +++ b/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts @@ -23,6 +23,7 @@ import { Variants } from '../../api/Variants'; import { RushConstants } from '../../logic/RushConstants'; import type { SelectionParameterSet } from '../parsing/SelectionParameterSet'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; +import type { Subspace } from '../../api/Subspace'; /** * This is the common base class for InstallAction and UpdateAction. @@ -112,32 +113,33 @@ export abstract class BaseInstallAction extends BaseRushAction { const installManagerOptions: IInstallManagerOptions = await this.buildInstallOptionsAsync(); // If we are doing a filtered install and subspaces is enabled, we need to find the affected subspaces and install for all of them. - let subspaceNames: string[] | undefined; - if ( - installManagerOptions.pnpmFilterArguments.length && - this.rushConfiguration.subspacesConfiguration?.enabled - ) { + let selectedSubspaces: ReadonlySet | undefined; + if (installManagerOptions.pnpmFilterArguments.length && this.rushConfiguration.subspacesFeatureEnabled) { const selectedProjects: Set | undefined = await this._selectionParameters?.getSelectedProjectsAsync(this._terminal); if (selectedProjects) { - subspaceNames = this.rushConfiguration.getProjectsSubspaceSet(selectedProjects); + selectedSubspaces = this.rushConfiguration.getProjectsSubspaceSet(selectedProjects); } else { throw new Error('Specified filter arguments resolved in no projects being selected.'); } } - if (subspaceNames) { + if (selectedSubspaces) { // Check each subspace for version inconsistencies - for (const subspaceName of subspaceNames) { + for (const subspace of selectedSubspaces) { VersionMismatchFinder.ensureConsistentVersions(this.rushConfiguration, this._terminal, { variant: this._variant.value, - subspaceName: subspaceName + subspace }); } } else if (this._subspaceParameter) { + const selectedSubspace: Subspace = this.rushConfiguration.getSubspace( + this._subspaceParameter.value ?? '' + ); + VersionMismatchFinder.ensureConsistentVersions(this.rushConfiguration, this._terminal, { variant: this._variant.value, - subspaceName: this._subspaceParameter.value + subspace: selectedSubspace }); } else { VersionMismatchFinder.ensureConsistentVersions(this.rushConfiguration, this._terminal, { @@ -193,12 +195,12 @@ export abstract class BaseInstallAction extends BaseRushAction { let installSuccessful: boolean = true; try { - if (subspaceNames) { + if (selectedSubspaces) { // Run the install for each affected subspace - for (const subspaceName of subspaceNames) { - installManagerOptions.subspaceName = subspaceName; + for (const selectedSubspace of selectedSubspaces) { + installManagerOptions.selectedSubspace = selectedSubspace; // eslint-disable-next-line no-console - console.log(colors.green(`Installing for subspace: ${subspaceName}`)); + console.log(colors.green(`Installing for subspace: ${selectedSubspace.subspaceName}`)); await this._doInstall(installManagerFactoryModule, purgeManager, installManagerOptions); } } else { diff --git a/libraries/rush-lib/src/cli/actions/BaseRushAction.ts b/libraries/rush-lib/src/cli/actions/BaseRushAction.ts index 88c43d617cc..cde875dc321 100644 --- a/libraries/rush-lib/src/cli/actions/BaseRushAction.ts +++ b/libraries/rush-lib/src/cli/actions/BaseRushAction.ts @@ -64,7 +64,7 @@ export abstract class BaseConfiglessRushAction extends CommandLineAction impleme if (this.rushConfiguration) { if (!this._safeForSimultaneousRushProcesses) { - if (!LockFile.tryAcquire(this.rushConfiguration.getCommonTempFolder(), 'rush')) { + if (!LockFile.tryAcquire(this.rushConfiguration.commonTempFolder, 'rush')) { // eslint-disable-next-line no-console console.log(colors.red(`Another Rush command is already running in this repository.`)); process.exit(1); @@ -90,7 +90,7 @@ export abstract class BaseConfiglessRushAction extends CommandLineAction impleme // eslint-disable-next-line dot-notation let environmentPath: string | undefined = process.env['PATH']; environmentPath = - path.join(this.rushConfiguration.getCommonTempFolder(), 'node_modules', '.bin') + + path.join(this.rushConfiguration.commonTempFolder, 'node_modules', '.bin') + path.delimiter + environmentPath; // eslint-disable-next-line dot-notation diff --git a/libraries/rush-lib/src/cli/actions/DeployAction.ts b/libraries/rush-lib/src/cli/actions/DeployAction.ts index 8958a886244..853e3297eff 100644 --- a/libraries/rush-lib/src/cli/actions/DeployAction.ts +++ b/libraries/rush-lib/src/cli/actions/DeployAction.ts @@ -147,11 +147,11 @@ export class DeployAction extends BaseRushAction { if (this.rushConfiguration.packageManager === 'pnpm') { const pnpmfileConfiguration: PnpmfileConfiguration = await PnpmfileConfiguration.initializeAsync( this.rushConfiguration, - undefined + this.rushConfiguration.defaultSubspace ); transformPackageJson = pnpmfileConfiguration.transform.bind(pnpmfileConfiguration); if (!scenarioConfiguration.json.omitPnpmWorkaroundLinks) { - pnpmInstallFolder = this.rushConfiguration.getCommonTempFolder(); + pnpmInstallFolder = this.rushConfiguration.commonTempFolder; } } diff --git a/libraries/rush-lib/src/cli/actions/InstallAction.ts b/libraries/rush-lib/src/cli/actions/InstallAction.ts index 8a7f588537b..144334b7884 100644 --- a/libraries/rush-lib/src/cli/actions/InstallAction.ts +++ b/libraries/rush-lib/src/cli/actions/InstallAction.ts @@ -8,6 +8,7 @@ import { BaseInstallAction } from './BaseInstallAction'; import type { IInstallManagerOptions } from '../../logic/base/BaseInstallManagerTypes'; import type { RushCommandLineParser } from '../RushCommandLineParser'; import { SelectionParameterSet } from '../parsing/SelectionParameterSet'; +import type { Subspace } from '../../api/Subspace'; export class InstallAction extends BaseInstallAction { private readonly _checkOnlyParameter: CommandLineFlagParameter; @@ -46,6 +47,9 @@ export class InstallAction extends BaseInstallAction { protected async buildInstallOptionsAsync(): Promise { const terminal: Terminal = new Terminal(new ConsoleTerminalProvider()); + const selectedSubspace: Subspace | undefined = this._subspaceParameter.value + ? this.rushConfiguration.getSubspace(this._subspaceParameter.value) + : undefined; return { debug: this.parser.isDebug, allowShrinkwrapUpdates: false, @@ -64,8 +68,7 @@ export class InstallAction extends BaseInstallAction { // These are derived independently of the selection for command line brevity pnpmFilterArguments: await this._selectionParameters!.getPnpmFilterArgumentsAsync(terminal), checkOnly: this._checkOnlyParameter.value, - subspaceName: this._subspaceParameter.value, - + selectedSubspace, beforeInstallAsync: () => this.rushSession.hooks.beforeInstall.promise(this) }; } diff --git a/libraries/rush-lib/src/cli/actions/PublishAction.ts b/libraries/rush-lib/src/cli/actions/PublishAction.ts index a7f7be0f1c5..53a3fd51359 100644 --- a/libraries/rush-lib/src/cli/actions/PublishAction.ts +++ b/libraries/rush-lib/src/cli/actions/PublishAction.ts @@ -212,15 +212,19 @@ export class PublishAction extends BaseRushAction { * Executes the publish action, which will read change request files, apply changes to package.jsons, */ protected async runAsync(): Promise { - await PolicyValidator.validatePolicyAsync(this.rushConfiguration, { bypassPolicy: false }); + await PolicyValidator.validatePolicyAsync( + this.rushConfiguration, + this.rushConfiguration.defaultSubspace, + { bypassPolicy: false } + ); // Example: "common\temp\publish-home" - this._targetNpmrcPublishFolder = path.join(this.rushConfiguration.getCommonTempFolder(), 'publish-home'); + this._targetNpmrcPublishFolder = path.join(this.rushConfiguration.commonTempFolder, 'publish-home'); // Example: "common\temp\publish-home\.npmrc" this._targetNpmrcPublishPath = path.join(this._targetNpmrcPublishFolder, '.npmrc'); - const allPackages: Map = this.rushConfiguration.projectsByName; + const allPackages: ReadonlyMap = this.rushConfiguration.projectsByName; if (this._regenerateChangelogs.value) { // eslint-disable-next-line no-console @@ -268,7 +272,7 @@ export class PublishAction extends BaseRushAction { private async _publishChangesAsync( git: Git, publishGit: PublishGit, - allPackages: Map + allPackages: ReadonlyMap ): Promise { const changeManager: ChangeManager = new ChangeManager(this.rushConfiguration); await changeManager.loadAsync( @@ -348,7 +352,7 @@ export class PublishAction extends BaseRushAction { } } - private _publishAll(git: PublishGit, allPackages: Map): void { + private _publishAll(git: PublishGit, allPackages: ReadonlyMap): void { // eslint-disable-next-line no-console console.log(`Rush publish starts with includeAll and version policy ${this._versionPolicy.value}`); @@ -524,7 +528,7 @@ export class PublishAction extends BaseRushAction { const tarballPath: string = path.join(project.publishFolder, tarballName); const destFolder: string = this._releaseFolder.value ? this._releaseFolder.value - : path.join(this.rushConfiguration.getCommonTempFolder(), 'artifacts', 'packages'); + : path.join(this.rushConfiguration.commonTempFolder, 'artifacts', 'packages'); FileSystem.move({ sourcePath: tarballPath, @@ -576,11 +580,7 @@ export class PublishAction extends BaseRushAction { Utilities.createFolderWithRetry(this._targetNpmrcPublishFolder); // Copy down the committed "common\config\rush\.npmrc-publish" file, if there is one - Utilities.syncNpmrc( - this.rushConfiguration.getCommonRushConfigFolder(), - this._targetNpmrcPublishFolder, - true - ); + Utilities.syncNpmrc(this.rushConfiguration.commonRushConfigFolder, this._targetNpmrcPublishFolder, true); } private _addSharedNpmConfig(env: { [key: string]: string | undefined }, args: string[]): void { diff --git a/libraries/rush-lib/src/cli/actions/UpdateAction.ts b/libraries/rush-lib/src/cli/actions/UpdateAction.ts index c966ab791b9..e4f70a38af0 100644 --- a/libraries/rush-lib/src/cli/actions/UpdateAction.ts +++ b/libraries/rush-lib/src/cli/actions/UpdateAction.ts @@ -8,6 +8,7 @@ import type { IInstallManagerOptions } from '../../logic/base/BaseInstallManager import type { RushCommandLineParser } from '../RushCommandLineParser'; import { SelectionParameterSet } from '../parsing/SelectionParameterSet'; import { ConsoleTerminalProvider, Terminal } from '@rushstack/node-core-library'; +import type { Subspace } from '../../api/Subspace'; export class UpdateAction extends BaseInstallAction { private readonly _fullParameter: CommandLineFlagParameter; @@ -33,7 +34,7 @@ export class UpdateAction extends BaseInstallAction { parser }); - if (this.rushConfiguration?.subspacesConfiguration?.enabled) { + if (this.rushConfiguration?.subspacesFeatureEnabled) { // Partial update is supported only when subspaces is enabled. this._selectionParameters = new SelectionParameterSet(this.rushConfiguration, this, { // Include lockfile processing since this expands the selection, and we need to select @@ -77,6 +78,10 @@ export class UpdateAction extends BaseInstallAction { protected async buildInstallOptionsAsync(): Promise { const terminal: Terminal = new Terminal(new ConsoleTerminalProvider()); + const selectedSubspace: Subspace | undefined = this._subspaceParameter.value + ? this.rushConfiguration.getSubspace(this._subspaceParameter.value) + : undefined; + return { debug: this.parser.isDebug, allowShrinkwrapUpdates: true, @@ -95,7 +100,7 @@ export class UpdateAction extends BaseInstallAction { // These are derived independently of the selection for command line brevity pnpmFilterArguments: (await this._selectionParameters?.getPnpmFilterArgumentsAsync(terminal)) || [], checkOnly: false, - subspaceName: this._subspaceParameter.value, + selectedSubspace, beforeInstallAsync: () => this.rushSession.hooks.beforeInstall.promise(this) }; diff --git a/libraries/rush-lib/src/cli/actions/VersionAction.ts b/libraries/rush-lib/src/cli/actions/VersionAction.ts index d224980c1bd..542930e25a8 100644 --- a/libraries/rush-lib/src/cli/actions/VersionAction.ts +++ b/libraries/rush-lib/src/cli/actions/VersionAction.ts @@ -95,10 +95,14 @@ export class VersionAction extends BaseRushAction { } protected async runAsync(): Promise { - await PolicyValidator.validatePolicyAsync(this.rushConfiguration, { - bypassPolicyAllowed: true, - bypassPolicy: this._bypassPolicy.value - }); + await PolicyValidator.validatePolicyAsync( + this.rushConfiguration, + this.rushConfiguration.defaultSubspace, + { + bypassPolicyAllowed: true, + bypassPolicy: this._bypassPolicy.value + } + ); const git: Git = new Git(this.rushConfiguration); const userEmail: string = git.getGitEmail(); diff --git a/libraries/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts index f17e893604f..60de3deabf3 100644 --- a/libraries/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts @@ -159,7 +159,7 @@ export class GlobalScriptAction extends BaseScriptAction { const exitCode: number = Utilities.executeLifecycleCommand(shellCommand, { rushConfiguration: this.rushConfiguration, workingDirectory: this.rushConfiguration.rushJsonFolder, - initCwd: this.rushConfiguration.getCommonTempFolder(), + initCwd: this.rushConfiguration.commonTempFolder, handleOutput: false, environmentPathOptions: { includeRepoBin: true, diff --git a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts index dec3fb89610..33545142ef8 100644 --- a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts @@ -263,9 +263,11 @@ export class PhasedScriptAction extends BaseScriptAction { if (!this._runsBeforeInstall) { // TODO: Replace with last-install.flag when "rush link" and "rush unlink" are removed - const lastLinkFlag: LastLinkFlag = LastLinkFlagFactory.getCommonTempFlag(this.rushConfiguration); + const lastLinkFlag: LastLinkFlag = LastLinkFlagFactory.getCommonTempFlag( + this.rushConfiguration.defaultSubspace + ); // Only check for a valid link flag when subspaces is not enabled - if (!lastLinkFlag.isValid() && !this.rushConfiguration.subspacesConfiguration?.enabled) { + if (!lastLinkFlag.isValid() && !this.rushConfiguration.subspacesFeatureEnabled) { const useWorkspaces: boolean = this.rushConfiguration.pnpmOptions && this.rushConfiguration.pnpmOptions.useWorkspaces; if (useWorkspaces) { diff --git a/libraries/rush-lib/src/cli/test/RushCommandLineParser.test.ts b/libraries/rush-lib/src/cli/test/RushCommandLineParser.test.ts index 89a921c91e8..50d02fa27cd 100644 --- a/libraries/rush-lib/src/cli/test/RushCommandLineParser.test.ts +++ b/libraries/rush-lib/src/cli/test/RushCommandLineParser.test.ts @@ -103,7 +103,8 @@ async function getCommandLineParserInstanceAsync( // Bulk tasks are hard-coded to expect install to have been completed. So, ensure the last-link.flag // file exists and is valid - LastLinkFlagFactory.getCommonTempFlag(parser.rushConfiguration).create(); + // TODO: Support subspaces + LastLinkFlagFactory.getCommonTempFlag(parser.rushConfiguration.defaultSubspace).create(); // Mock the command process.argv = ['pretend-this-is-node.exe', 'pretend-this-is-rush', taskName]; @@ -378,7 +379,7 @@ describe('RushCommandLineParser', () => { it('creates a custom telemetry file', async () => { const repoName: string = 'tapFlushTelemetryAndRunBuildActionRepo'; const instance: IParserTestInstance = await getCommandLineParserInstanceAsync(repoName, 'build'); - const telemetryFilePath: string = `${instance.parser.rushConfiguration.getCommonTempFolder()}/test-telemetry.json`; + const telemetryFilePath: string = `${instance.parser.rushConfiguration.commonTempFolder}/test-telemetry.json`; FileSystem.deleteFile(telemetryFilePath); /** diff --git a/libraries/rush-lib/src/cli/test/RushXCommandLine.test.ts b/libraries/rush-lib/src/cli/test/RushXCommandLine.test.ts index f5b18f70860..b29f362a3c8 100644 --- a/libraries/rush-lib/src/cli/test/RushXCommandLine.test.ts +++ b/libraries/rush-lib/src/cli/test/RushXCommandLine.test.ts @@ -60,9 +60,9 @@ describe(RushXCommandLine.name, () => { } as RushConfigurationProject ]; rushConfiguration = { - getCommonRushConfigFolder: () => '', + commonRushConfigFolder: '', rushJsonFolder: '', - getCommonTempFolder: () => 'common/temp', + commonTempFolder: 'common/temp', projects, tryGetProjectForPath(path: string): RushConfigurationProject | undefined { return projects.find((project) => project.projectFolder === path); diff --git a/libraries/rush-lib/src/cli/test/rush-mock-flush-telemetry-plugin/index.ts b/libraries/rush-lib/src/cli/test/rush-mock-flush-telemetry-plugin/index.ts index ef652092eb4..d3e9d15ae67 100644 --- a/libraries/rush-lib/src/cli/test/rush-mock-flush-telemetry-plugin/index.ts +++ b/libraries/rush-lib/src/cli/test/rush-mock-flush-telemetry-plugin/index.ts @@ -10,7 +10,7 @@ import type { RushSession, RushConfiguration, ITelemetryData } from '../../../in export default class RushMockFlushTelemetryPlugin { public apply(rushSession: RushSession, rushConfiguration: RushConfiguration): void { async function flushTelemetry(data: ReadonlyArray): Promise { - const targetPath: string = `${rushConfiguration.getCommonTempFolder()}/test-telemetry.json`; + const targetPath: string = `${rushConfiguration.commonTempFolder}/test-telemetry.json`; await JsonFile.saveAsync(data, targetPath, { ignoreUndefinedValues: true }); } diff --git a/libraries/rush-lib/src/index.ts b/libraries/rush-lib/src/index.ts index be57668c1e3..6e09de32db5 100644 --- a/libraries/rush-lib/src/index.ts +++ b/libraries/rush-lib/src/index.ts @@ -10,6 +10,7 @@ export { ApprovedPackagesPolicy } from './api/ApprovedPackagesPolicy'; export { RushConfiguration, ITryFindRushJsonLocationOptions } from './api/RushConfiguration'; +export { Subspace } from './api/Subspace'; export { SubspacesConfiguration } from './api/SubspacesConfiguration'; export { diff --git a/libraries/rush-lib/src/logic/Autoinstaller.ts b/libraries/rush-lib/src/logic/Autoinstaller.ts index b285553f93b..9108c25e4f8 100644 --- a/libraries/rush-lib/src/logic/Autoinstaller.ts +++ b/libraries/rush-lib/src/logic/Autoinstaller.ts @@ -83,7 +83,6 @@ export class Autoinstaller { this._rushConfiguration, this._rushGlobalFolder, RushConstants.defaultMaxInstallAttempts, - undefined, this._restrictConsoleOutput ); @@ -128,7 +127,7 @@ export class Autoinstaller { } // Copy: .../common/autoinstallers/my-task/.npmrc - Utilities.syncNpmrc(this._rushConfiguration.getCommonRushConfigFolder(), autoinstallerFullPath); + Utilities.syncNpmrc(this._rushConfiguration.commonRushConfigFolder, autoinstallerFullPath); this._logIfConsoleOutputIsNotRestricted( `Installing dependencies under ${autoinstallerFullPath}...\n` @@ -164,7 +163,6 @@ export class Autoinstaller { this._rushConfiguration, this._rushGlobalFolder, RushConstants.defaultMaxInstallAttempts, - undefined, this._restrictConsoleOutput ); @@ -211,7 +209,7 @@ export class Autoinstaller { this._logIfConsoleOutputIsNotRestricted(); - Utilities.syncNpmrc(this._rushConfiguration.getCommonRushConfigFolder(), this.folderFullPath); + Utilities.syncNpmrc(this._rushConfiguration.commonRushConfigFolder, this.folderFullPath); Utilities.executeCommand({ command: this._rushConfiguration.packageManagerToolFilename, diff --git a/libraries/rush-lib/src/logic/ChangeManager.ts b/libraries/rush-lib/src/logic/ChangeManager.ts index 3d2b8a4edf7..254ae58bb50 100644 --- a/libraries/rush-lib/src/logic/ChangeManager.ts +++ b/libraries/rush-lib/src/logic/ChangeManager.ts @@ -20,7 +20,7 @@ import { ChangelogGenerator } from './ChangelogGenerator'; export class ChangeManager { private _prereleaseToken!: PrereleaseToken; private _orderedChanges!: IChangeInfo[]; - private _allPackages!: Map; + private _allPackages!: ReadonlyMap; private _allChanges!: IChangeRequests; private _changeFiles!: ChangeFiles; private _rushConfiguration: RushConfiguration; @@ -69,7 +69,7 @@ export class ChangeManager { return this._orderedChanges; } - public get allPackages(): Map { + public get allPackages(): ReadonlyMap { return this._allPackages; } diff --git a/libraries/rush-lib/src/logic/ChangelogGenerator.ts b/libraries/rush-lib/src/logic/ChangelogGenerator.ts index 2a8f1021029..cdcc4880ec1 100644 --- a/libraries/rush-lib/src/logic/ChangelogGenerator.ts +++ b/libraries/rush-lib/src/logic/ChangelogGenerator.ts @@ -33,7 +33,7 @@ export class ChangelogGenerator { */ public static updateChangelogs( allChanges: IChangeRequests, - allProjects: Map, + allProjects: ReadonlyMap, rushConfiguration: RushConfiguration, shouldCommit: boolean ): IChangelog[] { @@ -64,7 +64,7 @@ export class ChangelogGenerator { * Fully regenerate the markdown files based on the current json files. */ public static regenerateChangelogs( - allProjects: Map, + allProjects: ReadonlyMap, rushConfiguration: RushConfiguration ): void { allProjects.forEach((project) => { diff --git a/libraries/rush-lib/src/logic/EventHooksManager.ts b/libraries/rush-lib/src/logic/EventHooksManager.ts index d0b09082a2e..9adca3f769a 100644 --- a/libraries/rush-lib/src/logic/EventHooksManager.ts +++ b/libraries/rush-lib/src/logic/EventHooksManager.ts @@ -18,7 +18,7 @@ export class EventHooksManager { public constructor(rushConfiguration: RushConfiguration) { this._rushConfiguration = rushConfiguration; this._eventHooks = rushConfiguration.eventHooks; - this._commonTempFolder = rushConfiguration.getCommonTempFolder(); + this._commonTempFolder = rushConfiguration.commonTempFolder; } public handle(event: Event, isDebug: boolean, ignoreHooks: boolean): void { diff --git a/libraries/rush-lib/src/logic/PackageJsonUpdater.ts b/libraries/rush-lib/src/logic/PackageJsonUpdater.ts index 96990aca522..358f3c37a86 100644 --- a/libraries/rush-lib/src/logic/PackageJsonUpdater.ts +++ b/libraries/rush-lib/src/logic/PackageJsonUpdater.ts @@ -33,6 +33,7 @@ import { type IPackageJsonUpdaterRushRemoveOptions, SemVerStyle } from './PackageJsonUpdaterTypes'; +import type { Subspace } from '../api/Subspace'; /** * Options for adding a dependency to a particular project. @@ -242,15 +243,15 @@ export class PackageJsonUpdater { } if (!skipUpdate) { - if (this._rushConfiguration.subspacesConfiguration?.enabled) { - const subspaceNames: string[] = this._rushConfiguration.getProjectsSubspaceSet( + if (this._rushConfiguration.subspacesFeatureEnabled) { + const subspaceSet: ReadonlySet = this._rushConfiguration.getProjectsSubspaceSet( new Set(options.projects) ); - for (const subspaceName of subspaceNames) { - await this._doUpdate(debugInstall, variant, subspaceName); + for (const subspace of subspaceSet) { + await this._doUpdate(debugInstall, subspace, variant); } } else { - await this._doUpdate(debugInstall, variant); + await this._doUpdate(debugInstall, this._rushConfiguration.defaultSubspace, variant); } } } @@ -272,23 +273,23 @@ export class PackageJsonUpdater { } if (!skipUpdate) { - if (this._rushConfiguration.subspacesConfiguration?.enabled) { - const subspaceNames: string[] = this._rushConfiguration.getProjectsSubspaceSet( + if (this._rushConfiguration.subspacesFeatureEnabled) { + const subspaceSet: ReadonlySet = this._rushConfiguration.getProjectsSubspaceSet( new Set(options.projects) ); - for (const subspaceName of subspaceNames) { - await this._doUpdate(debugInstall, variant, subspaceName); + for (const subspace of subspaceSet) { + await this._doUpdate(debugInstall, subspace, variant); } } else { - await this._doUpdate(debugInstall, variant); + await this._doUpdate(debugInstall, this._rushConfiguration.defaultSubspace, variant); } } } private async _doUpdate( debugInstall: boolean, - variant: string | undefined, - subspaceName?: string | undefined + subspace: Subspace, + variant: string | undefined ): Promise { this._terminal.writeLine(); this._terminal.writeLine(Colors.green('Running "rush update"')); @@ -309,7 +310,7 @@ export class PackageJsonUpdater { maxInstallAttempts: RushConstants.defaultMaxInstallAttempts, pnpmFilterArguments: [], checkOnly: false, - subspaceName + selectedSubspace: subspace }; const installManager: BaseInstallManager = await InstallManagerFactory.getInstallManagerAsync( @@ -665,7 +666,7 @@ export class PackageJsonUpdater { const allVersions: string = Utilities.executeCommandAndCaptureOutput( this._rushConfiguration.packageManagerToolFilename, commandArgs, - this._rushConfiguration.getCommonTempFolder() + this._rushConfiguration.commonTempFolder ); let versionList: string[]; @@ -726,7 +727,7 @@ export class PackageJsonUpdater { selectedVersion = Utilities.executeCommandAndCaptureOutput( this._rushConfiguration.packageManagerToolFilename, commandArgs, - this._rushConfiguration.getCommonTempFolder() + this._rushConfiguration.commonTempFolder ).trim(); } diff --git a/libraries/rush-lib/src/logic/ProjectWatcher.ts b/libraries/rush-lib/src/logic/ProjectWatcher.ts index cadab8c37d8..574db2a33e9 100644 --- a/libraries/rush-lib/src/logic/ProjectWatcher.ts +++ b/libraries/rush-lib/src/logic/ProjectWatcher.ts @@ -120,7 +120,7 @@ export class ProjectWatcher { pathsToWatch.set(repoRoot, { recurse: false }); // Watch the rush config folder non-recursively - pathsToWatch.set(Path.convertToSlashes(this._rushConfiguration.getCommonRushConfigFolder()), { + pathsToWatch.set(Path.convertToSlashes(this._rushConfiguration.commonTempFolder), { recurse: false }); diff --git a/libraries/rush-lib/src/logic/PublishUtilities.ts b/libraries/rush-lib/src/logic/PublishUtilities.ts index aa8ca892584..f683e9fdf82 100644 --- a/libraries/rush-lib/src/logic/PublishUtilities.ts +++ b/libraries/rush-lib/src/logic/PublishUtilities.ts @@ -37,7 +37,7 @@ interface IAddChangeOptions { change: IChangeInfo; changeFilePath?: string; allChanges: IChangeRequests; - allPackages: Map; + allPackages: ReadonlyMap; rushConfiguration: RushConfiguration; prereleaseToken?: PrereleaseToken; projectsToExclude?: Set; @@ -50,7 +50,7 @@ export class PublishUtilities { * @returns Dictionary of all change requests, keyed by package name. */ public static async findChangeRequestsAsync( - allPackages: Map, + allPackages: ReadonlyMap, rushConfiguration: RushConfiguration, changeFiles: ChangeFiles, includeCommitDetails?: boolean, @@ -196,7 +196,7 @@ export class PublishUtilities { */ public static updatePackages( allChanges: IChangeRequests, - allPackages: Map, + allPackages: ReadonlyMap, rushConfiguration: RushConfiguration, shouldCommit: boolean, prereleaseToken?: PrereleaseToken, @@ -378,7 +378,7 @@ export class PublishUtilities { private static _writePackageChanges( change: IChangeInfo, allChanges: IChangeRequests, - allPackages: Map, + allPackages: ReadonlyMap, rushConfiguration: RushConfiguration, shouldCommit: boolean, prereleaseToken?: PrereleaseToken, @@ -456,7 +456,7 @@ export class PublishUtilities { } private static _isCyclicDependency( - allPackages: Map, + allPackages: ReadonlyMap, packageName: string, dependencyName: string ): boolean { @@ -468,7 +468,7 @@ export class PublishUtilities { packageName: string, dependencies: { [key: string]: string } | undefined, allChanges: IChangeRequests, - allPackages: Map, + allPackages: ReadonlyMap, rushConfiguration: RushConfiguration, prereleaseToken: PrereleaseToken | undefined, projectsToExclude?: Set @@ -700,7 +700,7 @@ export class PublishUtilities { private static _updateDownstreamDependencies( change: IChangeInfo, allChanges: IChangeRequests, - allPackages: Map, + allPackages: ReadonlyMap, rushConfiguration: RushConfiguration, prereleaseToken: PrereleaseToken | undefined, projectsToExclude?: Set @@ -750,7 +750,7 @@ export class PublishUtilities { dependencies: { [packageName: string]: string } | undefined, change: IChangeInfo, allChanges: IChangeRequests, - allPackages: Map, + allPackages: ReadonlyMap, rushConfiguration: RushConfiguration, prereleaseToken: PrereleaseToken | undefined, projectsToExclude?: Set @@ -831,7 +831,7 @@ export class PublishUtilities { dependencyName: string, dependencyChange: IChangeInfo, allChanges: IChangeRequests, - allPackages: Map, + allPackages: ReadonlyMap, rushConfiguration: RushConfiguration ): void { let currentDependencyVersion: string | undefined = dependencies[dependencyName]; diff --git a/libraries/rush-lib/src/logic/PurgeManager.ts b/libraries/rush-lib/src/logic/PurgeManager.ts index 18efc2bd6d7..6c2c15e9e01 100644 --- a/libraries/rush-lib/src/logic/PurgeManager.ts +++ b/libraries/rush-lib/src/logic/PurgeManager.ts @@ -24,7 +24,7 @@ export class PurgeManager { this._rushGlobalFolder = rushGlobalFolder; const commonAsyncRecyclerPath: string = path.join( - this._rushConfiguration.getCommonTempFolder(), + this._rushConfiguration.commonTempFolder, RushConstants.rushRecyclerFolderName ); this.commonTempFolderRecycler = new AsyncRecycler(commonAsyncRecyclerPath); @@ -53,11 +53,11 @@ export class PurgeManager { public purgeNormal(): void { // Delete everything under common\temp except for the recycler folder itself // eslint-disable-next-line no-console - console.log('Purging ' + this._rushConfiguration.getCommonTempFolder()); + console.log('Purging ' + this._rushConfiguration.commonTempFolder); this.commonTempFolderRecycler.moveAllItemsInFolder( - this._rushConfiguration.getCommonTempFolder(), - this._getMembersToExclude(this._rushConfiguration.getCommonTempFolder(), true) + this._rushConfiguration.commonTempFolder, + this._getMembersToExclude(this._rushConfiguration.commonTempFolder, true) ); } diff --git a/libraries/rush-lib/src/logic/RepoStateFile.ts b/libraries/rush-lib/src/logic/RepoStateFile.ts index 50f2f34331e..d2c6758dde4 100644 --- a/libraries/rush-lib/src/logic/RepoStateFile.ts +++ b/libraries/rush-lib/src/logic/RepoStateFile.ts @@ -36,7 +36,6 @@ interface IRepoStateJson { export class RepoStateFile { private static _jsonSchema: JsonSchema = JsonSchema.fromLoadedObject(schemaJson); - private _subspaceName: string | undefined; private _pnpmShrinkwrapHash: string | undefined; private _preferredVersionsHash: string | undefined; private _isValid: boolean; @@ -47,14 +46,8 @@ export class RepoStateFile { */ public readonly filePath: string; - private constructor( - repoStateJson: IRepoStateJson | undefined, - isValid: boolean, - filePath: string, - subspaceName: string | undefined - ) { + private constructor(repoStateJson: IRepoStateJson | undefined, isValid: boolean, filePath: string) { this.filePath = filePath; - this._subspaceName = subspaceName; this._isValid = isValid; if (repoStateJson) { @@ -91,7 +84,7 @@ export class RepoStateFile { * @param jsonFilename - The path to the repo-state.json file. * @param variant - The variant currently being used by Rush. */ - public static loadFromFile(jsonFilename: string, subspaceName: string | undefined): RepoStateFile { + public static loadFromFile(jsonFilename: string): RepoStateFile { let fileContents: string | undefined; try { fileContents = FileSystem.readFile(jsonFilename); @@ -131,7 +124,7 @@ export class RepoStateFile { } } - return new RepoStateFile(repoStateJson, !foundMergeConflictMarker, jsonFilename, subspaceName); + return new RepoStateFile(repoStateJson, !foundMergeConflictMarker, jsonFilename); } /** @@ -142,7 +135,10 @@ export class RepoStateFile { * * @returns true if the file was modified, otherwise false. */ - public refreshState(rushConfiguration: RushConfiguration): boolean { + public refreshState( + rushConfiguration: RushConfiguration, + commonVersions: CommonVersionsConfiguration | undefined + ): boolean { // Only support saving the pnpm shrinkwrap hash if it was enabled const preventShrinkwrapChanges: boolean = rushConfiguration.packageManager === 'pnpm' && @@ -169,12 +165,7 @@ export class RepoStateFile { } // Currently, only support saving the preferred versions hash if using workspaces - const useWorkspaces: boolean = - rushConfiguration.pnpmOptions && rushConfiguration.pnpmOptions.useWorkspaces; - if (useWorkspaces) { - const commonVersions: CommonVersionsConfiguration = rushConfiguration.getCommonVersions( - this._subspaceName - ); + if (commonVersions) { const preferredVersionsHash: string = commonVersions.getPreferredVersionsHash(); if (this._preferredVersionsHash !== preferredVersionsHash) { this._preferredVersionsHash = preferredVersionsHash; diff --git a/libraries/rush-lib/src/logic/SetupChecks.ts b/libraries/rush-lib/src/logic/SetupChecks.ts index 50ad02bf709..487dd553209 100644 --- a/libraries/rush-lib/src/logic/SetupChecks.ts +++ b/libraries/rush-lib/src/logic/SetupChecks.ts @@ -67,9 +67,7 @@ export class SetupChecks { const seenFolders: Set = new Set(); // Check from the real parent of the common/temp folder - const commonTempParent: string = path.dirname( - FileSystem.getRealPath(rushConfiguration.getCommonTempFolder()) - ); + const commonTempParent: string = path.dirname(FileSystem.getRealPath(rushConfiguration.commonTempFolder)); SetupChecks._collectPhantomFoldersUpwards(commonTempParent, phantomFolders, seenFolders); // Check from the real folder containing rush.json diff --git a/libraries/rush-lib/src/logic/Telemetry.ts b/libraries/rush-lib/src/logic/Telemetry.ts index 02e0fdac7ca..761638499a6 100644 --- a/libraries/rush-lib/src/logic/Telemetry.ts +++ b/libraries/rush-lib/src/logic/Telemetry.ts @@ -143,7 +143,7 @@ export class Telemetry { this._store = []; const folderName: string = 'telemetry'; - this._dataFolder = path.join(this._rushConfiguration.getCommonTempFolder(), folderName); + this._dataFolder = path.join(this._rushConfiguration.commonTempFolder, folderName); } public log(telemetryData: ITelemetryData): void { diff --git a/libraries/rush-lib/src/logic/TempProjectHelper.ts b/libraries/rush-lib/src/logic/TempProjectHelper.ts index 50052087bfd..4ac0cd1c088 100644 --- a/libraries/rush-lib/src/logic/TempProjectHelper.ts +++ b/libraries/rush-lib/src/logic/TempProjectHelper.ts @@ -8,29 +8,27 @@ import * as path from 'path'; import type { RushConfigurationProject } from '../api/RushConfigurationProject'; import type { RushConfiguration } from '../api/RushConfiguration'; import { RushConstants } from './RushConstants'; +import type { Subspace } from '../api/Subspace'; // The PosixModeBits are intended to be used with bitwise operations. /* eslint-disable no-bitwise */ export class TempProjectHelper { private _rushConfiguration: RushConfiguration; + private _subspace: Subspace; - public constructor(rushConfiguration: RushConfiguration) { + public constructor(rushConfiguration: RushConfiguration, subspace: Subspace) { this._rushConfiguration = rushConfiguration; + this._subspace = subspace; } /** * Deletes the existing tarball and creates a tarball for the given rush project */ - public createTempProjectTarball( - rushProject: RushConfigurationProject, - subspaceName: string | undefined - ): void { - FileSystem.ensureFolder( - path.resolve(this._rushConfiguration.getCommonTempFolder(subspaceName), 'projects') - ); - const tarballFile: string = this.getTarballFilePath(rushProject, subspaceName); - const tempProjectFolder: string = this.getTempProjectFolder(rushProject, subspaceName); + public createTempProjectTarball(rushProject: RushConfigurationProject): void { + FileSystem.ensureFolder(path.resolve(this._subspace.getSubspaceTempFolder(), 'projects')); + const tarballFile: string = this.getTarballFilePath(rushProject); + const tempProjectFolder: string = this.getTempProjectFolder(rushProject); FileSystem.deleteFile(tarballFile); @@ -64,21 +62,18 @@ export class TempProjectHelper { * Gets the path to the tarball * Example: "C:\MyRepo\common\temp\projects\my-project-2.tgz" */ - public getTarballFilePath(project: RushConfigurationProject, subspaceName: string | undefined): string { + public getTarballFilePath(project: RushConfigurationProject): string { return path.join( - this._rushConfiguration.getCommonTempFolder(subspaceName), + this._subspace.getSubspaceTempFolder(), RushConstants.rushTempProjectsFolderName, `${project.unscopedTempProjectName}.tgz` ); } - public getTempProjectFolder( - rushProject: RushConfigurationProject, - subspaceName: string | undefined - ): string { + public getTempProjectFolder(rushProject: RushConfigurationProject): string { const unscopedTempProjectName: string = rushProject.unscopedTempProjectName; return path.join( - this._rushConfiguration.getCommonTempFolder(subspaceName), + this._subspace.getSubspaceTempFolder(), RushConstants.rushTempProjectsFolderName, unscopedTempProjectName ); diff --git a/libraries/rush-lib/src/logic/UnlinkManager.ts b/libraries/rush-lib/src/logic/UnlinkManager.ts index 99afe360ae5..fa0c269baff 100644 --- a/libraries/rush-lib/src/logic/UnlinkManager.ts +++ b/libraries/rush-lib/src/logic/UnlinkManager.ts @@ -40,7 +40,7 @@ export class UnlinkManager { throw new AlreadyReportedError(); } - LastLinkFlagFactory.getCommonTempFlag(this._rushConfiguration).clear(); + LastLinkFlagFactory.getCommonTempFlag(this._rushConfiguration.defaultSubspace).clear(); return this._deleteProjectFiles(); } diff --git a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts index b9683bde66e..be4d61f7af7 100644 --- a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts @@ -45,6 +45,8 @@ import type { IInstallManagerOptions } from './BaseInstallManagerTypes'; import { isVariableSetInNpmrcFile } from '../../utilities/npmrcUtilities'; import type { PnpmResolutionMode } from '../pnpm/PnpmOptionsConfiguration'; import { SubspacePnpmfileConfiguration } from '../pnpm/SubspacePnpmfileConfiguration'; +import type { Subspace } from '../../api/Subspace'; +import type { CommonVersionsConfiguration } from '../../api/CommonVersionsConfiguration'; /** * Pnpm don't support --ignore-compatibility-db, so use --config.ignoreCompatibilityDb for now. @@ -84,17 +86,15 @@ export abstract class BaseInstallManager { this.installRecycler = purgeManager.commonTempFolderRecycler; this.options = options; - this._commonTempLinkFlag = LastLinkFlagFactory.getCommonTempFlag( - rushConfiguration, - this.options.subspaceName - ); + const mainSubspace: Subspace = options.selectedSubspace ?? rushConfiguration.defaultSubspace; + this._commonTempLinkFlag = LastLinkFlagFactory.getCommonTempFlag(mainSubspace); this.subspaceInstallFlags = new Map(); - if (rushConfiguration.subspacesConfiguration?.enabled) { - for (const subspaceName of rushConfiguration.subspaceNames) { + if (rushConfiguration.subspacesFeatureEnabled) { + for (const subspace of rushConfiguration.subspaces) { this.subspaceInstallFlags.set( - subspaceName, - LastInstallFlagFactory.getCommonTempFlag(rushConfiguration, subspaceName) + subspace.subspaceName, + LastInstallFlagFactory.getCommonTempFlag(rushConfiguration, subspace) ); } } @@ -103,10 +103,6 @@ export abstract class BaseInstallManager { this._terminal = new Terminal(this._terminalProvider); } - protected subspaceTempInstallFlag(subspaceName: string): LastInstallFlag | undefined { - return this.subspaceInstallFlags.get(subspaceName); - } - public async doInstallAsync(): Promise { const isFilteredInstall: boolean = this.options.pnpmFilterArguments.length > 0; const useWorkspaces: boolean = @@ -126,38 +122,35 @@ export abstract class BaseInstallManager { } // Ensure that subspaces is enabled - const subspaceName: string | undefined = this.options.subspaceName; - if (this.rushConfiguration.subspacesConfiguration?.enabled && !this.options.subspaceName) { + if (this.rushConfiguration.subspacesFeatureEnabled && !this.options.selectedSubspace) { // Temporarily ensure that a subspace is provided // eslint-disable-next-line no-console console.log(); // eslint-disable-next-line no-console console.log( colors.red( - `The subspaces feature currently only supports installing for a specified set of subspace, passed by the "--subspace" parameter or selected from targeted projects using any project selector.` + `The subspaces feature currently only supports installing for a specified set of subspace,` + + ` passed by the "--subspace" parameter or selected from targeted projects using any project selector.` + ) + ); + throw new AlreadyReportedError(); + } else if (this.options.selectedSubspace && !this.rushConfiguration.subspacesFeatureEnabled) { + // eslint-disable-next-line no-console + console.log(); + // eslint-disable-next-line no-console + console.log( + colors.red( + `The "--subspace" parameter can only be passed if the "enabled" option is enabled in subspaces.json.` ) ); throw new AlreadyReportedError(); - } else if (this.options.subspaceName && !this.rushConfiguration.subspacesConfiguration?.enabled) { - // Ensure that subspaces is enabled - if (!this.rushConfiguration.subspacesConfiguration?.enabled) { - // eslint-disable-next-line no-console - console.log(); - // eslint-disable-next-line no-console - console.log( - colors.red( - `A subspace parameter can only be passed if the "enabled" option is enabled in subspaces.json.` - ) - ); - throw new AlreadyReportedError(); - } } // Prevent update when using a filter, as modifications to the shrinkwrap shouldn't be saved if (this.options.allowShrinkwrapUpdates && isFilteredInstall) { // Allow partial update when there are subspace projects - if (!this.options.subspaceName) { + if (!this.options.selectedSubspace) { // eslint-disable-next-line no-console console.log(); // eslint-disable-next-line no-console @@ -171,17 +164,16 @@ export abstract class BaseInstallManager { } } - const { shrinkwrapIsUpToDate, variantIsUpToDate, npmrcHash } = await this.prepareAsync(subspaceName); + const subspace: Subspace = this.options.selectedSubspace ?? this.rushConfiguration.defaultSubspace; + + const { shrinkwrapIsUpToDate, variantIsUpToDate, npmrcHash } = await this.prepareAsync(subspace); if (this.options.checkOnly) { return; } // eslint-disable-next-line no-console - console.log( - '\n' + - colors.bold(`Checking installation in "${this.rushConfiguration.getCommonTempFolder(subspaceName)}"`) - ); + console.log('\n' + colors.bold(`Checking installation in "${subspace.getSubspaceTempFolder()}"`)); // This marker file indicates that the last "rush install" completed successfully. // Always perform a clean install if filter flags were provided. Additionally, if @@ -189,7 +181,7 @@ export abstract class BaseInstallManager { // need to perform a clean install. Otherwise, we can do an incremental install. const commonTempInstallFlag: LastInstallFlag = LastInstallFlagFactory.getCommonTempFlag( this.rushConfiguration, - subspaceName, + subspace, { npmrcHash: npmrcHash || '' } ); const optionsToIgnore: string[] | undefined = !this.rushConfiguration.experimentsConfiguration @@ -207,7 +199,7 @@ export abstract class BaseInstallManager { const canSkipInstall: () => boolean = () => { // Based on timestamps, can we skip this install entirely? const outputStats: FileSystemStats = FileSystem.getStatistics(commonTempInstallFlag.path); - return this.canSkipInstall(outputStats.mtime, subspaceName); + return this.canSkipInstall(outputStats.mtime, subspace); }; if (cleanInstall || !shrinkwrapIsUpToDate || !variantIsUpToDate || !canSkipInstall()) { @@ -243,21 +235,23 @@ export abstract class BaseInstallManager { } // Perform the actual install - await this.installAsync(cleanInstall, subspaceName); + await this.installAsync(cleanInstall, subspace); if (this.options.allowShrinkwrapUpdates && !shrinkwrapIsUpToDate) { // Copy (or delete) common\temp\pnpm-lock.yaml --> common\config\rush\pnpm-lock.yaml - Utilities.syncFile( - this.rushConfiguration.getTempShrinkwrapFilename(subspaceName), - this.rushConfiguration.getCommittedShrinkwrapFilename(subspaceName) - ); + Utilities.syncFile(subspace.getTempShrinkwrapFilename(), subspace.getCommittedShrinkwrapFilename()); } else { // TODO: Validate whether the package manager updated it in a nontrivial way } // Always update the state file if running "rush update" if (this.options.allowShrinkwrapUpdates) { - if (this.rushConfiguration.getRepoState(subspaceName).refreshState(this.rushConfiguration)) { + // Currently, only support saving the preferred versions hash if using workspaces + const commonVersions: CommonVersionsConfiguration | undefined = useWorkspaces + ? subspace.getCommonVersions() + : undefined; + + if (subspace.getRepoState().refreshState(this.rushConfiguration, commonVersions)) { // eslint-disable-next-line no-console console.log( colors.yellow( @@ -277,41 +271,41 @@ export abstract class BaseInstallManager { } // Perform any post-install work the install manager requires - await this.postInstallAsync(subspaceName); + await this.postInstallAsync(subspace); // eslint-disable-next-line no-console console.log(''); } protected abstract prepareCommonTempAsync( - subspaceName: string | undefined, + subspace: Subspace, shrinkwrapFile: BaseShrinkwrapFile | undefined ): Promise<{ shrinkwrapIsUpToDate: boolean; shrinkwrapWarnings: string[] }>; - protected abstract installAsync(cleanInstall: boolean, subspaceName: string | undefined): Promise; + protected abstract installAsync(cleanInstall: boolean, subspace: Subspace): Promise; - protected abstract postInstallAsync(subspaceName: string | undefined): Promise; + protected abstract postInstallAsync(subspace: Subspace): Promise; - protected canSkipInstall(lastModifiedDate: Date, subspaceName: string | undefined): boolean { + protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace): boolean { // Based on timestamps, can we skip this install entirely? const potentiallyChangedFiles: string[] = []; // Consider the timestamp on the node_modules folder; if someone tampered with it // or deleted it entirely, then we can't skip this install potentiallyChangedFiles.push( - path.join(this.rushConfiguration.getCommonTempFolder(subspaceName), RushConstants.nodeModulesFolderName) + path.join(subspace.getSubspaceTempFolder(), RushConstants.nodeModulesFolderName) ); // Additionally, if they pulled an updated shrinkwrap file from Git, // then we can't skip this install - potentiallyChangedFiles.push(this.rushConfiguration.getCommittedShrinkwrapFilename(subspaceName)); + potentiallyChangedFiles.push(subspace.getCommittedShrinkwrapFilename()); // Add common-versions.json file to the potentially changed files list. - potentiallyChangedFiles.push(this.rushConfiguration.getCommonVersionsFilePath(subspaceName)); + potentiallyChangedFiles.push(subspace.getCommonVersionsFilePath()); if (this.rushConfiguration.packageManager === 'pnpm') { // If the repo is using pnpmfile.js, consider that also - const pnpmFileFilename: string = this.rushConfiguration.getPnpmfilePath(subspaceName); + const pnpmFileFilename: string = subspace.getPnpmfilePath(); if (FileSystem.exists(pnpmFileFilename)) { potentiallyChangedFiles.push(pnpmFileFilename); @@ -321,13 +315,13 @@ export abstract class BaseInstallManager { return Utilities.isFileTimestampCurrent(lastModifiedDate, potentiallyChangedFiles); } - protected async prepareAsync(subspaceName?: string): Promise<{ + protected async prepareAsync(subspace: Subspace): Promise<{ variantIsUpToDate: boolean; shrinkwrapIsUpToDate: boolean; npmrcHash: string | undefined; }> { // Check the policies - await PolicyValidator.validatePolicyAsync(this.rushConfiguration, this.options, subspaceName); + await PolicyValidator.validatePolicyAsync(this.rushConfiguration, subspace, this.options); this._installGitHooks(); @@ -352,16 +346,14 @@ export abstract class BaseInstallManager { await InstallHelpers.ensureLocalPackageManager( this.rushConfiguration, this.rushGlobalFolder, - this.options.maxInstallAttempts, - subspaceName + this.options.maxInstallAttempts ); let shrinkwrapFile: BaseShrinkwrapFile | undefined = undefined; // (If it's a full update, then we ignore the shrinkwrap from Git since it will be overwritten) if (!this.options.fullUpgrade) { - const commitedShrinkwrapFileName: string = - this.rushConfiguration.getCommittedShrinkwrapFilename(subspaceName); + const commitedShrinkwrapFileName: string = subspace.getCommittedShrinkwrapFilename(); try { shrinkwrapFile = ShrinkwrapFileFactory.getShrinkwrapFile( this.rushConfiguration.packageManager, @@ -413,18 +405,19 @@ export abstract class BaseInstallManager { console.log(colors.bold('Using the default variant for installation.')); } - const subspaceEnvironmentVariable: string = `_RUSH_SUBSPACE_${subspaceName}_TEMP_FOLDER`; + const subspaceNameAllCaps: string = subspace.subspaceName.toUpperCase(); + const subspaceEnvironmentVariable: string = `_RUSH_SUBSPACE_${subspaceNameAllCaps}_TEMP_FOLDER`; const extraNpmrcLines: string[] = []; - if (subspaceName) { + if (this.options.selectedSubspace) { // Look for a global .npmrc-global file - const globalNpmrcPath: string = `${this.rushConfiguration.getCommonRushConfigFolder()}/.npmrc-global`; + const globalNpmrcPath: string = `${subspace.getSubspaceConfigFolder()}/.npmrc-global`; if (FileSystem.exists(globalNpmrcPath)) { const globalNpmrcFileLines: string[] = FileSystem.readFile(globalNpmrcPath).toString().split('\n'); extraNpmrcLines.push(...globalNpmrcFileLines); } // _RUSH_SUBSPACE_TEMP_FOLDER is used in .npmrc for subspaces. - process.env[subspaceEnvironmentVariable] = this.rushConfiguration.getCommonTempFolder(subspaceName); + process.env[subspaceEnvironmentVariable] = subspace.getSubspaceTempFolder(); extraNpmrcLines.push( `global-pnpmfile=\${${subspaceEnvironmentVariable}}/${RushConstants.pnpmfileGlobalFilename}` ); @@ -434,8 +427,8 @@ export abstract class BaseInstallManager { // "common\config\rush\.npmrc" --> "common\temp\.npmrc" // Also ensure that we remove any old one that may be hanging around const npmrcText: string | undefined = Utilities.syncNpmrc( - this.rushConfiguration.getCommonRushConfigFolder(subspaceName), - this.rushConfiguration.getCommonTempFolder(subspaceName), + subspace.getSubspaceConfigFolder(), + subspace.getSubspaceTempFolder(), undefined, undefined, extraNpmrcLines @@ -448,9 +441,9 @@ export abstract class BaseInstallManager { // Copy the committed patches folder if using pnpm if (this.rushConfiguration.packageManager === 'pnpm') { - const commonTempPnpmPatchesFolder: string = `${this.rushConfiguration.getCommonTempFolder( - subspaceName - )}/${RushConstants.pnpmPatchesFolderName}`; + const commonTempPnpmPatchesFolder: string = `${subspace.getSubspaceTempFolder()}/${ + RushConstants.pnpmPatchesFolderName + }`; const rushPnpmPatchesFolder: string = `${this.rushConfiguration.commonFolder}/pnpm-${RushConstants.pnpmPatchesFolderName}`; if (FileSystem.exists(rushPnpmPatchesFolder)) { FileSystem.copyFiles({ @@ -465,15 +458,15 @@ export abstract class BaseInstallManager { if (this.rushConfiguration.packageManager === 'pnpm') { await PnpmfileConfiguration.writeCommonTempPnpmfileShimAsync( this.rushConfiguration, - this.rushConfiguration.getCommonTempFolder(subspaceName), - subspaceName, + subspace.getSubspaceTempFolder(), + subspace, this.options ); - if (subspaceName) { + if (this.options.selectedSubspace) { await SubspacePnpmfileConfiguration.writeCommonTempSubspaceGlobalPnpmfileAsync( this.rushConfiguration, - subspaceName + subspace ); } } @@ -481,12 +474,12 @@ export abstract class BaseInstallManager { // Allow for package managers to do their own preparation and check that the shrinkwrap is up to date // eslint-disable-next-line prefer-const let { shrinkwrapIsUpToDate, shrinkwrapWarnings } = await this.prepareCommonTempAsync( - subspaceName, + subspace, shrinkwrapFile ); shrinkwrapIsUpToDate = shrinkwrapIsUpToDate && !this.options.recheckShrinkwrap; - this._syncTempShrinkwrap(subspaceName, shrinkwrapFile); + this._syncTempShrinkwrap(subspace, shrinkwrapFile); // Write out the reported warnings if (shrinkwrapWarnings.length > 0) { @@ -644,7 +637,7 @@ ${gitLfsHookHandling} * Used when invoking the NPM tool. Appends the common configuration options * to the command-line. */ - protected pushConfigurationArgs(args: string[], options: IInstallManagerOptions): void { + protected pushConfigurationArgs(args: string[], options: IInstallManagerOptions, subspace: Subspace): void { if (options.offline && this.rushConfiguration.packageManager !== 'pnpm') { throw new Error('The "--offline" parameter is only supported when using the PNPM package manager.'); } @@ -746,7 +739,7 @@ ${gitLfsHookHandling} If user does not set auto-install-peers in both pnpm-config.json and .npmrc, rush will default it to "false" */ const isAutoInstallPeersInNpmrc: boolean = isVariableSetInNpmrcFile( - this.rushConfiguration.getCommonRushConfigFolder(), + subspace.getSubspaceConfigFolder(), 'auto-install-peers' ); @@ -774,7 +767,7 @@ ${gitLfsHookHandling} If user does not set resolution-mode in pnpm-config.json and .npmrc, rush will default it to "highest" */ const isResolutionModeInNpmrc: boolean = isVariableSetInNpmrcFile( - this.rushConfiguration.getCommonRushConfigFolder(), + subspace.getSubspaceConfigFolder(), 'resolution-mode' ); @@ -936,24 +929,14 @@ ${gitLfsHookHandling} return true; } - private _syncTempShrinkwrap( - subspaceName: string | undefined, - shrinkwrapFile: BaseShrinkwrapFile | undefined - ): void { - const commitedShrinkwrapFileName: string = - this.rushConfiguration.getCommittedShrinkwrapFilename(subspaceName); + private _syncTempShrinkwrap(subspace: Subspace, shrinkwrapFile: BaseShrinkwrapFile | undefined): void { + const commitedShrinkwrapFileName: string = subspace.getCommittedShrinkwrapFilename(); if (shrinkwrapFile) { - Utilities.syncFile( - commitedShrinkwrapFileName, - this.rushConfiguration.getTempShrinkwrapFilename(subspaceName) - ); - Utilities.syncFile( - commitedShrinkwrapFileName, - this.rushConfiguration.getTempShrinkwrapPreinstallFilename(subspaceName) - ); + Utilities.syncFile(commitedShrinkwrapFileName, subspace.getTempShrinkwrapFilename()); + Utilities.syncFile(commitedShrinkwrapFileName, subspace.getTempShrinkwrapPreinstallFilename()); } else { // Otherwise delete the temporary file - FileSystem.deleteFile(this.rushConfiguration.getTempShrinkwrapFilename(subspaceName)); + FileSystem.deleteFile(subspace.getTempShrinkwrapFilename()); if (this.rushConfiguration.packageManager === 'pnpm') { // Workaround for https://github.com/pnpm/pnpm/issues/1890 @@ -966,10 +949,7 @@ ${gitLfsHookHandling} .packageManagerWrapper as PnpmPackageManager; FileSystem.deleteFile( - path.join( - this.rushConfiguration.getCommonTempFolder(subspaceName), - pnpmPackageManager.internalShrinkwrapRelativePath - ) + path.join(subspace.getSubspaceTempFolder(), pnpmPackageManager.internalShrinkwrapRelativePath) ); } } diff --git a/libraries/rush-lib/src/logic/base/BaseInstallManagerTypes.ts b/libraries/rush-lib/src/logic/base/BaseInstallManagerTypes.ts index 41b6a6d9479..050cf7973c7 100644 --- a/libraries/rush-lib/src/logic/base/BaseInstallManagerTypes.ts +++ b/libraries/rush-lib/src/logic/base/BaseInstallManagerTypes.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import type { Subspace } from '../../api/Subspace'; + export interface IInstallManagerOptions { /** * Whether the global "--debug" flag was specified. @@ -94,7 +96,8 @@ export interface IInstallManagerOptions { beforeInstallAsync?: () => Promise; /** - * The subspace to install for + * The specific subspace to install. If `undefined`, then the `--subspace parameter was not supplied + * on the command line. */ - subspaceName?: string; + selectedSubspace: Subspace | undefined; } diff --git a/libraries/rush-lib/src/logic/base/BaseLinkManager.ts b/libraries/rush-lib/src/logic/base/BaseLinkManager.ts index 924b8a0bd9d..12ec22b8fae 100644 --- a/libraries/rush-lib/src/logic/base/BaseLinkManager.ts +++ b/libraries/rush-lib/src/logic/base/BaseLinkManager.ts @@ -189,15 +189,15 @@ export abstract class BaseLinkManager { * @param force - Normally the operation will be skipped if the links are already up to date; * if true, this option forces the links to be recreated. */ - public async createSymlinksForProjects(force: boolean, subspaceName?: string | undefined): Promise { + public async createSymlinksForProjects(force: boolean): Promise { // eslint-disable-next-line no-console console.log('\n' + colors.bold('Linking local projects')); const stopwatch: Stopwatch = Stopwatch.start(); - await this._linkProjects(subspaceName); + await this._linkProjects(); // TODO: Remove when "rush link" and "rush unlink" are deprecated - LastLinkFlagFactory.getCommonTempFlag(this._rushConfiguration).create(); + LastLinkFlagFactory.getCommonTempFlag(this._rushConfiguration.defaultSubspace).create(); stopwatch.stop(); // eslint-disable-next-line no-console @@ -206,5 +206,5 @@ export abstract class BaseLinkManager { console.log('\nNext you should probably run "rush build" or "rush rebuild"'); } - protected abstract _linkProjects(subspaceName: string | undefined): Promise; + protected abstract _linkProjects(): Promise; } diff --git a/libraries/rush-lib/src/logic/base/BaseShrinkwrapFile.ts b/libraries/rush-lib/src/logic/base/BaseShrinkwrapFile.ts index 731a9e67143..353529a8288 100644 --- a/libraries/rush-lib/src/logic/base/BaseShrinkwrapFile.ts +++ b/libraries/rush-lib/src/logic/base/BaseShrinkwrapFile.ts @@ -13,6 +13,7 @@ import type { IExperimentsJson } from '../../api/ExperimentsConfiguration'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; import type { BaseProjectShrinkwrapFile } from './BaseProjectShrinkwrapFile'; import type { PackageManagerOptionsConfigurationBase } from './BasePackageManagerOptionsConfiguration'; +import type { Subspace } from '../../api/Subspace'; /** * This class is a parser for both npm's npm-shrinkwrap.json and pnpm's pnpm-lock.yaml file formats. @@ -115,7 +116,7 @@ export abstract class BaseShrinkwrapFile { */ public findOrphanedProjects( rushConfiguration: RushConfiguration, - subspaceName: string | undefined + subspace: Subspace ): ReadonlyArray { const orphanedProjectNames: string[] = []; // We can recognize temp projects because they are under the "@rush-temp" NPM scope. @@ -148,7 +149,7 @@ export abstract class BaseShrinkwrapFile { */ public abstract isWorkspaceProjectModifiedAsync( project: RushConfigurationProject, - subspaceName: string | undefined, + subspace: Subspace, variant?: string ): Promise; diff --git a/libraries/rush-lib/src/logic/buildCache/FileSystemBuildCacheProvider.ts b/libraries/rush-lib/src/logic/buildCache/FileSystemBuildCacheProvider.ts index 72c16d7626e..35df3b6896d 100644 --- a/libraries/rush-lib/src/logic/buildCache/FileSystemBuildCacheProvider.ts +++ b/libraries/rush-lib/src/logic/buildCache/FileSystemBuildCacheProvider.ts @@ -35,7 +35,7 @@ export class FileSystemBuildCacheProvider { public constructor(options: IFileSystemBuildCacheProviderOptions) { this._cacheFolderPath = options.rushUserConfiguration.buildCacheFolder || - path.join(options.rushConfiguration.getCommonTempFolder(), DEFAULT_BUILD_CACHE_FOLDER_NAME); + path.join(options.rushConfiguration.commonTempFolder, DEFAULT_BUILD_CACHE_FOLDER_NAME); } /** diff --git a/libraries/rush-lib/src/logic/deploy/DeployScenarioConfiguration.ts b/libraries/rush-lib/src/logic/deploy/DeployScenarioConfiguration.ts index 23a920f8c04..3266f321241 100644 --- a/libraries/rush-lib/src/logic/deploy/DeployScenarioConfiguration.ts +++ b/libraries/rush-lib/src/logic/deploy/DeployScenarioConfiguration.ts @@ -92,7 +92,7 @@ export class DeployScenarioConfiguration { scenarioFileName = `deploy.json`; } - return path.join(rushConfiguration.getCommonRushConfigFolder(), scenarioFileName); + return path.join(rushConfiguration.commonRushConfigFolder, scenarioFileName); } public static loadFromFile( diff --git a/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts b/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts index f1bbed698b5..f8dade3d5ed 100644 --- a/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts +++ b/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts @@ -19,6 +19,7 @@ import { Utilities } from '../../utilities/Utilities'; import type { IConfigurationEnvironment } from '../base/BasePackageManagerOptionsConfiguration'; import type { PnpmOptionsConfiguration } from '../pnpm/PnpmOptionsConfiguration'; import { merge } from '../../utilities/objectUtilities'; +import type { Subspace } from '../../api/Subspace'; interface ICommonPackageJson extends IPackageJson { pnpm?: { @@ -34,8 +35,8 @@ interface ICommonPackageJson extends IPackageJson { export class InstallHelpers { public static generateCommonPackageJson( rushConfiguration: RushConfiguration, - dependencies: Map = new Map(), - subspaceName: string | undefined + subspace: Subspace, + dependencies: Map = new Map() ): void { const commonPackageJson: ICommonPackageJson = { dependencies: {}, @@ -87,7 +88,7 @@ export class InstallHelpers { // Example: "C:\MyRepo\common\temp\package.json" const commonPackageJsonFilename: string = path.join( - rushConfiguration.getCommonTempFolder(subspaceName), + subspace.getSubspaceTempFolder(), FileConstants.PackageJson ); @@ -129,7 +130,6 @@ export class InstallHelpers { rushConfiguration: RushConfiguration, rushGlobalFolder: RushGlobalFolder, maxInstallAttempts: number, - subspaceName?: string | undefined, restrictConsoleOutput?: boolean ): Promise { let logIfConsoleOutputIsNotRestricted: (message?: string) => void; @@ -187,7 +187,7 @@ export class InstallHelpers { // In particular, we'll assume that two different NPM registries cannot have two // different implementations of the same version of the same package. // This was needed for: https://github.com/microsoft/rushstack/issues/691 - commonRushConfigFolder: rushConfiguration.getCommonRushConfigFolder(subspaceName) + commonRushConfigFolder: rushConfiguration.commonRushConfigFolder }); logIfConsoleOutputIsNotRestricted( @@ -202,11 +202,11 @@ export class InstallHelpers { packageManagerMarker.create(); // Example: "C:\MyRepo\common\temp" - FileSystem.ensureFolder(rushConfiguration.getCommonTempFolder(subspaceName)); + FileSystem.ensureFolder(rushConfiguration.commonTempFolder); // Example: "C:\MyRepo\common\temp\pnpm-local" const localPackageManagerToolFolder: string = path.join( - rushConfiguration.getCommonTempFolder(subspaceName), + rushConfiguration.commonTempFolder, `${packageManager}-local` ); diff --git a/libraries/rush-lib/src/logic/installManager/RushInstallManager.ts b/libraries/rush-lib/src/logic/installManager/RushInstallManager.ts index 84a836d8044..e6eb78769ce 100644 --- a/libraries/rush-lib/src/logic/installManager/RushInstallManager.ts +++ b/libraries/rush-lib/src/logic/installManager/RushInstallManager.ts @@ -39,6 +39,7 @@ import type { PurgeManager } from '../PurgeManager'; import { LinkManagerFactory } from '../LinkManagerFactory'; import type { BaseLinkManager } from '../base/BaseLinkManager'; import type { PnpmShrinkwrapFile, IPnpmShrinkwrapDependencyYaml } from '../pnpm/PnpmShrinkwrapFile'; +import type { Subspace } from '../../api/Subspace'; const globEscape: (unescaped: string) => string = require('glob-escape'); // No @types/glob-escape package exists @@ -70,7 +71,10 @@ export class RushInstallManager extends BaseInstallManager { options: IInstallManagerOptions ) { super(rushConfiguration, rushGlobalFolder, purgeManager, options); - this._tempProjectHelper = new TempProjectHelper(this.rushConfiguration); + this._tempProjectHelper = new TempProjectHelper( + this.rushConfiguration, + rushConfiguration.defaultSubspace + ); } /** @@ -82,14 +86,14 @@ export class RushInstallManager extends BaseInstallManager { * @override */ public async prepareCommonTempAsync( - subspaceName: string | undefined, + subspace: Subspace, shrinkwrapFile: BaseShrinkwrapFile | undefined ): Promise<{ shrinkwrapIsUpToDate: boolean; shrinkwrapWarnings: string[] }> { const stopwatch: Stopwatch = Stopwatch.start(); // Example: "C:\MyRepo\common\temp\projects" const tempProjectsFolder: string = path.join( - this.rushConfiguration.getCommonTempFolder(subspaceName), + this.rushConfiguration.commonTempFolder, RushConstants.rushTempProjectsFolderName ); @@ -120,8 +124,8 @@ export class RushInstallManager extends BaseInstallManager { } // dependency name --> version specifier - const allExplicitPreferredVersions: Map = this.rushConfiguration - .getCommonVersions(subspaceName) + const allExplicitPreferredVersions: Map = this.rushConfiguration.defaultSubspace + .getCommonVersions() .getAllPreferredVersions(); if (shrinkwrapFile) { @@ -147,7 +151,7 @@ export class RushInstallManager extends BaseInstallManager { // If there are orphaned projects, we need to update const orphanedProjects: ReadonlyArray = shrinkwrapFile.findOrphanedProjects( this.rushConfiguration, - subspaceName + this.rushConfiguration.defaultSubspace ); if (orphanedProjects.length > 0) { @@ -176,7 +180,7 @@ export class RushInstallManager extends BaseInstallManager { const packageJson: PackageJsonEditor = rushProject.packageJsonEditor; // Example: "C:\MyRepo\common\temp\projects\my-project-2.tgz" - const tarballFile: string = this._tempProjectHelper.getTarballFilePath(rushProject, subspaceName); + const tarballFile: string = this._tempProjectHelper.getTarballFilePath(rushProject); // Example: dependencies["@rush-temp/my-project-2"] = "file:./projects/my-project-2.tgz" commonDependencies.set( @@ -273,10 +277,7 @@ export class RushInstallManager extends BaseInstallManager { } // Example: "C:\MyRepo\common\temp\projects\my-project-2" - const tempProjectFolder: string = this._tempProjectHelper.getTempProjectFolder( - rushProject, - subspaceName - ); + const tempProjectFolder: string = this._tempProjectHelper.getTempProjectFolder(rushProject); // Example: "C:\MyRepo\common\temp\projects\my-project-2\package.json" const tempPackageJsonFilename: string = path.join(tempProjectFolder, FileConstants.PackageJson); @@ -313,7 +314,7 @@ export class RushInstallManager extends BaseInstallManager { JsonFile.save(tempPackageJson, tempPackageJsonFilename); // Delete the existing tarball and create a new one - this._tempProjectHelper.createTempProjectTarball(rushProject, subspaceName); + this._tempProjectHelper.createTempProjectTarball(rushProject); // eslint-disable-next-line no-console console.log(`Updating ${tarballFile}`); @@ -336,8 +337,7 @@ export class RushInstallManager extends BaseInstallManager { const pnpmShrinkwrapFile: PnpmShrinkwrapFile = shrinkwrapFile as PnpmShrinkwrapFile; const tarballIntegrityValid: boolean = await this._validateRushProjectTarballIntegrityAsync( pnpmShrinkwrapFile, - rushProject, - subspaceName + rushProject ); if (!tarballIntegrityValid) { shrinkwrapIsUpToDate = false; @@ -362,7 +362,7 @@ export class RushInstallManager extends BaseInstallManager { // Remove the workspace file if it exists if (this.rushConfiguration.packageManager === 'pnpm') { const workspaceFilePath: string = path.join( - this.rushConfiguration.getCommonTempFolder(), + this.rushConfiguration.commonTempFolder, 'pnpm-workspace.yaml' ); try { @@ -375,7 +375,11 @@ export class RushInstallManager extends BaseInstallManager { } // Write the common package.json - InstallHelpers.generateCommonPackageJson(this.rushConfiguration, commonDependencies, subspaceName); + InstallHelpers.generateCommonPackageJson( + this.rushConfiguration, + this.rushConfiguration.defaultSubspace, + commonDependencies + ); stopwatch.stop(); // eslint-disable-next-line no-console @@ -407,8 +411,7 @@ export class RushInstallManager extends BaseInstallManager { private async _validateRushProjectTarballIntegrityAsync( shrinkwrapFile: PnpmShrinkwrapFile | undefined, - rushProject: RushConfigurationProject, - subspaceName: string | undefined + rushProject: RushConfigurationProject ): Promise { if (shrinkwrapFile) { const tempProjectDependencyKey: string | undefined = shrinkwrapFile.getTempProjectDependencyKey( @@ -421,9 +424,7 @@ export class RushInstallManager extends BaseInstallManager { const parentShrinkwrapEntry: IPnpmShrinkwrapDependencyYaml = shrinkwrapFile.getShrinkwrapEntryFromTempProjectDependencyKey(tempProjectDependencyKey)!; const newIntegrity: string = ( - await ssri.fromStream( - fs.createReadStream(this._tempProjectHelper.getTarballFilePath(rushProject, subspaceName)) - ) + await ssri.fromStream(fs.createReadStream(this._tempProjectHelper.getTarballFilePath(rushProject))) ).toString(); if (!parentShrinkwrapEntry.resolution || parentShrinkwrapEntry.resolution.integrity !== newIntegrity) { @@ -438,8 +439,8 @@ export class RushInstallManager extends BaseInstallManager { * * @override */ - protected canSkipInstall(lastModifiedDate: Date, subspaceName: string | undefined): boolean { - if (!super.canSkipInstall(lastModifiedDate, subspaceName)) { + protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace): boolean { + if (!super.canSkipInstall(lastModifiedDate, subspace)) { return false; } @@ -450,7 +451,7 @@ export class RushInstallManager extends BaseInstallManager { // Example: "C:\MyRepo\common\temp\projects\my-project-2.tgz" potentiallyChangedFiles.push( ...this.rushConfiguration.projects.map((x) => { - return this._tempProjectHelper.getTarballFilePath(x, subspaceName); + return this._tempProjectHelper.getTarballFilePath(x); }) ); @@ -462,12 +463,12 @@ export class RushInstallManager extends BaseInstallManager { * * @override */ - protected async installAsync(cleanInstall: boolean, subspaceName: string | undefined): Promise { + protected async installAsync(cleanInstall: boolean, subspace: Subspace): Promise { // Since we are actually running npm/pnpm/yarn install, recreate all the temp project tarballs. // This ensures that any existing tarballs with older header bits will be regenerated. // It is safe to assume that temp project pacakge.jsons already exist. for (const rushProject of this.rushConfiguration.projects) { - this._tempProjectHelper.createTempProjectTarball(rushProject, subspaceName); + this._tempProjectHelper.createTempProjectTarball(rushProject); } // NOTE: The PNPM store is supposed to be transactionally safe, so we don't delete it automatically. @@ -494,7 +495,7 @@ export class RushInstallManager extends BaseInstallManager { ); const commonNodeModulesFolder: string = path.join( - this.rushConfiguration.getCommonTempFolder(subspaceName), + this.rushConfiguration.commonTempFolder, RushConstants.nodeModulesFolderName ); @@ -519,16 +520,16 @@ export class RushInstallManager extends BaseInstallManager { // eslint-disable-next-line no-console console.log( `Running "${this.rushConfiguration.packageManager} prune"` + - ` in ${this.rushConfiguration.getCommonTempFolder(subspaceName)}` + ` in ${this.rushConfiguration.commonTempFolder}` ); const args: string[] = ['prune']; - this.pushConfigurationArgs(args, this.options); + this.pushConfigurationArgs(args, this.options, subspace); Utilities.executeCommandWithRetry( { command: packageManagerFilename, args: args, - workingDirectory: this.rushConfiguration.getCommonTempFolder(subspaceName), + workingDirectory: this.rushConfiguration.commonTempFolder, environment: packageManagerEnv }, this.options.maxInstallAttempts @@ -580,14 +581,14 @@ export class RushInstallManager extends BaseInstallManager { // Run "npm install" in the common folder const installArgs: string[] = ['install']; - this.pushConfigurationArgs(installArgs, this.options); + this.pushConfigurationArgs(installArgs, this.options, subspace); // eslint-disable-next-line no-console console.log( '\n' + colors.bold( `Running "${this.rushConfiguration.packageManager} install" in` + - ` ${this.rushConfiguration.getCommonTempFolder(subspaceName)}` + ` ${this.rushConfiguration.commonTempFolder}` ) + '\n' ); @@ -609,7 +610,7 @@ export class RushInstallManager extends BaseInstallManager { { command: packageManagerFilename, args: installArgs, - workingDirectory: this.rushConfiguration.getCommonTempFolder(subspaceName), + workingDirectory: this.rushConfiguration.commonTempFolder, environment: packageManagerEnv, suppressOutput: false }, @@ -633,23 +634,23 @@ export class RushInstallManager extends BaseInstallManager { // eslint-disable-next-line no-console console.log('\n' + colors.bold('Running "npm shrinkwrap"...')); const npmArgs: string[] = ['shrinkwrap']; - this.pushConfigurationArgs(npmArgs, this.options); + this.pushConfigurationArgs(npmArgs, this.options, subspace); Utilities.executeCommand({ command: this.rushConfiguration.packageManagerToolFilename, args: npmArgs, - workingDirectory: this.rushConfiguration.getCommonTempFolder(subspaceName) + workingDirectory: this.rushConfiguration.commonTempFolder }); // eslint-disable-next-line no-console console.log('"npm shrinkwrap" completed\n'); - await this._fixupNpm5RegressionAsync(subspaceName); + await this._fixupNpm5RegressionAsync(); } } - protected async postInstallAsync(subspaceName: string | undefined): Promise { + protected async postInstallAsync(subspace: Subspace): Promise { if (!this.options.noLink) { const linkManager: BaseLinkManager = LinkManagerFactory.getLinkManager(this.rushConfiguration); - await linkManager.createSymlinksForProjects(false, subspaceName); + await linkManager.createSymlinksForProjects(false); } else { // eslint-disable-next-line no-console console.log( @@ -672,9 +673,9 @@ export class RushInstallManager extends BaseInstallManager { * Our workaround is to rewrite the package.json files for each of the @rush-temp projects * in the node_modules folder, after "npm install" completes. */ - private async _fixupNpm5RegressionAsync(subspaceName: string | undefined): Promise { + private async _fixupNpm5RegressionAsync(): Promise { const pathToDeleteWithoutStar: string = path.join( - this.rushConfiguration.getCommonTempFolder(subspaceName), + this.rushConfiguration.commonTempFolder, 'node_modules', RushConstants.rushTempNpmScope ); diff --git a/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts b/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts index 2c04aec7b32..a800c9d67b0 100644 --- a/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts +++ b/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts @@ -33,9 +33,9 @@ import { EnvironmentConfiguration } from '../../api/EnvironmentConfiguration'; import { ShrinkwrapFileFactory } from '../ShrinkwrapFileFactory'; import { BaseProjectShrinkwrapFile } from '../base/BaseProjectShrinkwrapFile'; import { type CustomTipId, type ICustomTipInfo, PNPM_CUSTOM_TIPS } from '../../api/CustomTipsConfiguration'; -import { SubspacesConfiguration } from '../../api/SubspacesConfiguration'; import type { PnpmShrinkwrapFile } from '../pnpm/PnpmShrinkwrapFile'; import { objectsAreDeepEqual } from '../../utilities/objectUtilities'; +import type { Subspace } from '../../api/Subspace'; /** * This class implements common logic between "rush install" and "rush update". @@ -69,7 +69,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { * @override */ protected async prepareCommonTempAsync( - subspaceName: string | undefined, + subspace: Subspace, shrinkwrapFile: (PnpmShrinkwrapFile & BaseShrinkwrapFile) | undefined ): Promise<{ shrinkwrapIsUpToDate: boolean; shrinkwrapWarnings: string[] }> { // Block use of the RUSH_TEMP_FOLDER environment variable @@ -81,10 +81,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { } // eslint-disable-next-line no-console - console.log( - '\n' + - colors.bold('Updating workspace files in ' + this.rushConfiguration.getCommonTempFolder(subspaceName)) - ); + console.log('\n' + colors.bold('Updating workspace files in ' + subspace.getSubspaceTempFolder())); const shrinkwrapWarnings: string[] = []; @@ -111,7 +108,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { // If there are orphaned projects, we need to update const orphanedProjects: ReadonlyArray = shrinkwrapFile.findOrphanedProjects( this.rushConfiguration, - subspaceName + subspace ); if (orphanedProjects.length > 0) { @@ -127,15 +124,14 @@ export class WorkspaceInstallManager extends BaseInstallManager { // If preferred versions have been updated, or if the repo-state.json is invalid, // we can't be certain of the state of the shrinkwrap - const repoState: RepoStateFile = this.rushConfiguration.getRepoState(subspaceName); + const repoState: RepoStateFile = subspace.getRepoState(); if (!repoState.isValid) { shrinkwrapWarnings.push( `The ${RushConstants.repoStateFilename} file is invalid. There may be a merge conflict marker in the file.` ); shrinkwrapIsUpToDate = false; } else { - const commonVersions: CommonVersionsConfiguration = - this.rushConfiguration.getCommonVersions(subspaceName); + const commonVersions: CommonVersionsConfiguration = subspace.getCommonVersions(); if (repoState.preferredVersionsHash !== commonVersions.getPreferredVersionsHash()) { shrinkwrapWarnings.push( `Preferred versions from ${RushConstants.commonVersionsFilename} have been modified.` @@ -146,13 +142,13 @@ export class WorkspaceInstallManager extends BaseInstallManager { // To generate the workspace file, we will add each project to the file as we loop through and validate const workspaceFile: PnpmWorkspaceFile = new PnpmWorkspaceFile( - path.join(this.rushConfiguration.getCommonTempFolder(subspaceName), 'pnpm-workspace.yaml') + path.join(subspace.getSubspaceTempFolder(), 'pnpm-workspace.yaml') ); // Loop through the projects and add them to the workspace file. While we're at it, also validate that // referenced workspace projects are valid, and check if the shrinkwrap file is already up-to-date. for (const rushProject of this.rushConfiguration.projects) { - if (subspaceName && !SubspacesConfiguration.belongsInSubspace(rushProject, subspaceName)) { + if (!subspace.contains(rushProject)) { // skip processing any project that isn't in this subspace continue; } @@ -250,7 +246,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { // Now validate that the shrinkwrap file matches what is in the package.json if ( - await shrinkwrapFile?.isWorkspaceProjectModifiedAsync(rushProject, subspaceName, this.options.variant) + await shrinkwrapFile?.isWorkspaceProjectModifiedAsync(rushProject, subspace, this.options.variant) ) { shrinkwrapWarnings.push( `Dependencies of project "${rushProject.packageName}" do not match the current shrinkwrap.` @@ -271,7 +267,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { // get the relative path from common temp folder to repo root folder const relativeFromTempFolderToRootFolder: string = path.relative(commonTempFolder, rushJsonFolder); for (const rushProject of this.rushConfiguration.projects) { - if (subspaceName && !SubspacesConfiguration.belongsInSubspace(rushProject, subspaceName)) { + if (subspace.contains(rushProject)) { // skip processing any project that isn't in this subspace continue; } @@ -314,7 +310,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { } // Write the common package.json - InstallHelpers.generateCommonPackageJson(this.rushConfiguration, undefined, subspaceName); + InstallHelpers.generateCommonPackageJson(this.rushConfiguration, subspace, undefined); // Save the generated workspace file. Don't update the file timestamp unless the content has changed, // since "rush install" will consider this timestamp @@ -323,8 +319,8 @@ export class WorkspaceInstallManager extends BaseInstallManager { return { shrinkwrapIsUpToDate, shrinkwrapWarnings }; } - protected canSkipInstall(lastModifiedDate: Date, subspaceName?: string | undefined): boolean { - if (!super.canSkipInstall(lastModifiedDate, subspaceName)) { + protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace): boolean { + if (!super.canSkipInstall(lastModifiedDate, subspace)) { return false; } @@ -333,7 +329,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { if (this.rushConfiguration.packageManager === 'pnpm') { // Add workspace file. This file is only modified when workspace packages change. const pnpmWorkspaceFilename: string = path.join( - this.rushConfiguration.getCommonTempFolder(subspaceName), + subspace.getSubspaceTempFolder(), 'pnpm-workspace.yaml' ); @@ -362,7 +358,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { /** * Runs "npm install" in the common folder. */ - protected async installAsync(cleanInstall: boolean, subspaceName: string | undefined): Promise { + protected async installAsync(cleanInstall: boolean, subspace: Subspace): Promise { // Example: "C:\MyRepo\common\temp\npm-local\node_modules\.bin\npm" const packageManagerFilename: string = this.rushConfiguration.packageManagerToolFilename; @@ -375,7 +371,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { } const commonNodeModulesFolder: string = path.join( - this.rushConfiguration.getCommonTempFolder(subspaceName), + subspace.getSubspaceTempFolder(), RushConstants.nodeModulesFolderName ); @@ -400,14 +396,14 @@ export class WorkspaceInstallManager extends BaseInstallManager { // To ensure that the output is always colored, set the option "--color=always", even when it's piped. // Without this argument, certain text that should be colored (such as red) will appear white. const installArgs: string[] = ['install']; - this.pushConfigurationArgs(installArgs, options); + this.pushConfigurationArgs(installArgs, options, subspace); // eslint-disable-next-line no-console console.log( '\n' + colors.bold( `Running "${this.rushConfiguration.packageManager} install" in` + - ` ${this.rushConfiguration.getCommonTempFolder(subspaceName)}` + ` ${subspace.getSubspaceTempFolder()}` ) + '\n' ); @@ -460,7 +456,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { { command: packageManagerFilename, args: installArgs, - workingDirectory: this.rushConfiguration.getCommonTempFolder(subspaceName), + workingDirectory: subspace.getSubspaceTempFolder(), environment: packageManagerEnv, suppressOutput: false }, @@ -515,10 +511,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { // Ensure that node_modules folders exist after install, since the timestamps on these folders are used // to determine if the install can be skipped const projectNodeModulesFolders: string[] = [ - path.join( - this.rushConfiguration.getCommonTempFolder(subspaceName), - RushConstants.nodeModulesFolderName - ), + path.join(subspace.getSubspaceTempFolder(), RushConstants.nodeModulesFolderName), ...this.rushConfiguration.projects.map((project) => { return path.join(project.projectFolder, RushConstants.nodeModulesFolderName); }) @@ -532,7 +525,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { console.log(''); } - protected async postInstallAsync(subspaceName?: string | undefined): Promise { + protected async postInstallAsync(subspace: Subspace): Promise { // Grab the temp shrinkwrap, as this was the most recently completed install. It may also be // more up-to-date than the checked-in shrinkwrap since filtered installs are not written back. // Note that if there are no projects, or if we're in PNPM workspace mode and there are no @@ -540,15 +533,13 @@ export class WorkspaceInstallManager extends BaseInstallManager { const tempShrinkwrapFile: BaseShrinkwrapFile | undefined = ShrinkwrapFileFactory.getShrinkwrapFile( this.rushConfiguration.packageManager, this.rushConfiguration.pnpmOptions, - this.rushConfiguration.getTempShrinkwrapFilename(subspaceName) + subspace.getTempShrinkwrapFilename() ); if (tempShrinkwrapFile) { // Write or delete all project shrinkwraps related to the install await Async.forEachAsync( - subspaceName - ? this.rushConfiguration.getSubspaceProjects(subspaceName) - : this.rushConfiguration.projects, + subspace.getProjects(), async (project) => { await tempShrinkwrapFile.getProjectShrinkwrap(project)?.updateProjectShrinkwrapAsync(); }, @@ -561,9 +552,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { // If we're in PNPM workspace mode and PNPM didn't create a shrinkwrap file, // there are no dependencies. Generate empty shrinkwrap files for all projects. await Async.forEachAsync( - subspaceName - ? this.rushConfiguration.getSubspaceProjects(subspaceName) - : this.rushConfiguration.projects, + subspace.getProjects(), async (project) => { await BaseProjectShrinkwrapFile.saveEmptyProjectShrinkwrapFileAsync(project); }, @@ -578,15 +567,15 @@ export class WorkspaceInstallManager extends BaseInstallManager { } // TODO: Remove when "rush link" and "rush unlink" are deprecated - LastLinkFlagFactory.getCommonTempFlag(this.rushConfiguration, subspaceName).create(); + LastLinkFlagFactory.getCommonTempFlag(subspace).create(); } /** * Used when invoking the NPM tool. Appends the common configuration options * to the command-line. */ - protected pushConfigurationArgs(args: string[], options: IInstallManagerOptions): void { - super.pushConfigurationArgs(args, options); + protected pushConfigurationArgs(args: string[], options: IInstallManagerOptions, subspace: Subspace): void { + super.pushConfigurationArgs(args, options, subspace); // Add workspace-specific args if (this.rushConfiguration.packageManager === 'pnpm') { diff --git a/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts b/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts index 056edd3d1ab..417bd30c010 100644 --- a/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts +++ b/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts @@ -42,7 +42,8 @@ export async function doBasicInstallAsync(options: IRunInstallOptions): Promise< collectLogFile: false, pnpmFilterArguments: [], maxInstallAttempts: 1, - networkConcurrency: undefined + networkConcurrency: undefined, + selectedSubspace: undefined } ); diff --git a/libraries/rush-lib/src/logic/npm/NpmLinkManager.ts b/libraries/rush-lib/src/logic/npm/NpmLinkManager.ts index cd8be8de94e..243b6865cec 100644 --- a/libraries/rush-lib/src/logic/npm/NpmLinkManager.ts +++ b/libraries/rush-lib/src/logic/npm/NpmLinkManager.ts @@ -28,12 +28,12 @@ interface IQueueItem { } export class NpmLinkManager extends BaseLinkManager { - protected async _linkProjects(subspaceName: string | undefined): Promise { + protected async _linkProjects(): Promise { const npmPackage: readPackageTree.Node = await LegacyAdapters.convertCallbackToPromise< readPackageTree.Node, Error, string - >(readPackageTree, this._rushConfiguration.getCommonTempFolder(subspaceName)); + >(readPackageTree, this._rushConfiguration.commonTempFolder); const commonRootPackage: NpmPackage = NpmPackage.createFromNpm(npmPackage); @@ -74,14 +74,14 @@ export class NpmLinkManager extends BaseLinkManager { // Example: "C:\MyRepo\common\temp\projects\project1 const extractedFolder: string = path.join( - this._rushConfiguration.getCommonTempFolder(), + this._rushConfiguration.commonTempFolder, RushConstants.rushTempProjectsFolderName, unscopedTempProjectName ); // Example: "C:\MyRepo\common\temp\projects\project1.tgz" const tarballFile: string = path.join( - this._rushConfiguration.getCommonTempFolder(), + this._rushConfiguration.commonTempFolder, RushConstants.rushTempProjectsFolderName, unscopedTempProjectName + '.tgz' ); @@ -98,7 +98,7 @@ export class NpmLinkManager extends BaseLinkManager { // Example: "C:\MyRepo\common\temp\node_modules\@rush-temp\project1" const installFolderName: string = path.join( - this._rushConfiguration.getCommonTempFolder(), + this._rushConfiguration.commonTempFolder, RushConstants.nodeModulesFolderName, RushConstants.rushTempNpmScope, unscopedTempProjectName @@ -305,7 +305,7 @@ export class NpmLinkManager extends BaseLinkManager { // Also symlink the ".bin" folder if (localProjectPackage.children.length > 0) { const commonBinFolder: string = path.join( - this._rushConfiguration.getCommonTempFolder(), + this._rushConfiguration.commonTempFolder, 'node_modules', '.bin' ); diff --git a/libraries/rush-lib/src/logic/npm/NpmShrinkwrapFile.ts b/libraries/rush-lib/src/logic/npm/NpmShrinkwrapFile.ts index 59bcd63e5c1..4a8d2292aee 100644 --- a/libraries/rush-lib/src/logic/npm/NpmShrinkwrapFile.ts +++ b/libraries/rush-lib/src/logic/npm/NpmShrinkwrapFile.ts @@ -7,6 +7,7 @@ import { BaseShrinkwrapFile } from '../base/BaseShrinkwrapFile'; import { DependencySpecifier } from '../DependencySpecifier'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; import type { BaseProjectShrinkwrapFile } from '../base/BaseProjectShrinkwrapFile'; +import type { Subspace } from '../../api/Subspace'; interface INpmShrinkwrapDependencyJson { version: string; @@ -133,6 +134,7 @@ export class NpmShrinkwrapFile extends BaseShrinkwrapFile { /** @override */ public async isWorkspaceProjectModifiedAsync( project: RushConfigurationProject, + subspace: Subspace, variant?: string ): Promise { throw new InternalError('Not implemented'); diff --git a/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts b/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts index 2d49b413db5..bb1747bf929 100644 --- a/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts +++ b/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts @@ -144,7 +144,7 @@ export class ShellOperationRunner implements IOperationRunner { { rushConfiguration: this._rushConfiguration, workingDirectory: projectFolder, - initCwd: this._rushConfiguration.getCommonTempFolder(), + initCwd: this._rushConfiguration.commonTempFolder, handleOutput: true, environmentPathOptions: { includeProjectBin: true diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts b/libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts index 2401dc10c5f..b8a80a967df 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts @@ -39,7 +39,7 @@ export class PnpmLinkManager extends BaseLinkManager { /** * @override */ - public async createSymlinksForProjects(force: boolean, subspaceName?: string | undefined): Promise { + public async createSymlinksForProjects(force: boolean): Promise { const useWorkspaces: boolean = this._rushConfiguration.pnpmOptions && this._rushConfiguration.pnpmOptions.useWorkspaces; if (useWorkspaces) { @@ -53,25 +53,25 @@ export class PnpmLinkManager extends BaseLinkManager { throw new AlreadyReportedError(); } - await super.createSymlinksForProjects(force, subspaceName); + await super.createSymlinksForProjects(force); } - protected async _linkProjects(subspaceName: string | undefined): Promise { + protected async _linkProjects(): Promise { if (this._rushConfiguration.projects.length > 0) { // Use shrinkwrap from temp as the committed shrinkwrap may not always be up to date // See https://github.com/microsoft/rushstack/issues/1273#issuecomment-492779995 const pnpmShrinkwrapFile: PnpmShrinkwrapFile | undefined = PnpmShrinkwrapFile.loadFromFile( - this._rushConfiguration.getTempShrinkwrapFilename(subspaceName) + this._rushConfiguration.defaultSubspace.getTempShrinkwrapFilename() ); if (!pnpmShrinkwrapFile) { throw new InternalError( - `Cannot load shrinkwrap at "${this._rushConfiguration.getTempShrinkwrapFilename(subspaceName)}"` + `Cannot load shrinkwrap at "${this._rushConfiguration.defaultSubspace.getTempShrinkwrapFilename()}"` ); } for (const rushProject of this._rushConfiguration.projects) { - await this._linkProject(rushProject, pnpmShrinkwrapFile, subspaceName); + await this._linkProject(rushProject, pnpmShrinkwrapFile); } } else { // eslint-disable-next-line no-console @@ -91,8 +91,7 @@ export class PnpmLinkManager extends BaseLinkManager { */ private async _linkProject( project: RushConfigurationProject, - pnpmShrinkwrapFile: PnpmShrinkwrapFile, - subspaceName: string | undefined + pnpmShrinkwrapFile: PnpmShrinkwrapFile ): Promise { // eslint-disable-next-line no-console console.log(`\nLINKING: ${project.packageName}`); @@ -105,7 +104,7 @@ export class PnpmLinkManager extends BaseLinkManager { // Example: "C:\MyRepo\common\temp\projects\project1 const extractedFolder: string = path.join( - this._rushConfiguration.getCommonTempFolder(subspaceName), + this._rushConfiguration.commonTempFolder, RushConstants.rushTempProjectsFolderName, unscopedTempProjectName ); @@ -115,7 +114,7 @@ export class PnpmLinkManager extends BaseLinkManager { // Example: "C:\MyRepo\common\temp\node_modules\@rush-temp\project1" const installFolderName: string = path.join( - this._rushConfiguration.getCommonTempFolder(subspaceName), + this._rushConfiguration.commonTempFolder, RushConstants.nodeModulesFolderName, RushConstants.rushTempNpmScope, unscopedTempProjectName @@ -201,7 +200,7 @@ export class PnpmLinkManager extends BaseLinkManager { // e.g.: C:\wbt\common\temp\projects\api-documenter.tgz const absolutePathToTgzFile: string = path.resolve( - this._rushConfiguration.getCommonTempFolder(subspaceName), + this._rushConfiguration.commonTempFolder, relativePathToTgzFile ); @@ -230,8 +229,7 @@ export class PnpmLinkManager extends BaseLinkManager { const pathToLocalInstallation: string = await this._getPathToLocalInstallationAsync( tarballEntry, absolutePathToTgzFile, - folderNameSuffix, - subspaceName + folderNameSuffix ); const parentShrinkwrapEntry: IPnpmShrinkwrapDependencyYaml | undefined = @@ -291,8 +289,7 @@ export class PnpmLinkManager extends BaseLinkManager { private async _getPathToLocalInstallationAsync( tarballEntry: string, absolutePathToTgzFile: string, - folderSuffix: string, - subspaceName: string | undefined + folderSuffix: string ): Promise { if (this._pnpmVersion.major === 6) { // PNPM 6 changed formatting to replace all ':' and '/' chars with '+'. Additionally, folder names > 120 @@ -316,7 +313,7 @@ export class PnpmLinkManager extends BaseLinkManager { } return path.join( - this._rushConfiguration.getCommonTempFolder(subspaceName), + this._rushConfiguration.commonTempFolder, RushConstants.nodeModulesFolderName, '.pnpm', folderName, @@ -331,7 +328,7 @@ export class PnpmLinkManager extends BaseLinkManager { // file+projects+presentation-integration-tests.tgz_jsdom@11.12.0 const folderName: string = depPathToFilename(`${tarballEntry}${folderSuffix}`); return path.join( - this._rushConfiguration.getCommonTempFolder(subspaceName), + this._rushConfiguration.commonTempFolder, RushConstants.nodeModulesFolderName, '.pnpm', folderName, @@ -346,7 +343,7 @@ export class PnpmLinkManager extends BaseLinkManager { const escapedLocalPath: string = depPathToFilename(tarballEntry); const folderName: string = `${escapedLocalPath}${folderSuffix}`; return path.join( - this._rushConfiguration.getCommonTempFolder(subspaceName), + this._rushConfiguration.commonTempFolder, RushConstants.nodeModulesFolderName, '.pnpm', folderName, @@ -362,7 +359,7 @@ export class PnpmLinkManager extends BaseLinkManager { // See https://github.com/pnpm/pnpm/releases/tag/v4.0.0 return path.join( - this._rushConfiguration.getCommonTempFolder(subspaceName), + this._rushConfiguration.commonTempFolder, RushConstants.nodeModulesFolderName, '.pnpm', 'local', diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmProjectShrinkwrapFile.ts b/libraries/rush-lib/src/logic/pnpm/PnpmProjectShrinkwrapFile.ts index ee21dfe01d5..e44bdb7fea4 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmProjectShrinkwrapFile.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmProjectShrinkwrapFile.ts @@ -12,6 +12,7 @@ import type { } from './PnpmShrinkwrapFile'; import type { DependencySpecifier } from '../DependencySpecifier'; import { RushConstants } from '../RushConstants'; +import type { Subspace } from '../../api/Subspace'; /** * @@ -72,11 +73,10 @@ export class PnpmProjectShrinkwrapFile extends BaseProjectShrinkwrapFile | undefined { // Obtain the workspace importer from the shrinkwrap, which lists resolved dependencies - const subspaceName: string | undefined = - this.project.rushConfiguration.getProjectSubspace && - this.project.rushConfiguration.getProjectSubspace(this.project); + const subspace: Subspace = this.project.subspace; + const importerKey: string = this.shrinkwrapFile.getImporterKeyByPath( - this.project.rushConfiguration.getCommonTempFolder(subspaceName), + subspace.getSubspaceTempFolder(), this.project.projectFolder ); diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts b/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts index 8d8e866f87f..7f5bd13a2ff 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts @@ -28,6 +28,7 @@ import { PnpmProjectShrinkwrapFile } from './PnpmProjectShrinkwrapFile'; import type { PackageManagerOptionsConfigurationBase } from '../base/BasePackageManagerOptionsConfiguration'; import { PnpmOptionsConfiguration } from './PnpmOptionsConfiguration'; import type { IPnpmfile, IPnpmfileContext } from './IPnpmfile'; +import type { Subspace } from '../../api/Subspace'; const yamlModule: typeof import('js-yaml') = Import.lazy('js-yaml', require); @@ -588,21 +589,18 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { /** @override */ public findOrphanedProjects( rushConfiguration: RushConfiguration, - subspaceName: string | undefined + subspace: Subspace ): ReadonlyArray { // The base shrinkwrap handles orphaned projects the same across all package managers, // but this is only valid for non-workspace installs if (!this.isWorkspaceCompatible) { - return super.findOrphanedProjects(rushConfiguration, subspaceName); + return super.findOrphanedProjects(rushConfiguration, subspace); } const orphanedProjectPaths: string[] = []; for (const importerKey of this.getImporterKeys()) { // PNPM importer keys are relative paths from the workspace root, which is the common temp folder - const rushProjectPath: string = path.resolve( - rushConfiguration.getCommonTempFolder(subspaceName), - importerKey - ); + const rushProjectPath: string = path.resolve(subspace.getSubspaceTempFolder(), importerKey); if (!rushConfiguration.tryGetProjectForPath(rushProjectPath)) { orphanedProjectPaths.push(rushProjectPath); } @@ -680,11 +678,11 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { /** @override */ public async isWorkspaceProjectModifiedAsync( project: RushConfigurationProject, - subspaceName: string | undefined, + subspace: Subspace, variant?: string ): Promise { const importerKey: string = this.getImporterKeyByPath( - project.rushConfiguration.getCommonTempFolder(subspaceName), + subspace.getSubspaceTempFolder(), project.projectFolder ); @@ -700,7 +698,7 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { if (!this._pnpmfileConfiguration) { this._pnpmfileConfiguration = await PnpmfileConfiguration.initializeAsync( project.rushConfiguration, - subspaceName, + subspace, { variant } @@ -710,10 +708,10 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { let transformedPackageJson: IPackageJson = packageJson; let subspacePnpmfile: IPnpmfile | undefined; - if (subspaceName) { + if (project.rushConfiguration.subspacesFeatureEnabled) { // Get the pnpmfile const subspacePnpmfilePath: string = path.join( - project.rushConfiguration.getCommonTempFolder(subspaceName), + subspace.getSubspaceTempFolder(), RushConstants.pnpmfileGlobalFilename ); diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts b/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts index 3afec91237f..b608d3cc329 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts @@ -12,6 +12,7 @@ import * as pnpmfile from './PnpmfileShim'; import { pnpmfileShimFilename, scriptsFolderPath } from '../../utilities/PathConstants'; import type { IPnpmfileContext, IPnpmfileShimSettings } from './IPnpmfile'; +import type { Subspace } from '../../api/Subspace'; /** * Options used when generating the pnpmfile shim settings file. @@ -36,7 +37,7 @@ export class PnpmfileConfiguration { public static async initializeAsync( rushConfiguration: RushConfiguration, - subspaceName: string | undefined, + subspace: Subspace, pnpmfileShimOptions?: IPnpmfileShimOptions ): Promise { if (rushConfiguration.packageManager !== 'pnpm') { @@ -50,7 +51,7 @@ export class PnpmfileConfiguration { log: (message: string) => {}, pnpmfileShimSettings: await PnpmfileConfiguration._getPnpmfileShimSettingsAsync( rushConfiguration, - subspaceName, + subspace, pnpmfileShimOptions ) }; @@ -61,7 +62,7 @@ export class PnpmfileConfiguration { public static async writeCommonTempPnpmfileShimAsync( rushConfiguration: RushConfiguration, targetDir: string, - subspaceName: string | undefined, + subspace: Subspace, options?: IPnpmfileShimOptions ): Promise { if (rushConfiguration.packageManager !== 'pnpm') { @@ -82,7 +83,7 @@ export class PnpmfileConfiguration { }); const pnpmfileShimSettings: IPnpmfileShimSettings = - await PnpmfileConfiguration._getPnpmfileShimSettingsAsync(rushConfiguration, subspaceName, options); + await PnpmfileConfiguration._getPnpmfileShimSettingsAsync(rushConfiguration, subspace, options); // Write the settings file used by the shim await JsonFile.saveAsync(pnpmfileShimSettings, path.join(targetDir, 'pnpmfileSettings.json'), { @@ -92,7 +93,7 @@ export class PnpmfileConfiguration { private static async _getPnpmfileShimSettingsAsync( rushConfiguration: RushConfiguration, - subspaceName: string | undefined, + subspace: Subspace, options?: IPnpmfileShimOptions ): Promise { let allPreferredVersions: { [dependencyName: string]: string } = {}; @@ -101,8 +102,7 @@ export class PnpmfileConfiguration { // Only workspaces shims in the common versions using pnpmfile if ((rushConfiguration.packageManagerOptions as PnpmOptionsConfiguration).useWorkspaces) { - const commonVersionsConfiguration: CommonVersionsConfiguration = - rushConfiguration.getCommonVersions(subspaceName); + const commonVersionsConfiguration: CommonVersionsConfiguration = subspace.getCommonVersions(); const preferredVersions: Map = new Map(); MapExtensions.mergeFromMap(preferredVersions, commonVersionsConfiguration.getAllPreferredVersions()); MapExtensions.mergeFromMap(preferredVersions, rushConfiguration.getImplicitlyPreferredVersions()); @@ -124,7 +124,7 @@ export class PnpmfileConfiguration { }; // Use the provided path if available. Otherwise, use the default path. - const userPnpmfilePath: string | undefined = rushConfiguration.getPnpmfilePath(subspaceName); + const userPnpmfilePath: string | undefined = subspace.getPnpmfilePath(); if (userPnpmfilePath && FileSystem.exists(userPnpmfilePath)) { settings.userPnpmfilePath = userPnpmfilePath; } diff --git a/libraries/rush-lib/src/logic/pnpm/SubspacePnpmfileConfiguration.ts b/libraries/rush-lib/src/logic/pnpm/SubspacePnpmfileConfiguration.ts index 3a844b11c92..2e41ae1cb89 100644 --- a/libraries/rush-lib/src/logic/pnpm/SubspacePnpmfileConfiguration.ts +++ b/libraries/rush-lib/src/logic/pnpm/SubspacePnpmfileConfiguration.ts @@ -2,38 +2,22 @@ // See LICENSE in the project root for license information. import * as path from 'path'; -import { FileSystem, Import, type IPackageJson, JsonFile } from '@rushstack/node-core-library'; -import * as subspaceGlobalPnpmfile from './SubspaceGlobalPnpmfileShim'; +import { FileSystem, Import, JsonFile } from '@rushstack/node-core-library'; import { subspacePnpmfileShimFilename, scriptsFolderPath } from '../../utilities/PathConstants'; -import type { IPnpmfileContext, ISubspacePnpmfileShimSettings, IWorkspaceProjectInfo } from './IPnpmfile'; +import type { ISubspacePnpmfileShimSettings, IWorkspaceProjectInfo } from './IPnpmfile'; import type { RushConfiguration } from '../../api/RushConfiguration'; import type { PnpmPackageManager } from '../../api/packageManager/PnpmPackageManager'; -import { SubspacesConfiguration } from '../../api/SubspacesConfiguration'; import { RushConstants } from '../RushConstants'; +import type { Subspace } from '../../api/Subspace'; /** * Loads PNPM's pnpmfile.js configuration, and invokes it to preprocess package.json files, * optionally utilizing a pnpmfile shim to inject preferred versions. */ export class SubspacePnpmfileConfiguration { - private _context: IPnpmfileContext | undefined; - - public constructor(rushConfiguration: RushConfiguration) { - if (rushConfiguration.packageManager !== 'pnpm') { - throw new Error( - `PnpmfileConfiguration cannot be used with package manager "${rushConfiguration.packageManager}"` - ); - } - - // Set the context to swallow log output and store our settings - this._context = { - log: (message: string) => {}, - subspacePnpmfileShimSettings: SubspacePnpmfileConfiguration._getSubspacePnpmfileShimSettings( - rushConfiguration, - '' - ) - }; + private constructor() { + // not used } /** @@ -43,7 +27,7 @@ export class SubspacePnpmfileConfiguration { */ public static async writeCommonTempSubspaceGlobalPnpmfileAsync( rushConfiguration: RushConfiguration, - subspaceName: string + subspace: Subspace ): Promise { if (rushConfiguration.packageManager !== 'pnpm') { throw new Error( @@ -51,7 +35,7 @@ export class SubspacePnpmfileConfiguration { ); } - const targetDir: string = rushConfiguration.getCommonTempFolder(subspaceName); + const targetDir: string = subspace.getSubspaceTempFolder(); const subspaceGlobalPnpmfilePath: string = path.join(targetDir, RushConstants.pnpmfileGlobalFilename); // Write the shim itself @@ -61,7 +45,7 @@ export class SubspacePnpmfileConfiguration { }); const subspaceGlobalPnpmfileShimSettings: ISubspacePnpmfileShimSettings = - SubspacePnpmfileConfiguration._getSubspacePnpmfileShimSettings(rushConfiguration, subspaceName); + SubspacePnpmfileConfiguration._getSubspacePnpmfileShimSettings(rushConfiguration, subspace); // Write the settings file used by the shim await JsonFile.saveAsync( @@ -75,7 +59,7 @@ export class SubspacePnpmfileConfiguration { private static _getSubspacePnpmfileShimSettings( rushConfiguration: RushConfiguration, - subspaceName: string + subspace: Subspace ): ISubspacePnpmfileShimSettings { const workspaceProjects: Record = {}; const subspaceProjects: Record = {}; @@ -86,9 +70,7 @@ export class SubspacePnpmfileConfiguration { projectRelativeFolder, packageVersion: packageJson.version }; - (SubspacesConfiguration.belongsInSubspace(project, subspaceName) - ? subspaceProjects - : workspaceProjects)[packageName] = workspaceProjectInfo; + (subspace.contains(project) ? subspaceProjects : workspaceProjects)[packageName] = workspaceProjectInfo; } const settings: ISubspacePnpmfileShimSettings = { @@ -99,7 +81,7 @@ export class SubspacePnpmfileConfiguration { // common/config/rush/.pnpmfile-split-workspace.cjs const userPnpmfilePath: string = path.join( - rushConfiguration.getCommonRushConfigFolder(subspaceName), + subspace.getSubspaceConfigFolder(), (rushConfiguration.packageManagerWrapper as PnpmPackageManager).subspacePnpmfileFilename ); if (FileSystem.exists(userPnpmfilePath)) { @@ -108,16 +90,4 @@ export class SubspacePnpmfileConfiguration { return settings; } - - /** - * Transform a package.json file using the pnpmfile.js hook. - * @returns the tranformed object, or the original input if pnpmfile.js was not found. - */ - public transform(packageJson: IPackageJson): IPackageJson { - if (!subspaceGlobalPnpmfile.hooks?.readPackage || !this._context) { - return packageJson; - } else { - return subspaceGlobalPnpmfile.hooks.readPackage(packageJson, this._context); - } - } } diff --git a/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapFile.test.ts b/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapFile.test.ts index bdb19a7b7ba..b433514c8ce 100644 --- a/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapFile.test.ts +++ b/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapFile.test.ts @@ -133,9 +133,12 @@ describe(PnpmShrinkwrapFile.name, () => { const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile( `${__dirname}/yamlFiles/pnpm-lock-v5/not-modified.yaml` ); - await expect(pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync(project, undefined)).resolves.toBe( - false - ); + await expect( + pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( + project, + project.rushConfiguration.defaultSubspace + ) + ).resolves.toBe(false); }); it('can detect modified', async () => { @@ -143,9 +146,12 @@ describe(PnpmShrinkwrapFile.name, () => { const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile( `${__dirname}/yamlFiles/pnpm-lock-v5/modified.yaml` ); - await expect(pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync(project, undefined)).resolves.toBe( - true - ); + await expect( + pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( + project, + project.rushConfiguration.defaultSubspace + ) + ).resolves.toBe(true); }); it('can detect overrides', async () => { @@ -153,9 +159,12 @@ describe(PnpmShrinkwrapFile.name, () => { const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile( `${__dirname}/yamlFiles/pnpm-lock-v5/overrides-not-modified.yaml` ); - await expect(pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync(project, undefined)).resolves.toBe( - false - ); + await expect( + pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( + project, + project.rushConfiguration.defaultSubspace + ) + ).resolves.toBe(false); }); }); @@ -165,9 +174,12 @@ describe(PnpmShrinkwrapFile.name, () => { const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile( `${__dirname}/yamlFiles/pnpm-lock-v6/not-modified.yaml` ); - await expect(pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync(project, undefined)).resolves.toBe( - false - ); + await expect( + pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( + project, + project.rushConfiguration.defaultSubspace + ) + ).resolves.toBe(false); }); it('can detect modified', async () => { @@ -175,9 +187,12 @@ describe(PnpmShrinkwrapFile.name, () => { const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile( `${__dirname}/yamlFiles/pnpm-lock-v6/modified.yaml` ); - await expect(pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync(project, undefined)).resolves.toBe( - true - ); + await expect( + pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( + project, + project.rushConfiguration.defaultSubspace + ) + ).resolves.toBe(true); }); it('can detect overrides', async () => { @@ -185,9 +200,12 @@ describe(PnpmShrinkwrapFile.name, () => { const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile( `${__dirname}/yamlFiles/pnpm-lock-v6/overrides-not-modified.yaml` ); - await expect(pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync(project, undefined)).resolves.toBe( - false - ); + await expect( + pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( + project, + project.rushConfiguration.defaultSubspace + ) + ).resolves.toBe(false); }); it('can handle the inconsistent version of a package declared in dependencies and devDependencies', async () => { @@ -195,9 +213,12 @@ describe(PnpmShrinkwrapFile.name, () => { const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile( `${__dirname}/yamlFiles/pnpm-lock-v6/inconsistent-dep-devDep.yaml` ); - await expect(pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync(project, undefined)).resolves.toBe( - false - ); + await expect( + pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( + project, + project.rushConfiguration.defaultSubspace + ) + ).resolves.toBe(false); }); }); }); diff --git a/libraries/rush-lib/src/logic/policy/PolicyValidator.ts b/libraries/rush-lib/src/logic/policy/PolicyValidator.ts index bb3be30824b..7485b86fa32 100644 --- a/libraries/rush-lib/src/logic/policy/PolicyValidator.ts +++ b/libraries/rush-lib/src/logic/policy/PolicyValidator.ts @@ -5,6 +5,7 @@ import type { RushConfiguration } from '../../api/RushConfiguration'; import * as GitEmailPolicy from './GitEmailPolicy'; import * as ShrinkwrapFilePolicy from './ShrinkwrapFilePolicy'; import * as EnvironmentPolicy from './EnvironmentPolicy'; +import type { Subspace } from '../../api/Subspace'; export interface IPolicyValidatorOptions { bypassPolicyAllowed?: boolean; @@ -15,8 +16,8 @@ export interface IPolicyValidatorOptions { export async function validatePolicyAsync( rushConfiguration: RushConfiguration, - options: IPolicyValidatorOptions, - subspaceName?: string | undefined + subspace: Subspace, + options: IPolicyValidatorOptions ): Promise { if (!options.bypassPolicy) { GitEmailPolicy.validate(rushConfiguration, options); @@ -24,7 +25,7 @@ export async function validatePolicyAsync( if (!options.allowShrinkwrapUpdates) { // Don't validate the shrinkwrap if updates are allowed, as it's likely to change // It also may have merge conflict markers, which PNPM can gracefully handle, but the validator cannot - ShrinkwrapFilePolicy.validate(rushConfiguration, options, subspaceName); + ShrinkwrapFilePolicy.validate(rushConfiguration, subspace, options); } } } diff --git a/libraries/rush-lib/src/logic/policy/ShrinkwrapFilePolicy.ts b/libraries/rush-lib/src/logic/policy/ShrinkwrapFilePolicy.ts index 49cd25958d3..40788b8b00d 100644 --- a/libraries/rush-lib/src/logic/policy/ShrinkwrapFilePolicy.ts +++ b/libraries/rush-lib/src/logic/policy/ShrinkwrapFilePolicy.ts @@ -6,6 +6,7 @@ import type { IPolicyValidatorOptions } from './PolicyValidator'; import type { BaseShrinkwrapFile } from '../base/BaseShrinkwrapFile'; import { ShrinkwrapFileFactory } from '../ShrinkwrapFileFactory'; import type { RepoStateFile } from '../RepoStateFile'; +import type { Subspace } from '../../api/Subspace'; export interface IShrinkwrapFilePolicyValidatorOptions extends IPolicyValidatorOptions { repoState: RepoStateFile; @@ -16,15 +17,15 @@ export interface IShrinkwrapFilePolicyValidatorOptions extends IPolicyValidatorO */ export function validate( rushConfiguration: RushConfiguration, - options: IPolicyValidatorOptions, - subspaceName?: string | undefined + subspace: Subspace, + options: IPolicyValidatorOptions ): void { // eslint-disable-next-line no-console console.log('Validating package manager shrinkwrap file.\n'); const shrinkwrapFile: BaseShrinkwrapFile | undefined = ShrinkwrapFileFactory.getShrinkwrapFile( rushConfiguration.packageManager, rushConfiguration.packageManagerOptions, - rushConfiguration.getCommittedShrinkwrapFilename(subspaceName) + subspace.getCommittedShrinkwrapFilename() ); if (!shrinkwrapFile) { @@ -38,7 +39,7 @@ export function validate( rushConfiguration.packageManagerOptions, { ...options, - repoState: rushConfiguration.getRepoState(options.shrinkwrapVariant) + repoState: subspace.getRepoState() }, rushConfiguration.experimentsConfiguration.configuration ); diff --git a/libraries/rush-lib/src/logic/selectors/SubspaceSelectorParser.ts b/libraries/rush-lib/src/logic/selectors/SubspaceSelectorParser.ts index 5ddc36fbd01..f4b3a2bfd06 100644 --- a/libraries/rush-lib/src/logic/selectors/SubspaceSelectorParser.ts +++ b/libraries/rush-lib/src/logic/selectors/SubspaceSelectorParser.ts @@ -3,6 +3,8 @@ import type { RushConfiguration } from '../../api/RushConfiguration'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; +import type { Subspace } from '../../api/Subspace'; +import { RushConstants } from '../RushConstants'; import type { IEvaluateSelectorOptions, ISelectorParser } from './ISelectorParser'; export class SubspaceSelectorParser implements ISelectorParser { @@ -15,12 +17,20 @@ export class SubspaceSelectorParser implements ISelectorParser> { - this._rushConfiguration.validateSubspaceName(unscopedSelector); + const subspace: Subspace = this._rushConfiguration.getSubspace(unscopedSelector); - return this._rushConfiguration.getSubspaceProjects(unscopedSelector); + return subspace.getProjects(); } public getCompletions(): Iterable { - return this._rushConfiguration.subspaceNames; + // Tab completion is a performance sensitive operation, so avoid loading all the projects + const subspaceNames: string[] = []; + if (this._rushConfiguration.subspacesConfiguration) { + subspaceNames.push(...this._rushConfiguration.subspacesConfiguration.subspaceNames); + } + if (!subspaceNames.indexOf(RushConstants.defaultSubspaceName)) { + subspaceNames.push(RushConstants.defaultSubspaceName); + } + return subspaceNames; } } diff --git a/libraries/rush-lib/src/logic/setup/SetupPackageRegistry.ts b/libraries/rush-lib/src/logic/setup/SetupPackageRegistry.ts index 569d7a853ec..c9017ef6c0c 100644 --- a/libraries/rush-lib/src/logic/setup/SetupPackageRegistry.ts +++ b/libraries/rush-lib/src/logic/setup/SetupPackageRegistry.ts @@ -74,7 +74,7 @@ export class SetupPackageRegistry { ); this._artifactoryConfiguration = new ArtifactoryConfiguration( - path.join(this.rushConfiguration.getCommonRushConfigFolder(), 'artifactory.json') + path.join(this.rushConfiguration.commonRushConfigFolder, 'artifactory.json') ); this._messages = { @@ -112,8 +112,8 @@ export class SetupPackageRegistry { if (!this._options.syncNpmrcAlreadyCalled) { Utilities.syncNpmrc( - this.rushConfiguration.getCommonRushConfigFolder(), - this.rushConfiguration.getCommonTempFolder() + this.rushConfiguration.commonRushConfigFolder, + this.rushConfiguration.commonTempFolder ); } @@ -131,7 +131,7 @@ export class SetupPackageRegistry { this._terminal.writeLine('Testing access to private NPM registry: ' + packageRegistry.registryUrl); const result: child_process.SpawnSyncReturns = Executable.spawnSync('npm', npmArgs, { - currentWorkingDirectory: this.rushConfiguration.getCommonTempFolder(), + currentWorkingDirectory: this.rushConfiguration.commonTempFolder, stdio: ['ignore', 'pipe', 'pipe'], // Wait at most 10 seconds for "npm view" to succeed timeoutMs: 10 * 1000 diff --git a/libraries/rush-lib/src/logic/test/BaseInstallManager.test.ts b/libraries/rush-lib/src/logic/test/BaseInstallManager.test.ts index d6f418fef59..b74ce187160 100644 --- a/libraries/rush-lib/src/logic/test/BaseInstallManager.test.ts +++ b/libraries/rush-lib/src/logic/test/BaseInstallManager.test.ts @@ -10,6 +10,7 @@ import type { IInstallManagerOptions } from '../base/BaseInstallManagerTypes'; import { RushConfiguration } from '../../api/RushConfiguration'; import { RushGlobalFolder } from '../../api/RushGlobalFolder'; +import type { Subspace } from '../../api/Subspace'; class FakeBaseInstallManager extends BaseInstallManager { public constructor( @@ -35,8 +36,8 @@ class FakeBaseInstallManager extends BaseInstallManager { protected postInstallAsync(): Promise { return Promise.resolve(); } - public pushConfigurationArgs(args: string[], options: IInstallManagerOptions): void { - return super.pushConfigurationArgs(args, options); + public pushConfigurationArgs(args: string[], options: IInstallManagerOptions, subspace: Subspace): void { + return super.pushConfigurationArgs(args, options, subspace); } } @@ -78,14 +79,14 @@ describe('BaseInstallManager Test', () => { jest.spyOn(ConsoleTerminalProvider.prototype, 'write').mockImplementation(mockWrite); const argsPnpmV6: string[] = []; - fakeBaseInstallManager6.pushConfigurationArgs(argsPnpmV6, options); + fakeBaseInstallManager6.pushConfigurationArgs(argsPnpmV6, options, rushConfigurationV7.defaultSubspace); expect(argsPnpmV6).not.toContain(pnpmIgnoreCompatibilityDbParameter); expect(mockWrite.mock.calls[0][0]).toContain( "Warning: Your rush.json specifies a pnpmVersion with a known issue that may cause unintended version selections. It's recommended to upgrade to PNPM >=6.34.0 or >=7.9.0. For details see: https://rushjs.io/link/pnpm-issue-5132" ); const argsPnpmV7: string[] = []; - fakeBaseInstallManager7.pushConfigurationArgs(argsPnpmV7, options); + fakeBaseInstallManager7.pushConfigurationArgs(argsPnpmV7, options, rushConfigurationV7.defaultSubspace); expect(argsPnpmV7).not.toContain(pnpmIgnoreCompatibilityDbParameter); expect(mockWrite.mock.calls[0][0]).toContain( "Warning: Your rush.json specifies a pnpmVersion with a known issue that may cause unintended version selections. It's recommended to upgrade to PNPM >=6.34.0 or >=7.9.0. For details see: https://rushjs.io/link/pnpm-issue-5132" @@ -109,7 +110,7 @@ describe('BaseInstallManager Test', () => { jest.spyOn(ConsoleTerminalProvider.prototype, 'write').mockImplementation(mockWrite); const args: string[] = []; - fakeBaseInstallManager.pushConfigurationArgs(args, options); + fakeBaseInstallManager.pushConfigurationArgs(args, options, rushConfiguration.defaultSubspace); expect(args).toContain(pnpmIgnoreCompatibilityDbParameter); if (mockWrite.mock.calls.length) { diff --git a/libraries/rush-lib/src/logic/test/InstallHelpers.test.ts b/libraries/rush-lib/src/logic/test/InstallHelpers.test.ts index 99b70b1e307..e943f877654 100644 --- a/libraries/rush-lib/src/logic/test/InstallHelpers.test.ts +++ b/libraries/rush-lib/src/logic/test/InstallHelpers.test.ts @@ -23,7 +23,11 @@ describe('InstallHelpers', () => { const RUSH_JSON_FILENAME: string = `${__dirname}/pnpmConfig/rush.json`; const rushConfiguration: RushConfiguration = RushConfiguration.loadFromConfigurationFile(RUSH_JSON_FILENAME); - InstallHelpers.generateCommonPackageJson(rushConfiguration, undefined, undefined); + InstallHelpers.generateCommonPackageJson( + rushConfiguration, + rushConfiguration.defaultSubspace, + undefined + ); const packageJson: IPackageJson = mockJsonFileSave.mock.calls[0][0]; expect(packageJson).toEqual( expect.objectContaining({ diff --git a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts index 90726b58a0e..5760c02a45b 100644 --- a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts +++ b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts @@ -26,10 +26,10 @@ describe(ProjectChangeAnalyzer.name, () => { files: Map ): ProjectChangeAnalyzer { const rushConfiguration: RushConfiguration = { - getCommonRushConfigFolder: () => '', + commonRushConfigFolder: '', projects, rushJsonFolder: '', - getCommittedShrinkwrapFilename(subspaceName?: string | undefined): string { + getCommittedShrinkwrapFilename(): string { return 'common/config/rush/pnpm-lock.yaml'; }, getProjectLookupForRoot(root: string): LookupByPath { diff --git a/libraries/rush-lib/src/logic/test/PublishUtilities.test.ts b/libraries/rush-lib/src/logic/test/PublishUtilities.test.ts index 8a68f61ee54..c454ffb6550 100644 --- a/libraries/rush-lib/src/logic/test/PublishUtilities.test.ts +++ b/libraries/rush-lib/src/logic/test/PublishUtilities.test.ts @@ -10,7 +10,7 @@ import { ChangeFiles } from '../ChangeFiles'; /* eslint-disable dot-notation */ function generateChangeSnapshot( - allPackages: Map, + allPackages: ReadonlyMap, allChanges: IChangeRequests ): string { const unchangedLines: string[] = []; @@ -82,7 +82,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns no changes in an empty change folder', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -94,7 +95,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns 1 change when changing a leaf package', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -109,7 +111,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns 6 changes when patching a root package', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -142,7 +145,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns 8 changes when hotfixing a root package', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -173,7 +177,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns 9 changes when major bumping a root package', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -206,7 +211,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('updates policy project dependencies when updating a lockstep version policy with no nextBump', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -239,7 +245,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns 2 changes when bumping cyclic dependencies', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -270,7 +277,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns error when mixing hotfix and non-hotfix changes', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; await expect( async () => await PublishUtilities.findChangeRequestsAsync( @@ -282,7 +290,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns error when adding hotfix with config disabled', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; // Overload hotfixChangeEnabled function (packagesRushConfiguration as unknown as Record).hotfixChangeEnabled = false; @@ -297,7 +306,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('can resolve multiple changes requests on the same package', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -330,7 +340,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('can resolve multiple reverse-ordered changes requests on the same package', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -363,7 +374,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('can resolve multiple hotfix changes', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -394,7 +406,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('can update an explicit dependency', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -425,7 +438,7 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('can exclude lock step projects', async () => { - const allPackages: Map = repoRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = repoRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, repoRushConfiguration, @@ -465,7 +478,7 @@ describe(PublishUtilities.sortChangeRequests.name, () => { }); it('can return a sorted array of the change requests to be published in the correct order', async () => { - const allPackages: Map = rushConfiguration.projectsByName; + const allPackages: ReadonlyMap = rushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, rushConfiguration, @@ -553,7 +566,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns no changes in an empty change folder', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -565,7 +579,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns 1 change when changing a leaf package', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -580,7 +595,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns 6 changes when patching a root package', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -613,7 +629,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns 8 changes when hotfixing a root package', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -644,7 +661,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns 9 changes when major bumping a root package', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -677,7 +695,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns 2 changes when bumping cyclic dependencies', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -708,7 +727,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns error when mixing hotfix and non-hotfix changes', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; await expect( async () => await PublishUtilities.findChangeRequestsAsync( @@ -720,7 +740,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('returns error when adding hotfix with config disabled', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; // Overload hotfixChangeEnabled function (packagesRushConfiguration as unknown as Record).hotfixChangeEnabled = false; @@ -735,7 +756,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('can resolve multiple changes requests on the same package', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -768,7 +790,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('can resolve multiple reverse-ordered changes requests on the same package', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -801,7 +824,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('can resolve multiple hotfix changes', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -832,7 +856,8 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('can update an explicit dependency', async () => { - const allPackages: Map = packagesRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = + packagesRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, packagesRushConfiguration, @@ -850,7 +875,7 @@ describe(PublishUtilities.findChangeRequestsAsync.name, () => { }); it('can exclude lock step projects', async () => { - const allPackages: Map = repoRushConfiguration.projectsByName; + const allPackages: ReadonlyMap = repoRushConfiguration.projectsByName; const allChanges: IChangeRequests = await PublishUtilities.findChangeRequestsAsync( allPackages, repoRushConfiguration, diff --git a/libraries/rush-lib/src/logic/test/ShrinkwrapFile.test.ts b/libraries/rush-lib/src/logic/test/ShrinkwrapFile.test.ts index f3a2a6c51cf..f20bd502fc2 100644 --- a/libraries/rush-lib/src/logic/test/ShrinkwrapFile.test.ts +++ b/libraries/rush-lib/src/logic/test/ShrinkwrapFile.test.ts @@ -154,7 +154,10 @@ describe(PnpmShrinkwrapFile.name, () => { projectRushTempFolder: `${projectName}/.rush/temp`, projectFolder: projectName, rushConfiguration: { - getCommonTempFolder: () => 'common/temp' + commonTempFolder: 'common/temp' + }, + subspace: { + getSubspaceTempFolder: () => 'common/temp' } } as RushConfigurationProject; diff --git a/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts b/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts index a49a468fa42..22e8bfa4ecb 100644 --- a/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts +++ b/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts @@ -12,12 +12,13 @@ import { VersionMismatchFinderProject } from './VersionMismatchFinderProject'; import { VersionMismatchFinderCommonVersions } from './VersionMismatchFinderCommonVersions'; import { CustomTipId } from '../../api/CustomTipsConfiguration'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; +import type { Subspace } from '../../api/Subspace'; const TRUNCATE_AFTER_PACKAGE_NAME_COUNT: number = 5; export interface IVersionMismatchFinderOptions { variant?: string | undefined; - subspaceName?: string | undefined; + subspace?: Subspace; } export interface IVersionMismatchFinderRushCheckOptions extends IVersionMismatchFinderOptions { @@ -99,9 +100,9 @@ export class VersionMismatchFinder { rushConfiguration: RushConfiguration, options: IVersionMismatchFinderOptions = {} ): VersionMismatchFinder { - const commonVersions: CommonVersionsConfiguration = rushConfiguration.getCommonVersions( - options.subspaceName - ); + const commonVersions: CommonVersionsConfiguration = ( + options.subspace ?? rushConfiguration.defaultSubspace + ).getCommonVersions(); const projects: VersionMismatchFinderEntity[] = []; @@ -111,8 +112,8 @@ export class VersionMismatchFinder { // If subspace is specified, only go through projects in that subspace let projectsToParse: RushConfigurationProject[] = []; - if (options.subspaceName) { - projectsToParse = rushConfiguration.getSubspaceProjects(options.subspaceName); + if (options.subspace) { + projectsToParse = options.subspace.getProjects(); } else { projectsToParse = rushConfiguration.projects; } diff --git a/libraries/rush-lib/src/logic/yarn/YarnShrinkwrapFile.ts b/libraries/rush-lib/src/logic/yarn/YarnShrinkwrapFile.ts index d1fd77cc783..4b18483be5a 100644 --- a/libraries/rush-lib/src/logic/yarn/YarnShrinkwrapFile.ts +++ b/libraries/rush-lib/src/logic/yarn/YarnShrinkwrapFile.ts @@ -13,6 +13,7 @@ import type { DependencySpecifier } from '../DependencySpecifier'; import { PackageNameParsers } from '../../api/PackageNameParsers'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; import type { BaseProjectShrinkwrapFile } from '../base/BaseProjectShrinkwrapFile'; +import type { Subspace } from '../../api/Subspace'; /** * @yarnpkg/lockfile doesn't have types @@ -285,6 +286,7 @@ export class YarnShrinkwrapFile extends BaseShrinkwrapFile { /** @override */ public async isWorkspaceProjectModifiedAsync( project: RushConfigurationProject, + subspace: Subspace, variant?: string ): Promise { throw new InternalError('Not implemented'); diff --git a/libraries/rush-lib/src/utilities/Utilities.ts b/libraries/rush-lib/src/utilities/Utilities.ts index c3cbd6f7220..6bf996f93df 100644 --- a/libraries/rush-lib/src/utilities/Utilities.ts +++ b/libraries/rush-lib/src/utilities/Utilities.ts @@ -594,9 +594,7 @@ export class Utilities { ...options.environmentPathOptions, rushJsonFolder: options.rushConfiguration?.rushJsonFolder, projectRoot: options.workingDirectory, - commonTempFolder: options.rushConfiguration - ? options.rushConfiguration.getCommonTempFolder() - : undefined + commonTempFolder: options.rushConfiguration ? options.rushConfiguration.commonTempFolder : undefined } });