From cebab5e5bc4f747951c80264178d5952a0eef353 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Wed, 18 Sep 2024 14:01:38 -0700 Subject: [PATCH 01/24] Bring back the Variants feature. --- .../bring-back-variants_2024-09-18-21-01.json | 10 ++ common/reviews/api/rush-lib.api.md | 37 ++++-- libraries/rush-lib/assets/rush-init/rush.json | 33 ++++++ .../src/api/CommonVersionsConfiguration.ts | 8 +- .../src/api/EnvironmentConfiguration.ts | 9 ++ .../rush-lib/src/api/RushConfiguration.ts | 103 +++++++++++++--- libraries/rush-lib/src/api/Subspace.ts | 59 ++++++--- libraries/rush-lib/src/api/Variants.ts | 30 +++++ .../RushCommandLine.test.ts.snap | 32 +++++ .../src/cli/RushPnpmCommandLineParser.ts | 4 +- .../src/cli/actions/BaseInstallAction.ts | 22 ++-- .../rush-lib/src/cli/actions/CheckAction.ts | 20 +++- .../rush-lib/src/cli/actions/DeployAction.ts | 5 +- .../rush-lib/src/cli/actions/InstallAction.ts | 4 + .../rush-lib/src/cli/actions/PublishAction.ts | 3 + .../rush-lib/src/cli/actions/UpdateAction.ts | 4 + .../cli/actions/UpgradeInteractiveAction.ts | 17 ++- .../rush-lib/src/cli/actions/VersionAction.ts | 21 ++-- .../cli/scriptActions/PhasedScriptAction.ts | 12 +- .../CommandLineHelp.test.ts.snap | 24 +++- .../rush-lib/src/logic/DependencyAnalyzer.ts | 43 +++++-- .../rush-lib/src/logic/PackageJsonUpdater.ts | 34 ++++-- .../src/logic/PackageJsonUpdaterTypes.ts | 4 + libraries/rush-lib/src/logic/RepoStateFile.ts | 12 +- libraries/rush-lib/src/logic/RushConstants.ts | 7 ++ .../src/logic/base/BaseInstallManager.ts | 112 ++++++++++++------ .../src/logic/base/BaseInstallManagerTypes.ts | 5 + .../src/logic/base/BaseShrinkwrapFile.ts | 4 +- .../installManager/RushInstallManager.ts | 12 +- .../installManager/WorkspaceInstallManager.ts | 18 +-- .../installManager/doBasicInstallAsync.ts | 20 +++- .../src/logic/npm/NpmShrinkwrapFile.ts | 3 +- .../src/logic/pnpm/PnpmShrinkwrapFile.ts | 6 +- .../src/logic/pnpm/PnpmfileConfiguration.ts | 16 ++- .../pnpm/SubspacePnpmfileConfiguration.ts | 10 +- .../pnpm/test/PnpmShrinkwrapFile.test.ts | 21 ++-- .../pnpm/test/PnpmfileConfiguration.test.ts | 3 +- .../src/logic/policy/PolicyValidator.ts | 3 +- .../src/logic/policy/ShrinkwrapFilePolicy.ts | 3 +- .../versionMismatch/VersionMismatchFinder.ts | 40 +++---- .../rush-lib/src/schemas/rush.schema.json | 18 ++- 41 files changed, 636 insertions(+), 215 deletions(-) create mode 100644 common/changes/@microsoft/rush/bring-back-variants_2024-09-18-21-01.json create mode 100644 libraries/rush-lib/src/api/Variants.ts diff --git a/common/changes/@microsoft/rush/bring-back-variants_2024-09-18-21-01.json b/common/changes/@microsoft/rush/bring-back-variants_2024-09-18-21-01.json new file mode 100644 index 00000000000..4812498c1f6 --- /dev/null +++ b/common/changes/@microsoft/rush/bring-back-variants_2024-09-18-21-01.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Bring back the Variants feature that was removed in https://github.com/microsoft/rushstack/pull/4538.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index f4729b376b4..da54365669d 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -18,6 +18,7 @@ import { IPackageJson } from '@rushstack/node-core-library'; import { IPrefixMatch } from '@rushstack/lookup-by-path'; import { ITerminal } from '@rushstack/terminal'; import { ITerminalProvider } from '@rushstack/terminal'; +import { JsonNull } from '@rushstack/node-core-library'; import { JsonObject } from '@rushstack/node-core-library'; import { LookupByPath } from '@rushstack/lookup-by-path'; import { PackageNameParser } from '@rushstack/node-core-library'; @@ -128,7 +129,7 @@ export class CommonVersionsConfiguration { getAllPreferredVersions(): Map; getPreferredVersionsHash(): string; readonly implicitlyPreferredVersions: boolean | undefined; - static loadFromFile(jsonFilename: string, rushConfiguration?: RushConfiguration): CommonVersionsConfiguration; + static loadFromFile(jsonFilePath: string, rushConfiguration?: RushConfiguration): CommonVersionsConfiguration; readonly preferredVersions: Map; save(): boolean; } @@ -258,6 +259,7 @@ export const EnvironmentVariableNames: { readonly RUSH_PREVIEW_VERSION: "RUSH_PREVIEW_VERSION"; readonly RUSH_ALLOW_UNSUPPORTED_NODEJS: "RUSH_ALLOW_UNSUPPORTED_NODEJS"; readonly RUSH_ALLOW_WARNINGS_IN_SUCCESSFUL_BUILD: "RUSH_ALLOW_WARNINGS_IN_SUCCESSFUL_BUILD"; + readonly RUSH_VARIANT: "RUSH_VARIANT"; readonly RUSH_PARALLELISM: "RUSH_PARALLELISM"; readonly RUSH_ABSOLUTE_SYMLINKS: "RUSH_ABSOLUTE_SYMLINKS"; readonly RUSH_PNPM_STORE_PATH: "RUSH_PNPM_STORE_PATH"; @@ -1126,7 +1128,7 @@ export class RepoStateFile { get packageJsonInjectedDependenciesHash(): string | undefined; get pnpmShrinkwrapHash(): string | undefined; get preferredVersionsHash(): string | undefined; - refreshState(rushConfiguration: RushConfiguration, subspace: Subspace | undefined): boolean; + refreshState(rushConfiguration: RushConfiguration, subspace: Subspace | undefined, variant?: string): boolean; } // @public @@ -1159,6 +1161,7 @@ export class RushConfiguration { readonly commonTempFolder: string; // @deprecated get commonVersions(): CommonVersionsConfiguration; + readonly currentVariantJsonFilePath: string; // @beta readonly customTipsConfiguration: CustomTipsConfiguration; // @beta @@ -1176,12 +1179,13 @@ export class RushConfiguration { findProjectByShorthandName(shorthandProjectName: string): RushConfigurationProject | undefined; findProjectByTempName(tempProjectName: string): RushConfigurationProject | undefined; // @deprecated (undocumented) - getCommittedShrinkwrapFilename(subspace?: Subspace): string; + getCommittedShrinkwrapFilename(subspace?: Subspace, variant?: string): string; // @deprecated (undocumented) - getCommonVersions(subspace?: Subspace): CommonVersionsConfiguration; + getCommonVersions(subspace?: Subspace, variant?: string): CommonVersionsConfiguration; // @deprecated (undocumented) - getCommonVersionsFilePath(subspace?: Subspace): string; - getImplicitlyPreferredVersions(subspace?: Subspace): Map; + getCommonVersionsFilePath(subspace?: Subspace, variant?: string): string; + getCurrentlyInstalledVariantAsync(): Promise; + getImplicitlyPreferredVersions(subspace?: Subspace, variant?: string): Map; // @deprecated (undocumented) getPnpmfilePath(subspace?: Subspace): string; getProjectByName(projectName: string): RushConfigurationProject | undefined; @@ -1201,9 +1205,11 @@ export class RushConfiguration { readonly gitSampleEmail: string; readonly gitTagSeparator: string | undefined; readonly gitVersionBumpCommitMessage: string | undefined; - // @internal @deprecated - readonly _hasVariantsField: boolean; readonly hotfixChangeEnabled: boolean; + // Warning: (ae-forgotten-export) The symbol "ICurrentVariantJson" needs to be exported by the entry point index.d.ts + // + // (undocumented) + _loadCurrentVariantJsonAsync(): Promise; static loadFromConfigurationFile(rushJsonFilename: string): RushConfiguration; // (undocumented) static loadFromDefaultLocation(options?: ITryFindRushJsonLocationOptions): RushConfiguration; @@ -1261,6 +1267,8 @@ export class RushConfiguration { tryGetSubspace(subspaceName: string): Subspace | undefined; // (undocumented) static tryLoadFromDefaultLocation(options?: ITryFindRushJsonLocationOptions): RushConfiguration | undefined; + // @beta + readonly variants: ReadonlySet; // @beta (undocumented) readonly versionPolicyConfiguration: VersionPolicyConfiguration; // @beta (undocumented) @@ -1368,6 +1376,7 @@ export class RushConstants { static readonly rushTempNpmScope: '@rush-temp'; static readonly rushTempProjectsFolderName: 'projects'; static readonly rushUserConfigurationFolderName: '.rush-user'; + static readonly rushVariantsFolderName: 'variants'; static readonly rushWebSiteUrl: 'https://rushjs.io'; static readonly subspacesConfigFilename: 'subspaces.json'; // (undocumented) @@ -1459,13 +1468,13 @@ export class Subspace { // @beta contains(project: RushConfigurationProject): boolean; // @beta - getCommittedShrinkwrapFilename(): string; + getCommittedShrinkwrapFilePath(variant?: string): string; // @beta - getCommonVersions(): CommonVersionsConfiguration; + getCommonVersions(variant?: string): CommonVersionsConfiguration; // @beta - getCommonVersionsFilePath(): string; + getCommonVersionsFilePath(variant?: string): string; // @beta - getPackageJsonInjectedDependenciesHash(): string | undefined; + getPackageJsonInjectedDependenciesHash(variant: string | undefined): string | undefined; // @beta getPnpmConfigFilePath(): string; // @beta @@ -1489,7 +1498,9 @@ export class Subspace { // @beta getTempShrinkwrapPreinstallFilename(subspaceName?: string | undefined): string; // @beta - get shouldEnsureConsistentVersions(): boolean; + getVariantDependentSubspaceConfigFolderPath(variant: string | undefined): string; + // @beta + shouldEnsureConsistentVersions(variant?: string): boolean; // (undocumented) readonly subspaceName: string; } diff --git a/libraries/rush-lib/assets/rush-init/rush.json b/libraries/rush-lib/assets/rush-init/rush.json index f77555b001b..d664739144a 100644 --- a/libraries/rush-lib/assets/rush-init/rush.json +++ b/libraries/rush-lib/assets/rush-init/rush.json @@ -273,6 +273,39 @@ "postRushx": [] }, + /** + * Installation variants allow you to maintain a parallel set of configuration files that can be + * used to build the entire monorepo with an alternate set of dependencies. For example, suppose + * you upgrade all your projects to use a new release of an important framework, but during a transition period + * you intend to maintain compatibility with the old release. In this situation, you probably want your + * CI validation to build the entire repo twice: once with the old release, and once with the new release. + * + * Rush "installation variants" correspond to sets of config files located under this folder: + * + * common/config/rush/variants/ + * + * The variant folder can contain an alternate common-versions.json file. Its "preferredVersions" field can be used + * to select older versions of dependencies (within a loose SemVer range specified in your package.json files). + * To install a variant, run "rush install --variant ". + * + * For more details and instructions, see this article: https://rushjs.io/pages/advanced/installation_variants/ + */ + "variants": [ + /*[BEGIN "HYPOTHETICAL"]*/ + { + /** + * The folder name for this variant. + */ + "variantName": "old-sdk", + + /** + * An informative description + */ + "description": "Build this repo using the previous release of the SDK" + } + /*[END "HYPOTHETICAL"]*/ + ], + /** * Rush can collect anonymous telemetry about everyday developer activity such as * success/failure of installs, builds, and other operations. You can use this to identify diff --git a/libraries/rush-lib/src/api/CommonVersionsConfiguration.ts b/libraries/rush-lib/src/api/CommonVersionsConfiguration.ts index a8343638f87..73ae342733b 100644 --- a/libraries/rush-lib/src/api/CommonVersionsConfiguration.ts +++ b/libraries/rush-lib/src/api/CommonVersionsConfiguration.ts @@ -185,16 +185,16 @@ export class CommonVersionsConfiguration { * If the file has not been created yet, then an empty object is returned. */ public static loadFromFile( - jsonFilename: string, + jsonFilePath: string, rushConfiguration?: RushConfiguration ): CommonVersionsConfiguration { let commonVersionsJson: ICommonVersionsJson | undefined = undefined; - if (FileSystem.exists(jsonFilename)) { - commonVersionsJson = JsonFile.loadAndValidate(jsonFilename, CommonVersionsConfiguration._jsonSchema); + if (FileSystem.exists(jsonFilePath)) { + commonVersionsJson = JsonFile.loadAndValidate(jsonFilePath, CommonVersionsConfiguration._jsonSchema); } - return new CommonVersionsConfiguration(commonVersionsJson, jsonFilename, rushConfiguration); + return new CommonVersionsConfiguration(commonVersionsJson, jsonFilePath, rushConfiguration); } private static _deserializeTable( diff --git a/libraries/rush-lib/src/api/EnvironmentConfiguration.ts b/libraries/rush-lib/src/api/EnvironmentConfiguration.ts index 7826651d1d2..da883ddff60 100644 --- a/libraries/rush-lib/src/api/EnvironmentConfiguration.ts +++ b/libraries/rush-lib/src/api/EnvironmentConfiguration.ts @@ -50,6 +50,14 @@ export const EnvironmentVariableNames = { */ RUSH_ALLOW_WARNINGS_IN_SUCCESSFUL_BUILD: 'RUSH_ALLOW_WARNINGS_IN_SUCCESSFUL_BUILD', + /** + * This variable selects a specific installation variant for Rush to use when installing + * and linking package dependencies. + * For more information, see the command-line help for the `--variant` parameter + * and this article: https://rushjs.io/pages/advanced/installation_variants/ + */ + RUSH_VARIANT: 'RUSH_VARIANT', + /** * Specifies the maximum number of concurrent processes to launch during a build. * For more information, see the command-line help for the `--parallelism` parameter for "rush build". @@ -533,6 +541,7 @@ export class EnvironmentConfiguration { case EnvironmentVariableNames.RUSH_PARALLELISM: case EnvironmentVariableNames.RUSH_PREVIEW_VERSION: + case EnvironmentVariableNames.RUSH_VARIANT: case EnvironmentVariableNames.RUSH_DEPLOY_TARGET_FOLDER: // Handled by @microsoft/rush front end break; diff --git a/libraries/rush-lib/src/api/RushConfiguration.ts b/libraries/rush-lib/src/api/RushConfiguration.ts index 70ed8338ab0..f09ee08eb02 100644 --- a/libraries/rush-lib/src/api/RushConfiguration.ts +++ b/libraries/rush-lib/src/api/RushConfiguration.ts @@ -12,7 +12,8 @@ import { FileSystem, type PackageNameParser, type FileSystemStats, - InternalError + InternalError, + type JsonNull } from '@rushstack/node-core-library'; import { LookupByPath } from '@rushstack/lookup-by-path'; import { trueCasePathSync } from 'true-case-path'; @@ -144,6 +145,14 @@ export interface IRushRepositoryJsonMultipleUrls extends IRushRepositoryJsonBase export type IRushRepositoryJson = IRushRepositoryJsonSingleUrl | IRushRepositoryJsonMultipleUrls; +/** + * Options defining an allowed variant as part of IRushConfigurationJson. + */ +export interface IRushVariantOptionsJson { + variantName: string; + description: string; +} + /** * This represents the JSON data structure for the "rush.json" configuration file. * See rush.schema.json for documentation. @@ -173,7 +182,14 @@ export interface IRushConfigurationJson { pnpmOptions?: IPnpmOptionsJson; yarnOptions?: IYarnOptionsJson; ensureConsistentVersions?: boolean; - variants?: unknown; + variants?: IRushVariantOptionsJson[]; +} + +/** + * This represents the JSON data structure for the "current-variant.json" data file. + */ +export interface ICurrentVariantJson { + variant: string | JsonNull; } /** @@ -212,6 +228,8 @@ export class RushConfiguration { private readonly _pathTrees: Map>; + private _currentVariantJsonLoadingPromise: Promise | undefined; + // Lazily loaded when the projects() getter is called. private _projects: RushConfigurationProject[] | undefined; @@ -340,12 +358,13 @@ export class RushConfiguration { public readonly subspacesFeatureEnabled: boolean; /** - * If true, the `variants` field is present in rush.json. + * The filename of the variant dependency data file. By default this is + * called 'current-variant.json' resides in the Rush common folder. + * Its data structure is defined by ICurrentVariantJson. * - * @internal - * @deprecated - Remove when the field is removed from the rush.json schema. + * Example: `C:\MyRepo\common\temp\current-variant.json` */ - public readonly _hasVariantsField: boolean; + public readonly currentVariantJsonFilePath: string; /** * The version of the locally package manager tool. (Example: "1.2.3") @@ -564,6 +583,13 @@ export class RushConfiguration { */ public readonly _rushPluginsConfiguration: RushPluginsConfiguration; + /** + * The variants specified in the rush.json configuration file. + * + * @beta + */ + public readonly variants: ReadonlySet; + /** * Use RushConfiguration.loadFromConfigurationFile() or Use RushConfiguration.loadFromDefaultLocation() * instead. @@ -617,6 +643,8 @@ export class RushConfiguration { this.changesFolder = path.join(this.commonFolder, RushConstants.changeFilesFolderName); + this.currentVariantJsonFilePath = path.join(this.commonTempFolder, 'current-variant.json'); + this.suppressNodeLtsWarning = !!rushConfigurationJson.suppressNodeLtsWarning; this._ensureConsistentVersionsJsonValue = rushConfigurationJson.ensureConsistentVersions; @@ -826,7 +854,18 @@ export class RushConfiguration { ); this.customTipsConfiguration = new CustomTipsConfiguration(this.customTipsConfigurationFilePath); - this._hasVariantsField = !!rushConfigurationJson.variants; + const variants: Set = new Set(); + for (const variantOptions of rushConfigurationJson.variants ?? []) { + const { variantName } = variantOptions; + + if (variants.has(variantName)) { + throw new Error(`Duplicate variant named '${variantName}' specified in configuration.`); + } + + variants.add(variantName); + } + + this.variants = variants; this._pathTrees = new Map(); } @@ -1372,39 +1411,57 @@ export class RushConfiguration { * Instead it will be initialized in an empty state, and calling CommonVersionsConfiguration.save() * will create the file. * - * @deprecated Use `getCommonVersions` instead, which gets the correct common version data. + * @deprecated Use `getCommonVersions` instead, which gets the correct common version data + * for a given active variant. */ public get commonVersions(): CommonVersionsConfiguration { - return this.defaultSubspace.getCommonVersions(); + return this.defaultSubspace.getCommonVersions(undefined); + } + + /** + * Gets the currently-installed variant, if an installation has occurred. + * For Rush operations which do not take a --variant parameter, this method + * determines which variant, if any, was last specified when performing "rush install" + * or "rush update". + */ + public async getCurrentlyInstalledVariantAsync(): Promise { + if (!this._currentVariantJsonLoadingPromise) { + this._currentVariantJsonLoadingPromise = this._loadCurrentVariantJsonAsync(); + } + + return (await this._currentVariantJsonLoadingPromise)?.variant ?? undefined; } /** * @deprecated Use {@link Subspace.getCommonVersionsFilePath} instead */ - public getCommonVersionsFilePath(subspace?: Subspace): string { - return (subspace ?? this.defaultSubspace).getCommonVersionsFilePath(); + public getCommonVersionsFilePath(subspace?: Subspace, variant?: string): string { + return (subspace ?? this.defaultSubspace).getCommonVersionsFilePath(variant); } /** * @deprecated Use {@link Subspace.getCommonVersions} instead */ - public getCommonVersions(subspace?: Subspace): CommonVersionsConfiguration { - return (subspace ?? this.defaultSubspace).getCommonVersions(); + public getCommonVersions(subspace?: Subspace, variant?: string): CommonVersionsConfiguration { + return (subspace ?? this.defaultSubspace).getCommonVersions(variant); } /** * Returns a map of all direct dependencies that only have a single semantic version specifier. * + * @param subspace - The subspace to use + * @param variant - The name of the current variant in use by the active command. + * * @returns A map of dependency name --\> version specifier for implicitly preferred versions. */ - public getImplicitlyPreferredVersions(subspace?: Subspace): Map { + public getImplicitlyPreferredVersions(subspace?: Subspace, variant?: string): Map { // TODO: During the next major release of Rush, replace this `require` call with a dynamic import, and // change this function to be async. const DependencyAnalyzerModule: typeof DependencyAnalyzerModuleType = require('../logic/DependencyAnalyzer'); const dependencyAnalyzer: DependencyAnalyzerModuleType.DependencyAnalyzer = DependencyAnalyzerModule.DependencyAnalyzer.forRushConfiguration(this); const dependencyAnalysis: DependencyAnalyzerModuleType.IDependencyAnalysis = - dependencyAnalyzer.getAnalysis(subspace); + dependencyAnalyzer.getAnalysis(subspace, variant); return dependencyAnalysis.implicitlyPreferredVersionByPackageName; } @@ -1423,10 +1480,10 @@ export class RushConfiguration { } /** - * @deprecated Use {@link Subspace.getCommittedShrinkwrapFilename} instead + * @deprecated Use {@link Subspace.getCommittedShrinkwrapFilePath} instead */ - public getCommittedShrinkwrapFilename(subspace?: Subspace): string { - return (subspace ?? this.defaultSubspace).getCommittedShrinkwrapFilename(); + public getCommittedShrinkwrapFilename(subspace?: Subspace, variant?: string): string { + return (subspace ?? this.defaultSubspace).getCommittedShrinkwrapFilePath(variant); } /** @@ -1518,4 +1575,14 @@ export class RushConfiguration { } return undefined; } + + public async _loadCurrentVariantJsonAsync(): Promise { + try { + return await JsonFile.loadAsync(this.currentVariantJsonFilePath); + } catch (e) { + if (!FileSystem.isNotExistError(e)) { + throw e; + } + } + } } diff --git a/libraries/rush-lib/src/api/Subspace.ts b/libraries/rush-lib/src/api/Subspace.ts index 239feed81bc..22ded62cbef 100644 --- a/libraries/rush-lib/src/api/Subspace.ts +++ b/libraries/rush-lib/src/api/Subspace.ts @@ -199,6 +199,28 @@ export class Subspace { return this._detail; } + /** + * Returns the full path of the folder containing this subspace's variant-dependent configuration files + * such as `pnpm-lock.yaml`. + * + * Example: `common/config/subspaces/my-subspace` or `common/config/subspaces/my-subspace/variants/my-variant` + * @beta + * + * @remarks + * The following files may be variant-dependent: + * - Lockfiles: (i.e. - `pnpm-lock.yaml`, `npm-shrinkwrap.json`, `yarn.lock`, etc) + * - 'common-versions.json' + * - 'pnpmfile.js'/'.pnpmfile.cjs' + */ + public getVariantDependentSubspaceConfigFolderPath(variant: string | undefined): string { + const subspaceConfigFolderPath: string = this.getSubspaceConfigFolderPath(); + if (!variant) { + return subspaceConfigFolderPath; + } else { + return `${subspaceConfigFolderPath}/${RushConstants.rushVariantsFolderName}/${variant}`; + } + } + /** * Returns the full path of the folder containing this subspace's configuration files such as `pnpm-lock.yaml`. * @@ -265,8 +287,10 @@ export class Subspace { * Example: `C:\MyRepo\common\subspaces\my-subspace\common-versions.json` * @beta */ - public getCommonVersionsFilePath(): string { - return this._ensureDetail().subspaceConfigFolderPath + '/' + RushConstants.commonVersionsFilename; + public getCommonVersionsFilePath(variant?: string): string { + return ( + this.getVariantDependentSubspaceConfigFolderPath(variant) + '/' + RushConstants.commonVersionsFilename + ); } /** @@ -276,15 +300,15 @@ export class Subspace { * @beta */ public getPnpmConfigFilePath(): string { - return this._ensureDetail().subspaceConfigFolderPath + '/' + RushConstants.pnpmConfigFilename; + return this.getSubspaceConfigFolderPath() + '/' + RushConstants.pnpmConfigFilename; } /** * Gets the settings from the common-versions.json config file. * @beta */ - public getCommonVersions(): CommonVersionsConfiguration { - const commonVersionsFilename: string = this.getCommonVersionsFilePath(); + public getCommonVersions(variant?: string): CommonVersionsConfiguration { + const commonVersionsFilename: string = this.getCommonVersionsFilePath(variant); if (!this._commonVersionsConfiguration) { this._commonVersionsConfiguration = CommonVersionsConfiguration.loadFromFile( commonVersionsFilename, @@ -299,13 +323,13 @@ export class Subspace { * or from the rush.json file if it isn't defined in common-versions.json * @beta */ - public get shouldEnsureConsistentVersions(): boolean { + public shouldEnsureConsistentVersions(variant?: string): boolean { // If the subspaces feature is enabled, or the ensureConsistentVersions field is defined, return the value of the field - if ( - this._rushConfiguration.subspacesFeatureEnabled || - this.getCommonVersions().ensureConsistentVersions !== undefined - ) { - return !!this.getCommonVersions().ensureConsistentVersions; + if (this._rushConfiguration.subspacesFeatureEnabled) { + const commonVersions: CommonVersionsConfiguration = this.getCommonVersions(variant); + if (commonVersions.ensureConsistentVersions !== undefined) { + return commonVersions.ensureConsistentVersions; + } } // Fallback to ensureConsistentVersions in rush.json if subspaces is not enabled, @@ -318,7 +342,7 @@ export class Subspace { * @beta */ public getRepoStateFilePath(): string { - return this._ensureDetail().subspaceConfigFolderPath + '/' + RushConstants.repoStateFilename; + return this.getSubspaceConfigFolderPath() + '/' + RushConstants.repoStateFilename; } /** @@ -332,11 +356,12 @@ export class Subspace { } /** - * Gets the committed shrinkwrap file name. + * 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.getSubspaceConfigFolderPath(); + public getCommittedShrinkwrapFilePath(variant?: string): string { + const subspaceConfigFolderPath: string = this.getVariantDependentSubspaceConfigFolderPath(variant); return path.join(subspaceConfigFolderPath, this._rushConfiguration.shrinkwrapFilename); } @@ -373,12 +398,12 @@ export class Subspace { * Returns hash value of injected dependencies in related package.json. * @beta */ - public getPackageJsonInjectedDependenciesHash(): string | undefined { + public getPackageJsonInjectedDependenciesHash(variant: string | undefined): string | undefined { const allPackageJson: IPackageJsonLite[] = []; const relatedProjects: RushConfigurationProject[] = []; const subspacePnpmfileShimSettings: ISubspacePnpmfileShimSettings = - SubspacePnpmfileConfiguration.getSubspacePnpmfileShimSettings(this._rushConfiguration, this); + SubspacePnpmfileConfiguration.getSubspacePnpmfileShimSettings(this._rushConfiguration, this, variant); for (const rushProject of this.getProjects()) { const injectedDependencies: Array = diff --git a/libraries/rush-lib/src/api/Variants.ts b/libraries/rush-lib/src/api/Variants.ts new file mode 100644 index 00000000000..57bb125c6a2 --- /dev/null +++ b/libraries/rush-lib/src/api/Variants.ts @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { CommandLineStringParameter, ICommandLineStringDefinition } from '@rushstack/ts-command-line'; + +import { EnvironmentVariableNames } from './EnvironmentConfiguration'; +import type { RushConfiguration } from './RushConfiguration'; +import { RushConstants } from '../logic/RushConstants'; + +/** + * Provides the parameter configuration for '--variant'. + */ +export const VARIANT_PARAMETER: ICommandLineStringDefinition = { + parameterLongName: '--variant', + argumentName: 'VARIANT', + description: 'Run command using a variant installation configuration', + environmentVariable: EnvironmentVariableNames.RUSH_VARIANT +}; + +export function getVariant( + variantsParameter: CommandLineStringParameter, + rushConfiguration: RushConfiguration +): string | undefined { + const variant: string | undefined = variantsParameter.value; + if (variant && !rushConfiguration.variants.has(variant)) { + throw new Error(`The variant "${variant}" is not defined in ${RushConstants.rushJsonFilename}`); + } + + return variant; +} diff --git a/libraries/rush-lib/src/api/test/__snapshots__/RushCommandLine.test.ts.snap b/libraries/rush-lib/src/api/test/__snapshots__/RushCommandLine.test.ts.snap index 991aa424551..b630b43a6ad 100644 --- a/libraries/rush-lib/src/api/test/__snapshots__/RushCommandLine.test.ts.snap +++ b/libraries/rush-lib/src/api/test/__snapshots__/RushCommandLine.test.ts.snap @@ -160,6 +160,14 @@ Object { Object { "actionName": "check", "parameters": Array [ + Object { + "description": "Run command using a variant installation configuration", + "environmentVariable": "RUSH_VARIANT", + "kind": "String", + "longName": "--variant", + "required": false, + "shortName": undefined, + }, Object { "description": "If this flag is specified, output will be in JSON format.", "environmentVariable": undefined, @@ -382,6 +390,14 @@ Object { "required": false, "shortName": undefined, }, + Object { + "description": "Run command using a variant installation configuration", + "environmentVariable": "RUSH_VARIANT", + "kind": "String", + "longName": "--variant", + "required": false, + "shortName": undefined, + }, Object { "description": "Normally all projects in the monorepo will be processed; adding this parameter will instead select a subset of projects. Each \\"--to\\" parameter expands this selection to include PROJECT and all its dependencies. \\".\\" can be used as shorthand for the project in the current working directory. For details, refer to the website article \\"Selecting subsets of projects\\".", "environmentVariable": undefined, @@ -897,6 +913,14 @@ Object { "required": false, "shortName": undefined, }, + Object { + "description": "Run command using a variant installation configuration", + "environmentVariable": "RUSH_VARIANT", + "kind": "String", + "longName": "--variant", + "required": false, + "shortName": undefined, + }, Object { "description": "Normally \\"rush update\\" tries to preserve your existing installed versions and only makes the minimum updates needed to satisfy the package.json files. This conservative approach prevents your PR from getting involved with package updates that are unrelated to your work. Use \\"--full\\" when you really want to update all dependencies to the latest SemVer-compatible version. This should be done periodically by a person or robot whose role is to deal with potential upgrade regressions.", "environmentVariable": undefined, @@ -989,6 +1013,14 @@ Object { "required": false, "shortName": "-s", }, + Object { + "description": "Run command using a variant installation configuration", + "environmentVariable": "RUSH_VARIANT", + "kind": "String", + "longName": "--variant", + "required": false, + "shortName": undefined, + }, ], }, Object { diff --git a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts index 2f404435424..f054e4bbe00 100644 --- a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts +++ b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts @@ -32,6 +32,7 @@ import { objectsAreDeepEqual } from '../utilities/objectUtilities'; import { Utilities } from '../utilities/Utilities'; import type { Subspace } from '../api/Subspace'; import type { PnpmOptionsConfiguration } from '../logic/pnpm/PnpmOptionsConfiguration'; +import { EnvironmentVariableNames } from '../api/EnvironmentConfiguration'; const RUSH_SKIP_CHECKS_PARAMETER: string = '--rush-skip-checks'; @@ -460,7 +461,6 @@ export class RushPnpmCommandLineParser { } const subspaceTempFolder: string = this._subspace.getSubspaceTempFolderPath(); - const subspaceConfigFolder: string = this._subspace.getSubspaceConfigFolderPath(); switch (commandName) { case 'patch-remove': @@ -470,6 +470,7 @@ export class RushPnpmCommandLineParser { // 2. we can not fallback to use Monorepo config folder (common/config/rush) due to that this command is intended to apply to input subspace only. // It will produce unexpected behavior if we use the fallback. if (this._subspace.getPnpmOptions() === undefined) { + const subspaceConfigFolder: string = this._subspace.getSubspaceConfigFolderPath(); this._terminal.writeErrorLine( `The "rush-pnpm patch-commit" command cannot proceed without a pnpm-config.json file.` + ` Create one in this folder: ${subspaceConfigFolder}` @@ -543,6 +544,7 @@ export class RushPnpmCommandLineParser { networkConcurrency: undefined, offline: false, collectLogFile: false, + variant: process.env[EnvironmentVariableNames.RUSH_VARIANT], // For `rush-pnpm`, only use the env var maxInstallAttempts: RushConstants.defaultMaxInstallAttempts, pnpmFilterArgumentValues: [], selectedProjects: new Set(this._rushConfiguration.projects), diff --git a/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts b/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts index 413089788b7..6460b8e5530 100644 --- a/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts +++ b/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts @@ -4,6 +4,7 @@ import type { CommandLineFlagParameter, CommandLineIntegerParameter, + CommandLineStringParameter, IRequiredCommandLineIntegerParameter } from '@rushstack/ts-command-line'; import { AlreadyReportedError } from '@rushstack/node-core-library'; @@ -22,6 +23,7 @@ import { RushConstants } from '../../logic/RushConstants'; import { SUBSPACE_LONG_ARG_NAME, type SelectionParameterSet } from '../parsing/SelectionParameterSet'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; import type { Subspace } from '../../api/Subspace'; +import { getVariant, VARIANT_PARAMETER } from '../../api/Variants'; /** * Temporary data structure used by `BaseInstallAction.runAsync()` @@ -36,6 +38,7 @@ interface ISubspaceInstallationData { */ export abstract class BaseInstallAction extends BaseRushAction { protected readonly _terminal: ITerminal; + protected readonly _variantParameter: CommandLineStringParameter; protected readonly _purgeParameter: CommandLineFlagParameter; protected readonly _bypassPolicyParameter: CommandLineFlagParameter; protected readonly _noLinkParameter: CommandLineFlagParameter; @@ -105,6 +108,7 @@ export abstract class BaseInstallAction extends BaseRushAction { ` if the necessary NPM packages cannot be obtained from the local cache.` + ` For details, see the documentation for PNPM's "--offline" parameter.` }); + this._variantParameter = this.defineStringParameter(VARIANT_PARAMETER); } protected abstract buildInstallOptionsAsync(): Promise>; @@ -113,16 +117,6 @@ export abstract class BaseInstallAction extends BaseRushAction { const installManagerOptions: Omit = await this.buildInstallOptionsAsync(); - if (this.rushConfiguration._hasVariantsField) { - this._terminal.writeLine( - Colorize.yellow( - `Warning: Please remove the obsolete "variants" field from your ${RushConstants.rushJsonFilename} ` + - 'file. Installation variants have been replaced by the new Rush subspaces feature. ' + - 'In the next major release, Rush will fail to execute if this field is present.' - ) - ); - } - // 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 selectedSubspaces: ReadonlySet | undefined; const subspaceInstallationDataBySubspace: Map = new Map(); @@ -175,15 +169,19 @@ export abstract class BaseInstallAction extends BaseRushAction { } } + const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); if (selectedSubspaces) { // Check each subspace for version inconsistencies for (const subspace of selectedSubspaces) { VersionMismatchFinder.ensureConsistentVersions(this.rushConfiguration, this._terminal, { - subspace + subspace, + variant }); } } else { - VersionMismatchFinder.ensureConsistentVersions(this.rushConfiguration, this._terminal); + VersionMismatchFinder.ensureConsistentVersions(this.rushConfiguration, this._terminal, { + variant + }); } const stopwatch: Stopwatch = Stopwatch.start(); diff --git a/libraries/rush-lib/src/cli/actions/CheckAction.ts b/libraries/rush-lib/src/cli/actions/CheckAction.ts index e922d9ca833..f3bd0db0baa 100644 --- a/libraries/rush-lib/src/cli/actions/CheckAction.ts +++ b/libraries/rush-lib/src/cli/actions/CheckAction.ts @@ -2,17 +2,19 @@ // See LICENSE in the project root for license information. import type { CommandLineFlagParameter, CommandLineStringParameter } from '@rushstack/ts-command-line'; -import { ConsoleTerminalProvider, type ITerminal, Terminal } from '@rushstack/terminal'; +import { Colorize, ConsoleTerminalProvider, type ITerminal, Terminal } from '@rushstack/terminal'; import type { RushCommandLineParser } from '../RushCommandLineParser'; import { BaseRushAction } from './BaseRushAction'; import { VersionMismatchFinder } from '../../logic/versionMismatch/VersionMismatchFinder'; +import { getVariant, VARIANT_PARAMETER } from '../../api/Variants'; export class CheckAction extends BaseRushAction { private readonly _terminal: ITerminal; private readonly _jsonFlag: CommandLineFlagParameter; private readonly _verboseFlag: CommandLineFlagParameter; private readonly _subspaceParameter: CommandLineStringParameter | undefined; + private readonly _variantParameter: CommandLineStringParameter; public constructor(parser: RushCommandLineParser) { super({ @@ -46,6 +48,7 @@ export class CheckAction extends BaseRushAction { 'consistent only within that subspace (ignoring other subspaces). This parameter is required when ' + 'the "subspacesEnabled" setting is set to true in subspaces.json.' }); + this._variantParameter = this.defineStringParameter(VARIANT_PARAMETER); } protected async runAsync(): Promise { @@ -54,7 +57,22 @@ export class CheckAction extends BaseRushAction { `The --subspace parameter must be specified with "rush check" when subspaces is enabled.` ); } + + const currentlyInstalledVariant: string | undefined = + await this.rushConfiguration.getCurrentlyInstalledVariantAsync(); + const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); + if (!variant && currentlyInstalledVariant) { + // eslint-disable-next-line no-console + this._terminal.writeWarningLine( + Colorize.yellow( + `Variant '${currentlyInstalledVariant}' has been installed, but 'rush check' is currently checking the default variant. ` + + `Use 'rush ${this.actionName} ${this._variantParameter.longName} '${currentlyInstalledVariant}' to check the current installation.` + ) + ); + } + VersionMismatchFinder.rushCheck(this.rushConfiguration, this._terminal, { + variant, printAsJson: this._jsonFlag.value, truncateLongPackageNameLists: !this._verboseFlag.value, subspace: this._subspaceParameter?.value diff --git a/libraries/rush-lib/src/cli/actions/DeployAction.ts b/libraries/rush-lib/src/cli/actions/DeployAction.ts index 2d0b0d1e2da..c254184840e 100644 --- a/libraries/rush-lib/src/cli/actions/DeployAction.ts +++ b/libraries/rush-lib/src/cli/actions/DeployAction.ts @@ -160,10 +160,13 @@ export class DeployAction extends BaseRushAction { const projects: RushConfigurationProject[] = this.rushConfiguration.projects; if (this.rushConfiguration.packageManager === 'pnpm') { + const currentlyInstalledVariant: string | undefined = + await this.rushConfiguration.getCurrentlyInstalledVariantAsync(); for (const project of projects) { const pnpmfileConfiguration: PnpmfileConfiguration = await PnpmfileConfiguration.initializeAsync( this.rushConfiguration, - project.subspace + project.subspace, + currentlyInstalledVariant ); const subspace: IExtractorSubspace = { subspaceName: project.subspace.subspaceName, diff --git a/libraries/rush-lib/src/cli/actions/InstallAction.ts b/libraries/rush-lib/src/cli/actions/InstallAction.ts index ceb55884326..63e9e1f89b0 100644 --- a/libraries/rush-lib/src/cli/actions/InstallAction.ts +++ b/libraries/rush-lib/src/cli/actions/InstallAction.ts @@ -9,6 +9,7 @@ import type { RushCommandLineParser } from '../RushCommandLineParser'; import { SelectionParameterSet } from '../parsing/SelectionParameterSet'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; import type { Subspace } from '../../api/Subspace'; +import { getVariant } from '../../api/Variants'; export class InstallAction extends BaseInstallAction { private readonly _checkOnlyParameter: CommandLineFlagParameter; @@ -61,6 +62,8 @@ export class InstallAction extends BaseInstallAction { (await this._selectionParameters?.getSelectedProjectsAsync(this._terminal)) ?? new Set(this.rushConfiguration.projects); + const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); + return { debug: this.parser.isDebug, allowShrinkwrapUpdates: false, @@ -72,6 +75,7 @@ export class InstallAction extends BaseInstallAction { offline: this._offlineParameter.value!, networkConcurrency: this._networkConcurrencyParameter.value, collectLogFile: this._debugPackageManagerParameter.value!, + variant, // Because the 'defaultValue' option on the _maxInstallAttempts parameter is set, // it is safe to assume that the value is not null maxInstallAttempts: this._maxInstallAttempts.value!, diff --git a/libraries/rush-lib/src/cli/actions/PublishAction.ts b/libraries/rush-lib/src/cli/actions/PublishAction.ts index 90d6266534b..12170823a0f 100644 --- a/libraries/rush-lib/src/cli/actions/PublishAction.ts +++ b/libraries/rush-lib/src/cli/actions/PublishAction.ts @@ -213,9 +213,12 @@ export class PublishAction extends BaseRushAction { * Executes the publish action, which will read change request files, apply changes to package.jsons, */ protected async runAsync(): Promise { + const currentlyInstalledVariant: string | undefined = + await this.rushConfiguration.getCurrentlyInstalledVariantAsync(); await PolicyValidator.validatePolicyAsync( this.rushConfiguration, this.rushConfiguration.defaultSubspace, + currentlyInstalledVariant, { bypassPolicy: false } ); diff --git a/libraries/rush-lib/src/cli/actions/UpdateAction.ts b/libraries/rush-lib/src/cli/actions/UpdateAction.ts index 9cb780bf50f..3e6c8d71b95 100644 --- a/libraries/rush-lib/src/cli/actions/UpdateAction.ts +++ b/libraries/rush-lib/src/cli/actions/UpdateAction.ts @@ -9,6 +9,7 @@ import type { RushCommandLineParser } from '../RushCommandLineParser'; import { SelectionParameterSet } from '../parsing/SelectionParameterSet'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; import type { Subspace } from '../../api/Subspace'; +import { getVariant } from '../../api/Variants'; export class UpdateAction extends BaseInstallAction { private readonly _fullParameter: CommandLineFlagParameter; @@ -84,6 +85,8 @@ export class UpdateAction extends BaseInstallAction { (await this._selectionParameters?.getSelectedProjectsAsync(this._terminal)) ?? new Set(this.rushConfiguration.projects); + const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); + return { debug: this.parser.isDebug, allowShrinkwrapUpdates: true, @@ -95,6 +98,7 @@ export class UpdateAction extends BaseInstallAction { offline: this._offlineParameter.value!, networkConcurrency: this._networkConcurrencyParameter.value, collectLogFile: this._debugPackageManagerParameter.value!, + variant, // Because the 'defaultValue' option on the _maxInstallAttempts parameter is set, // it is safe to assume that the value is not null maxInstallAttempts: this._maxInstallAttempts.value!, diff --git a/libraries/rush-lib/src/cli/actions/UpgradeInteractiveAction.ts b/libraries/rush-lib/src/cli/actions/UpgradeInteractiveAction.ts index 012b457dd3b..ffc1468f7c8 100644 --- a/libraries/rush-lib/src/cli/actions/UpgradeInteractiveAction.ts +++ b/libraries/rush-lib/src/cli/actions/UpgradeInteractiveAction.ts @@ -1,16 +1,18 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import type { CommandLineFlagParameter } from '@rushstack/ts-command-line'; +import type { CommandLineFlagParameter, CommandLineStringParameter } from '@rushstack/ts-command-line'; import type { RushCommandLineParser } from '../RushCommandLineParser'; import { BaseRushAction } from './BaseRushAction'; import type * as PackageJsonUpdaterType from '../../logic/PackageJsonUpdater'; import type * as InteractiveUpgraderType from '../../logic/InteractiveUpgrader'; +import { getVariant, VARIANT_PARAMETER } from '../../api/Variants'; export class UpgradeInteractiveAction extends BaseRushAction { private _makeConsistentFlag: CommandLineFlagParameter; private _skipUpdateFlag: CommandLineFlagParameter; + private readonly _variantParameter: CommandLineStringParameter; public constructor(parser: RushCommandLineParser) { const documentation: string[] = [ @@ -42,6 +44,8 @@ export class UpgradeInteractiveAction extends BaseRushAction { description: 'If specified, the "rush update" command will not be run after updating the package.json files.' }); + + this._variantParameter = this.defineStringParameter(VARIANT_PARAMETER); } public async runAsync(): Promise { @@ -58,17 +62,22 @@ export class UpgradeInteractiveAction extends BaseRushAction { this.rushConfiguration ); + const variant: string | undefined = + getVariant(this._variantParameter, this.rushConfiguration) ?? + (await this.rushConfiguration.getCurrentlyInstalledVariantAsync()); const shouldMakeConsistent: boolean = - this.rushConfiguration.defaultSubspace.shouldEnsureConsistentVersions || this._makeConsistentFlag.value; + this.rushConfiguration.defaultSubspace.shouldEnsureConsistentVersions(variant) || + this._makeConsistentFlag.value; const { projects, depsToUpgrade } = await interactiveUpgrader.upgradeAsync(); await packageJsonUpdater.doRushUpgradeAsync({ - projects: projects, + projects, packagesToAdd: depsToUpgrade.packages, updateOtherPackages: shouldMakeConsistent, skipUpdate: this._skipUpdateFlag.value, - debugInstall: this.parser.isDebug + debugInstall: this.parser.isDebug, + variant }); } } diff --git a/libraries/rush-lib/src/cli/actions/VersionAction.ts b/libraries/rush-lib/src/cli/actions/VersionAction.ts index 228950d2ea1..54070750902 100644 --- a/libraries/rush-lib/src/cli/actions/VersionAction.ts +++ b/libraries/rush-lib/src/cli/actions/VersionAction.ts @@ -95,8 +95,10 @@ export class VersionAction extends BaseRushAction { } protected async runAsync(): Promise { + const currentlyInstalledVariant: string | undefined = + await this.rushConfiguration.getCurrentlyInstalledVariantAsync(); for (const subspace of this.rushConfiguration.subspaces) { - await PolicyValidator.validatePolicyAsync(this.rushConfiguration, subspace, { + await PolicyValidator.validatePolicyAsync(this.rushConfiguration, subspace, currentlyInstalledVariant, { bypassPolicyAllowed: true, bypassPolicy: this._bypassPolicy.value }); @@ -128,7 +130,7 @@ export class VersionAction extends BaseRushAction { if (updatedPackages.size > 0) { // eslint-disable-next-line no-console console.log(`${updatedPackages.size} packages are getting updated.`); - await this._gitProcessAsync(tempBranch, this._targetBranch.value); + await this._gitProcessAsync(tempBranch, this._targetBranch.value, currentlyInstalledVariant); } } else if (this._bumpVersion.value) { const tempBranch: string = 'version/bump-' + new Date().getTime(); @@ -138,7 +140,7 @@ export class VersionAction extends BaseRushAction { this._prereleaseIdentifier.value, true ); - await this._gitProcessAsync(tempBranch, this._targetBranch.value); + await this._gitProcessAsync(tempBranch, this._targetBranch.value, currentlyInstalledVariant); } } @@ -205,7 +207,7 @@ export class VersionAction extends BaseRushAction { } } - private _validateResult(): void { + private _validateResult(variant: string | undefined): void { // Load the config from file to avoid using inconsistent in-memory data. const rushConfig: RushConfiguration = RushConfiguration.loadFromConfigurationFile( this.rushConfiguration.rushJsonFile @@ -219,7 +221,8 @@ export class VersionAction extends BaseRushAction { } const mismatchFinder: VersionMismatchFinder = VersionMismatchFinder.getMismatches(rushConfig, { - subspace: subspace + subspace, + variant }); if (mismatchFinder.numberOfMismatches) { throw new Error( @@ -230,9 +233,13 @@ export class VersionAction extends BaseRushAction { } } - private async _gitProcessAsync(tempBranch: string, targetBranch: string | undefined): Promise { + private async _gitProcessAsync( + tempBranch: string, + targetBranch: string | undefined, + variant: string | undefined + ): Promise { // Validate the result before commit. - this._validateResult(); + this._validateResult(variant); const git: Git = new Git(this.rushConfiguration); const publishGit: PublishGit = new PublishGit(git, targetBranch); diff --git a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts index 96270c35b5e..2f4d752227f 100644 --- a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts @@ -289,16 +289,20 @@ export class PhasedScriptAction extends BaseScriptAction { public async runAsync(): Promise { if (this._alwaysInstall || this._installParameter?.value) { - const { doBasicInstallAsync } = await import( - /* webpackChunkName: 'doBasicInstallAsync' */ - '../../logic/installManager/doBasicInstallAsync' - ); + const [{ doBasicInstallAsync }, currentlyInstalledVariant] = await Promise.all([ + import( + /* webpackChunkName: 'doBasicInstallAsync' */ + '../../logic/installManager/doBasicInstallAsync' + ), + this.rushConfiguration.getCurrentlyInstalledVariantAsync() + ]); await doBasicInstallAsync({ terminal: this._terminal, rushConfiguration: this.rushConfiguration, rushGlobalFolder: this.rushGlobalFolder, isDebug: this.parser.isDebug, + variant: currentlyInstalledVariant, beforeInstallAsync: (subspace: Subspace) => this.rushSession.hooks.beforeInstall.promise(this, subspace), afterInstallAsync: (subspace: Subspace) => this.rushSession.hooks.afterInstall.promise(this, subspace) diff --git a/libraries/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap b/libraries/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap index be766d0245c..ff2c10abf36 100644 --- a/libraries/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap +++ b/libraries/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap @@ -346,13 +346,18 @@ Optional arguments: `; exports[`CommandLineHelp prints the help for each action: check 1`] = ` -"usage: rush check [-h] [--json] [--verbose] [--subspace SUBSPACE_NAME] +"usage: rush check [-h] [--variant VARIANT] [--json] [--verbose] + [--subspace SUBSPACE_NAME] + Checks each project's package.json files and ensures that all dependencies are of the same version throughout the repository. Optional arguments: -h, --help Show this help message and exit. + --variant VARIANT Run command using a variant installation + configuration. This parameter may alternatively be + specified via the RUSH_VARIANT environment variable. --json If this flag is specified, output will be in JSON format. --verbose If this flag is specified, long lists of package @@ -624,8 +629,8 @@ exports[`CommandLineHelp prints the help for each action: install 1`] = ` "usage: rush install [-h] [-p] [--bypass-policy] [--no-link] [--network-concurrency COUNT] [--debug-package-manager] [--max-install-attempts NUMBER] [--ignore-hooks] - [--offline] [-t PROJECT] [-T PROJECT] [-f PROJECT] - [-o PROJECT] [-i PROJECT] [-I PROJECT] + [--offline] [--variant VARIANT] [-t PROJECT] [-T PROJECT] + [-f PROJECT] [-o PROJECT] [-i PROJECT] [-I PROJECT] [--to-version-policy VERSION_POLICY_NAME] [--from-version-policy VERSION_POLICY_NAME] [--subspace SUBSPACE_NAME] [--check-only] @@ -675,6 +680,9 @@ Optional arguments: necessary NPM packages cannot be obtained from the local cache. For details, see the documentation for PNPM's \\"--offline\\" parameter. + --variant VARIANT Run command using a variant installation + configuration. This parameter may alternatively be + specified via the RUSH_VARIANT environment variable. -t PROJECT, --to PROJECT Normally all projects in the monorepo will be processed; adding this parameter will instead select @@ -1223,7 +1231,7 @@ exports[`CommandLineHelp prints the help for each action: update 1`] = ` "usage: rush update [-h] [-p] [--bypass-policy] [--no-link] [--network-concurrency COUNT] [--debug-package-manager] [--max-install-attempts NUMBER] [--ignore-hooks] - [--offline] [--full] [--recheck] + [--offline] [--variant VARIANT] [--full] [--recheck] The \\"rush update\\" command installs the dependencies described in your package. @@ -1268,6 +1276,9 @@ Optional arguments: necessary NPM packages cannot be obtained from the local cache. For details, see the documentation for PNPM's \\"--offline\\" parameter. + --variant VARIANT Run command using a variant installation + configuration. This parameter may alternatively be + specified via the RUSH_VARIANT environment variable. --full Normally \\"rush update\\" tries to preserve your existing installed versions and only makes the minimum updates needed to satisfy the package.json @@ -1324,6 +1335,8 @@ Optional arguments: exports[`CommandLineHelp prints the help for each action: upgrade-interactive 1`] = ` "usage: rush upgrade-interactive [-h] [--make-consistent] [-s] + [--variant VARIANT] + Provide an interactive way to upgrade your dependencies. Running the command will open an interactive prompt that will ask you which projects and which @@ -1341,6 +1354,9 @@ Optional arguments: upgrade dependencies from other projects. -s, --skip-update If specified, the \\"rush update\\" command will not be run after updating the package.json files. + --variant VARIANT Run command using a variant installation configuration. + This parameter may alternatively be specified via the + RUSH_VARIANT environment variable. " `; diff --git a/libraries/rush-lib/src/logic/DependencyAnalyzer.ts b/libraries/rush-lib/src/logic/DependencyAnalyzer.ts index 3ffba41fcb9..16b70ff66ca 100644 --- a/libraries/rush-lib/src/logic/DependencyAnalyzer.ts +++ b/libraries/rush-lib/src/logic/DependencyAnalyzer.ts @@ -33,7 +33,7 @@ export class DependencyAnalyzer { | undefined; private _rushConfiguration: RushConfiguration; - private _analysisBySubspace: WeakMap | undefined; + private _analysisByVariantBySubspace: Map> | undefined; private constructor(rushConfiguration: RushConfiguration) { this._rushConfiguration = rushConfiguration; @@ -54,20 +54,37 @@ export class DependencyAnalyzer { return analyzer; } - public getAnalysis(subspace?: Subspace, addAction?: boolean): IDependencyAnalysis { - if (!this._analysisBySubspace) { - this._analysisBySubspace = new WeakMap(); + public getAnalysis(subspace?: Subspace, variant?: string, addAction?: boolean): IDependencyAnalysis { + // Use an empty string as the key when no variant provided. Anything else would possibly conflict + // with a variant created by the user + const variantKey: string = variant || ''; + + if (!this._analysisByVariantBySubspace) { + this._analysisByVariantBySubspace = new Map(); } const subspaceToAnalyze: Subspace = subspace || this._rushConfiguration.defaultSubspace; - if (!this._analysisBySubspace.has(subspaceToAnalyze)) { - this._analysisBySubspace.set( - subspaceToAnalyze, - this._getAnalysisInternal(subspace || this._rushConfiguration.defaultSubspace, addAction) + let analysisForVariant: WeakMap | undefined = + this._analysisByVariantBySubspace.get(variantKey); + + if (!analysisForVariant) { + analysisForVariant = new WeakMap(); + this._analysisByVariantBySubspace.set(variantKey, analysisForVariant); + } + + let analysisForSubspace: IDependencyAnalysis | undefined = analysisForVariant.get(subspaceToAnalyze); + + if (!analysisForSubspace) { + analysisForSubspace = this._getAnalysisInternal( + subspace || this._rushConfiguration.defaultSubspace, + variant, + addAction ); + + analysisForVariant.set(subspaceToAnalyze, analysisForSubspace); } - return this._analysisBySubspace.get(subspaceToAnalyze) as IDependencyAnalysis; + return analysisForSubspace; } /** @@ -76,8 +93,12 @@ export class DependencyAnalyzer { * @remarks * The result of this function is not cached. */ - private _getAnalysisInternal(subspace: Subspace, addAction?: boolean): IDependencyAnalysis { - const commonVersionsConfiguration: CommonVersionsConfiguration = subspace.getCommonVersions(); + private _getAnalysisInternal( + subspace: Subspace, + variant: string | undefined, + addAction?: boolean + ): IDependencyAnalysis { + const commonVersionsConfiguration: CommonVersionsConfiguration = subspace.getCommonVersions(variant); const allVersionsByPackageName: Map> = new Map(); const allowedAlternativeVersions: Map< string, diff --git a/libraries/rush-lib/src/logic/PackageJsonUpdater.ts b/libraries/rush-lib/src/logic/PackageJsonUpdater.ts index dac1787ed4d..ffcfcb54098 100644 --- a/libraries/rush-lib/src/logic/PackageJsonUpdater.ts +++ b/libraries/rush-lib/src/logic/PackageJsonUpdater.ts @@ -53,6 +53,10 @@ export interface IPackageJsonUpdaterRushUpgradeOptions { * If specified, "rush update" will be run in debug mode. */ debugInstall: boolean; + /** + * The variant to consider when performing installations and validating shrinkwrap updates. + */ + variant: string | undefined; } /** @@ -112,7 +116,7 @@ export class PackageJsonUpdater { * "rush upgrade-interactive". */ public async doRushUpgradeAsync(options: IPackageJsonUpdaterRushUpgradeOptions): Promise { - const { projects, packagesToAdd, updateOtherPackages, skipUpdate, debugInstall } = options; + const { projects, packagesToAdd, updateOtherPackages, skipUpdate, debugInstall, variant } = options; const { DependencyAnalyzer } = await import( /* webpackChunkName: 'DependencyAnalyzer' */ './DependencyAnalyzer' @@ -216,7 +220,8 @@ export class PackageJsonUpdater { if (updateOtherPackages) { const mismatchFinder: VersionMismatchFinder = VersionMismatchFinder.getMismatches( - this._rushConfiguration + this._rushConfiguration, + options ); for (const update of this._getUpdates(mismatchFinder, allDependenciesToUpdate)) { this.updateProject(update); @@ -236,10 +241,10 @@ export class PackageJsonUpdater { options.projects ); for (const subspace of subspaceSet) { - await this._doUpdateAsync(debugInstall, subspace); + await this._doUpdateAsync(debugInstall, subspace, variant); } } else { - await this._doUpdateAsync(debugInstall, this._rushConfiguration.defaultSubspace); + await this._doUpdateAsync(debugInstall, this._rushConfiguration.defaultSubspace, variant); } } } @@ -253,7 +258,7 @@ export class PackageJsonUpdater { } else { throw new Error('only accept "rush add" or "rush remove"'); } - const { skipUpdate, debugInstall } = options; + const { skipUpdate, debugInstall, variant } = options; for (const { project } of allPackageUpdates) { if (project.saveIfModified()) { this._terminal.writeLine(Colorize.green('Wrote'), project.filePath); @@ -266,15 +271,19 @@ export class PackageJsonUpdater { options.projects ); for (const subspace of subspaceSet) { - await this._doUpdateAsync(debugInstall, subspace); + await this._doUpdateAsync(debugInstall, subspace, variant); } } else { - await this._doUpdateAsync(debugInstall, this._rushConfiguration.defaultSubspace); + await this._doUpdateAsync(debugInstall, this._rushConfiguration.defaultSubspace, variant); } } } - private async _doUpdateAsync(debugInstall: boolean, subspace: Subspace): Promise { + private async _doUpdateAsync( + debugInstall: boolean, + subspace: Subspace, + variant: string | undefined + ): Promise { this._terminal.writeLine(); this._terminal.writeLine(Colorize.green('Running "rush update"')); this._terminal.writeLine(); @@ -290,6 +299,7 @@ export class PackageJsonUpdater { networkConcurrency: undefined, offline: false, collectLogFile: false, + variant, maxInstallAttempts: RushConstants.defaultMaxInstallAttempts, pnpmFilterArgumentValues: [], selectedProjects: new Set(this._rushConfiguration.projects), @@ -342,7 +352,8 @@ export class PackageJsonUpdater { dependencyAnalyzer: DependencyAnalyzer, options: IPackageJsonUpdaterRushAddOptions ): Promise { - const { projects, packagesToUpdate, devDependency, peerDependency, updateOtherPackages } = options; + const { projects, packagesToUpdate, devDependency, peerDependency, updateOtherPackages, variant } = + options; // Get projects for this subspace const subspaceProjects: RushConfigurationProject[] = projects.filter( @@ -353,7 +364,7 @@ export class PackageJsonUpdater { allVersionsByPackageName, implicitlyPreferredVersionByPackageName, commonVersionsConfiguration - }: IDependencyAnalysis = dependencyAnalyzer.getAnalysis(subspace, options.actionName === 'add'); + }: IDependencyAnalysis = dependencyAnalyzer.getAnalysis(subspace, variant, options.actionName === 'add'); this._terminal.writeLine(); const dependenciesToAddOrUpdate: Record = {}; @@ -418,7 +429,8 @@ export class PackageJsonUpdater { const mismatchFinder: VersionMismatchFinder = VersionMismatchFinder.getMismatches( this._rushConfiguration, { - subspace + subspace, + variant } ); otherPackageUpdates = this._getUpdates(mismatchFinder, Object.entries(dependenciesToAddOrUpdate)); diff --git a/libraries/rush-lib/src/logic/PackageJsonUpdaterTypes.ts b/libraries/rush-lib/src/logic/PackageJsonUpdaterTypes.ts index cc90cebd26c..00d4af19661 100644 --- a/libraries/rush-lib/src/logic/PackageJsonUpdaterTypes.ts +++ b/libraries/rush-lib/src/logic/PackageJsonUpdaterTypes.ts @@ -53,6 +53,10 @@ export interface IPackageJsonUpdaterRushBaseUpdateOptions { * actionName */ actionName: string; + /** + * The variant to consider when performing installations and validating shrinkwrap updates. + */ + variant?: string | undefined; } /** diff --git a/libraries/rush-lib/src/logic/RepoStateFile.ts b/libraries/rush-lib/src/logic/RepoStateFile.ts index d0efdee51d8..3bdff9898ed 100644 --- a/libraries/rush-lib/src/logic/RepoStateFile.ts +++ b/libraries/rush-lib/src/logic/RepoStateFile.ts @@ -151,7 +151,11 @@ export class RepoStateFile { * * @returns true if the file was modified, otherwise false. */ - public refreshState(rushConfiguration: RushConfiguration, subspace: Subspace | undefined): boolean { + public refreshState( + rushConfiguration: RushConfiguration, + subspace: Subspace | undefined, + variant?: string + ): boolean { if (subspace === undefined) { subspace = rushConfiguration.defaultSubspace; } @@ -163,7 +167,7 @@ export class RepoStateFile { rushConfiguration.pnpmOptions.preventManualShrinkwrapChanges; if (preventShrinkwrapChanges) { const pnpmShrinkwrapFile: PnpmShrinkwrapFile | undefined = PnpmShrinkwrapFile.loadFromFile( - subspace.getCommittedShrinkwrapFilename() + subspace.getCommittedShrinkwrapFilePath(variant) ); if (pnpmShrinkwrapFile) { @@ -185,7 +189,7 @@ export class RepoStateFile { const useWorkspaces: boolean = rushConfiguration.pnpmOptions && rushConfiguration.pnpmOptions.useWorkspaces; if (useWorkspaces) { - const commonVersions: CommonVersionsConfiguration = subspace.getCommonVersions(); + const commonVersions: CommonVersionsConfiguration = subspace.getCommonVersions(variant); const preferredVersionsHash: string = commonVersions.getPreferredVersionsHash(); if (this._preferredVersionsHash !== preferredVersionsHash) { this._preferredVersionsHash = preferredVersionsHash; @@ -198,7 +202,7 @@ export class RepoStateFile { if (rushConfiguration.packageManager === 'pnpm' && rushConfiguration.subspacesFeatureEnabled) { const packageJsonInjectedDependenciesHash: string | undefined = - subspace.getPackageJsonInjectedDependenciesHash(); + subspace.getPackageJsonInjectedDependenciesHash(variant); // packageJsonInjectedDependenciesHash is undefined, means there is no injected dependencies for that subspace // so we don't need to track the hash value for that subspace diff --git a/libraries/rush-lib/src/logic/RushConstants.ts b/libraries/rush-lib/src/logic/RushConstants.ts index ad0099d7cc1..fc1130f5593 100644 --- a/libraries/rush-lib/src/logic/RushConstants.ts +++ b/libraries/rush-lib/src/logic/RushConstants.ts @@ -52,6 +52,13 @@ export class RushConstants { */ public static readonly rushTempNpmScope: '@rush-temp' = '@rush-temp'; + /** + * The folder name ("variants") under which named variant configurations for + * alternate dependency sets may be found. + * Example: `C:\MyRepo\common\config\rush\variants` + */ + public static readonly rushVariantsFolderName: 'variants' = 'variants'; + /** * The folder name ("temp") under the common folder, or under the .rush folder in each project's directory where * temporary files will be stored. diff --git a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts index a7f25110613..0cd44fdef20 100644 --- a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts @@ -40,7 +40,7 @@ import { } from '../../api/LastInstallFlag'; import type { PnpmPackageManager } from '../../api/packageManager/PnpmPackageManager'; import type { PurgeManager } from '../PurgeManager'; -import type { RushConfiguration } from '../../api/RushConfiguration'; +import type { ICurrentVariantJson, RushConfiguration } from '../../api/RushConfiguration'; import { Rush } from '../../api/Rush'; import type { RushGlobalFolder } from '../../api/RushGlobalFolder'; import { RushConstants } from '../RushConstants'; @@ -114,7 +114,7 @@ export abstract class BaseInstallManager { } public async doInstallAsync(): Promise { - const { allowShrinkwrapUpdates, selectedProjects, pnpmFilterArgumentValues, resolutionOnly } = + const { allowShrinkwrapUpdates, selectedProjects, pnpmFilterArgumentValues, resolutionOnly, variant } = this.options; const isFilteredInstall: boolean = pnpmFilterArgumentValues.length > 0; const useWorkspaces: boolean = @@ -158,6 +158,7 @@ export abstract class BaseInstallManager { : undefined; const { shrinkwrapIsUpToDate, npmrcHash, projectImpactGraphIsUpToDate } = await this.prepareAsync( subspace, + variant, projectImpactGraphGenerator ); @@ -200,7 +201,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, subspace); + return this.canSkipInstall(outputStats.mtime, subspace, variant); }; if ( @@ -253,7 +254,7 @@ export abstract class BaseInstallManager { ]); if (this.options.allowShrinkwrapUpdates && !shrinkwrapIsUpToDate) { - const committedShrinkwrapFileName: string = subspace.getCommittedShrinkwrapFilename(); + const committedShrinkwrapFileName: string = subspace.getCommittedShrinkwrapFilePath(variant); const shrinkwrapFile: BaseShrinkwrapFile | undefined = ShrinkwrapFileFactory.getShrinkwrapFile( this.rushConfiguration.packageManager, this.rushConfiguration.packageManagerOptions, @@ -268,7 +269,7 @@ export abstract class BaseInstallManager { // Always update the state file if running "rush update" if (this.options.allowShrinkwrapUpdates) { - if (subspace.getRepoState().refreshState(this.rushConfiguration, subspace)) { + if (subspace.getRepoState().refreshState(this.rushConfiguration, subspace, variant)) { // eslint-disable-next-line no-console console.log( Colorize.yellow( @@ -379,7 +380,7 @@ export abstract class BaseInstallManager { protected abstract postInstallAsync(subspace: Subspace): Promise; - protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace): boolean { + protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace, variant: string | undefined): boolean { // Based on timestamps, can we skip this install entirely? const potentiallyChangedFiles: string[] = []; @@ -391,10 +392,10 @@ export abstract class BaseInstallManager { // Additionally, if they pulled an updated shrinkwrap file from Git, // then we can't skip this install - potentiallyChangedFiles.push(subspace.getCommittedShrinkwrapFilename()); + potentiallyChangedFiles.push(subspace.getCommittedShrinkwrapFilePath(variant)); // Add common-versions.json file to the potentially changed files list. - potentiallyChangedFiles.push(subspace.getCommonVersionsFilePath()); + potentiallyChangedFiles.push(subspace.getCommonVersionsFilePath(variant)); // Add pnpm-config.json file to the potentially changed files list. potentiallyChangedFiles.push(subspace.getPnpmConfigFilePath()); @@ -413,16 +414,18 @@ export abstract class BaseInstallManager { protected async prepareAsync( subspace: Subspace, + variant: string | undefined, projectImpactGraphGenerator: ProjectImpactGraphGenerator | undefined ): Promise<{ shrinkwrapIsUpToDate: boolean; npmrcHash: string | undefined; projectImpactGraphIsUpToDate: boolean; }> { + const terminal: ITerminal = this._terminal; const { allowShrinkwrapUpdates } = this.options; // Check the policies - await PolicyValidator.validatePolicyAsync(this.rushConfiguration, subspace, this.options); + await PolicyValidator.validatePolicyAsync(this.rushConfiguration, subspace, variant, this.options); await this._installGitHooksAsync(); @@ -432,8 +435,7 @@ export abstract class BaseInstallManager { if (approvedPackagesChecker.approvedPackagesFilesAreOutOfDate) { approvedPackagesChecker.rewriteConfigFiles(); if (allowShrinkwrapUpdates) { - // eslint-disable-next-line no-console - console.log( + terminal.writeLine( Colorize.yellow( 'Approved package files have been updated. These updates should be committed to source control' ) @@ -454,7 +456,7 @@ export abstract class BaseInstallManager { // (If it's a full update, then we ignore the shrinkwrap from Git since it will be overwritten) if (!this.options.fullUpgrade) { - const committedShrinkwrapFileName: string = subspace.getCommittedShrinkwrapFilename(); + const committedShrinkwrapFileName: string = subspace.getCommittedShrinkwrapFilePath(variant); try { shrinkwrapFile = ShrinkwrapFileFactory.getShrinkwrapFile( this.rushConfiguration.packageManager, @@ -462,18 +464,14 @@ export abstract class BaseInstallManager { committedShrinkwrapFileName ); } catch (ex) { - // eslint-disable-next-line no-console - console.log(); - // eslint-disable-next-line no-console - console.log( + terminal.writeLine(); + terminal.writeLine( `Unable to load the ${this.rushConfiguration.shrinkwrapFilePhrase}: ${(ex as Error).message}` ); if (!allowShrinkwrapUpdates) { - // eslint-disable-next-line no-console - console.log(); - // eslint-disable-next-line no-console - console.log(Colorize.red('You need to run "rush update" to fix this problem')); + terminal.writeLine(); + terminal.writeLine(Colorize.red('You need to run "rush update" to fix this problem')); throw new AlreadyReportedError(); } @@ -481,12 +479,45 @@ export abstract class BaseInstallManager { } } + // Write a file indicating which variant is being installed. + // This will be used by bulk scripts to determine the correct Shrinkwrap file to track. + const currentVariantJsonFilePath: string = this.rushConfiguration.currentVariantJsonFilePath; + const currentVariantJson: ICurrentVariantJson = { + variant: variant ?? null + }; + + // Determine if the variant is already current by updating current-variant.json. + // If nothing is written, the variant has not changed. + const variantIsUpToDate: boolean = !(await JsonFile.saveAsync( + currentVariantJson, + currentVariantJsonFilePath, + { + onlyIfChanged: true + } + )); + + if (this.options.variant) { + terminal.writeLine(); + terminal.writeLine(Colorize.bold(`Using variant '${this.options.variant}' for installation.`)); + } else if (!variantIsUpToDate && !variant && this.rushConfiguration.variants.size > 0) { + terminal.writeLine(); + terminal.writeLine(Colorize.bold('Using the default variant for installation.')); + } + const extraNpmrcLines: string[] = []; if (this.rushConfiguration.subspacesFeatureEnabled) { // Look for a monorepo level .npmrc file const commonNpmrcPath: string = `${this.rushConfiguration.commonRushConfigFolder}/.npmrc`; - if (FileSystem.exists(commonNpmrcPath)) { - const commonNpmrcFileLines: string[] = FileSystem.readFile(commonNpmrcPath).toString().split('\n'); + let commonNpmrcFileLines: string[] | undefined; + try { + commonNpmrcFileLines = (await FileSystem.readFileAsync(commonNpmrcPath)).split('\n'); + } catch (e) { + if (!FileSystem.isNotExistError(e)) { + throw e; + } + } + + if (commonNpmrcFileLines) { extraNpmrcLines.push(...commonNpmrcFileLines); } @@ -569,13 +600,15 @@ export abstract class BaseInstallManager { await PnpmfileConfiguration.writeCommonTempPnpmfileShimAsync( this.rushConfiguration, subspace.getSubspaceTempFolderPath(), - subspace + subspace, + variant ); if (this.rushConfiguration.subspacesFeatureEnabled) { await SubspacePnpmfileConfiguration.writeCommonTempSubspaceGlobalPnpmfileAsync( this.rushConfiguration, - subspace + subspace, + variant ); } } @@ -589,14 +622,12 @@ export abstract class BaseInstallManager { ]); shrinkwrapIsUpToDate = shrinkwrapIsUpToDate && !this.options.recheckShrinkwrap; - this._syncTempShrinkwrap(subspace, shrinkwrapFile); + this._syncTempShrinkwrap(subspace, variant, shrinkwrapFile); // Write out the reported warnings if (shrinkwrapWarnings.length > 0) { - // eslint-disable-next-line no-console - console.log(); - // eslint-disable-next-line no-console - console.log( + terminal.writeLine(); + terminal.writeLine( Colorize.yellow( PrintUtilities.wrapWords( `The ${this.rushConfiguration.shrinkwrapFilePhrase} contains the following issues:` @@ -605,18 +636,17 @@ export abstract class BaseInstallManager { ); for (const shrinkwrapWarning of shrinkwrapWarnings) { - // eslint-disable-next-line no-console - console.log(Colorize.yellow(' ' + shrinkwrapWarning)); + terminal.writeLine(Colorize.yellow(' ' + shrinkwrapWarning)); } - // eslint-disable-next-line no-console - console.log(); + + terminal.writeLine(); } let hasErrors: boolean = false; // Force update if the shrinkwrap is out of date if (!shrinkwrapIsUpToDate && !allowShrinkwrapUpdates) { - this._terminal.writeErrorLine(); - this._terminal.writeErrorLine( + terminal.writeErrorLine(); + terminal.writeErrorLine( `The ${this.rushConfiguration.shrinkwrapFilePhrase} is out of date. You need to run "rush update".` ); hasErrors = true; @@ -624,8 +654,8 @@ export abstract class BaseInstallManager { if (!projectImpactGraphIsUpToDate && !allowShrinkwrapUpdates) { hasErrors = true; - this._terminal.writeErrorLine(); - this._terminal.writeErrorLine( + terminal.writeErrorLine(); + terminal.writeErrorLine( Colorize.red( `The ${RushConstants.projectImpactGraphFilename} file is missing or out of date. You need to run "rush update".` ) @@ -1070,8 +1100,12 @@ ${gitLfsHookHandling} return true; } - private _syncTempShrinkwrap(subspace: Subspace, shrinkwrapFile: BaseShrinkwrapFile | undefined): void { - const committedShrinkwrapFileName: string = subspace.getCommittedShrinkwrapFilename(); + private _syncTempShrinkwrap( + subspace: Subspace, + variant: string | undefined, + shrinkwrapFile: BaseShrinkwrapFile | undefined + ): void { + const committedShrinkwrapFileName: string = subspace.getCommittedShrinkwrapFilePath(variant); if (shrinkwrapFile) { Utilities.syncFile(committedShrinkwrapFileName, subspace.getTempShrinkwrapFilename()); Utilities.syncFile(committedShrinkwrapFileName, subspace.getTempShrinkwrapPreinstallFilename()); diff --git a/libraries/rush-lib/src/logic/base/BaseInstallManagerTypes.ts b/libraries/rush-lib/src/logic/base/BaseInstallManagerTypes.ts index 044667e2e2c..47017a1da12 100644 --- a/libraries/rush-lib/src/logic/base/BaseInstallManagerTypes.ts +++ b/libraries/rush-lib/src/logic/base/BaseInstallManagerTypes.ts @@ -81,6 +81,11 @@ export interface IInstallManagerOptions { */ collectLogFile: boolean; + /** + * The variant to consider when performing installations and validating shrinkwrap updates. + */ + variant: string | undefined; + /** * Retry the install the specified number of times */ diff --git a/libraries/rush-lib/src/logic/base/BaseShrinkwrapFile.ts b/libraries/rush-lib/src/logic/base/BaseShrinkwrapFile.ts index 928d66116a1..bfedfdcd88e 100644 --- a/libraries/rush-lib/src/logic/base/BaseShrinkwrapFile.ts +++ b/libraries/rush-lib/src/logic/base/BaseShrinkwrapFile.ts @@ -154,12 +154,14 @@ export abstract class BaseShrinkwrapFile { * a given package.json. Returns true if any dependencies are not aligned with the shrinkwrap. * * @param project - the Rush project that is being validated against the shrinkwrap + * @param variant - the variant that is being validated * * @virtual */ public abstract isWorkspaceProjectModifiedAsync( project: RushConfigurationProject, - subspace: Subspace + subspace: Subspace, + variant: string | undefined ): Promise; /** @virtual */ diff --git a/libraries/rush-lib/src/logic/installManager/RushInstallManager.ts b/libraries/rush-lib/src/logic/installManager/RushInstallManager.ts index 717b1e0cb1e..d97e3de4709 100644 --- a/libraries/rush-lib/src/logic/installManager/RushInstallManager.ts +++ b/libraries/rush-lib/src/logic/installManager/RushInstallManager.ts @@ -90,6 +90,8 @@ export class RushInstallManager extends BaseInstallManager { ): Promise<{ shrinkwrapIsUpToDate: boolean; shrinkwrapWarnings: string[] }> { const stopwatch: Stopwatch = Stopwatch.start(); + const { fullUpgrade, variant } = this.options; + // Example: "C:\MyRepo\common\temp\projects" const tempProjectsFolder: string = path.join( this.rushConfiguration.commonTempFolder, @@ -109,7 +111,7 @@ export class RushInstallManager extends BaseInstallManager { if (!shrinkwrapFile) { shrinkwrapIsUpToDate = false; - } else if (shrinkwrapFile.isWorkspaceCompatible && !this.options.fullUpgrade) { + } else if (shrinkwrapFile.isWorkspaceCompatible && !fullUpgrade) { // eslint-disable-next-line no-console console.log(); // eslint-disable-next-line no-console @@ -124,7 +126,7 @@ export class RushInstallManager extends BaseInstallManager { // dependency name --> version specifier const allExplicitPreferredVersions: Map = this.rushConfiguration.defaultSubspace - .getCommonVersions() + .getCommonVersions(variant) .getAllPreferredVersions(); if (shrinkwrapFile) { @@ -167,7 +169,7 @@ export class RushInstallManager extends BaseInstallManager { // dependency name --> version specifier const commonDependencies: Map = new Map([ ...allExplicitPreferredVersions, - ...this.rushConfiguration.getImplicitlyPreferredVersions(subspace) + ...this.rushConfiguration.getImplicitlyPreferredVersions(subspace, variant) ]); // To make the common/package.json file more readable, sort alphabetically @@ -438,8 +440,8 @@ export class RushInstallManager extends BaseInstallManager { * * @override */ - protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace): boolean { - if (!super.canSkipInstall(lastModifiedDate, subspace)) { + protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace, variant: string | undefined): boolean { + if (!super.canSkipInstall(lastModifiedDate, subspace, variant)) { return false; } diff --git a/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts b/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts index 3ce4aa5f65c..cae9090d6d3 100644 --- a/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts +++ b/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts @@ -90,6 +90,8 @@ export class WorkspaceInstallManager extends BaseInstallManager { ); } + const { fullUpgrade, allowShrinkwrapUpdates, variant } = this.options; + // eslint-disable-next-line no-console console.log('\n' + Colorize.bold('Updating workspace files in ' + subspace.getSubspaceTempFolderPath())); @@ -102,7 +104,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { if (!shrinkwrapFile) { shrinkwrapIsUpToDate = false; } else { - if (!shrinkwrapFile.isWorkspaceCompatible && !this.options.fullUpgrade) { + if (!shrinkwrapFile.isWorkspaceCompatible && !fullUpgrade) { // eslint-disable-next-line no-console console.log(); // eslint-disable-next-line no-console @@ -141,7 +143,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { ); shrinkwrapIsUpToDate = false; } else { - const commonVersions: CommonVersionsConfiguration = subspace.getCommonVersions(); + const commonVersions: CommonVersionsConfiguration = subspace.getCommonVersions(variant); if (repoState.preferredVersionsHash !== commonVersions.getPreferredVersionsHash()) { shrinkwrapWarnings.push( `Preferred versions from ${RushConstants.commonVersionsFilename} have been modified.` @@ -152,7 +154,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { const stopwatch: Stopwatch = Stopwatch.start(); const packageJsonInjectedDependenciesHash: string | undefined = - subspace.getPackageJsonInjectedDependenciesHash(); + subspace.getPackageJsonInjectedDependenciesHash(variant); stopwatch.stop(); @@ -253,7 +255,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { throw new AlreadyReportedError(); } - if (!this.options.allowShrinkwrapUpdates) { + if (!allowShrinkwrapUpdates) { // eslint-disable-next-line no-console console.log(); // eslint-disable-next-line no-console @@ -269,7 +271,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { throw new AlreadyReportedError(); } - if (this.options.fullUpgrade) { + if (fullUpgrade) { // We will update to `workspace` notation. If the version specified is a range, then use the provided range. // Otherwise, use `workspace:*` to ensure we're always using the workspace package. const workspaceRange: string = @@ -312,7 +314,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { } // Now validate that the shrinkwrap file matches what is in the package.json - if (await shrinkwrapFile?.isWorkspaceProjectModifiedAsync(rushProject, subspace)) { + if (await shrinkwrapFile?.isWorkspaceProjectModifiedAsync(rushProject, subspace, variant)) { shrinkwrapWarnings.push( `Dependencies of project "${rushProject.packageName}" do not match the current shrinkwrap.` ); @@ -414,8 +416,8 @@ export class WorkspaceInstallManager extends BaseInstallManager { return packageExtensionsChecksum; } - protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace): boolean { - if (!super.canSkipInstall(lastModifiedDate, subspace)) { + protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace, variant: string | undefined): boolean { + if (!super.canSkipInstall(lastModifiedDate, subspace, variant)) { return false; } diff --git a/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts b/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts index 952f85dd525..58bd6148f8d 100644 --- a/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts +++ b/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts @@ -19,12 +19,21 @@ export interface IRunInstallOptions { rushGlobalFolder: RushGlobalFolder; isDebug: boolean; terminal: ITerminal; + variant: string | undefined; } export async function doBasicInstallAsync(options: IRunInstallOptions): Promise { - const { rushConfiguration, rushGlobalFolder, isDebug } = options; + const { + rushConfiguration, + rushGlobalFolder, + isDebug, + variant, + terminal, + beforeInstallAsync, + afterInstallAsync + } = options; - VersionMismatchFinder.ensureConsistentVersions(rushConfiguration, options.terminal); + VersionMismatchFinder.ensureConsistentVersions(rushConfiguration, terminal, { variant }); SetupChecks.validate(rushConfiguration); const purgeManager: typeof PurgeManager.prototype = new PurgeManager(rushConfiguration, rushGlobalFolder); @@ -48,9 +57,10 @@ export async function doBasicInstallAsync(options: IRunInstallOptions): Promise< maxInstallAttempts: 1, networkConcurrency: undefined, subspace: rushConfiguration.defaultSubspace, - terminal: options.terminal, - afterInstallAsync: options.afterInstallAsync, - beforeInstallAsync: options.beforeInstallAsync + terminal, + variant, + afterInstallAsync, + beforeInstallAsync } ); diff --git a/libraries/rush-lib/src/logic/npm/NpmShrinkwrapFile.ts b/libraries/rush-lib/src/logic/npm/NpmShrinkwrapFile.ts index 72568ada1bc..7c11ac6400d 100644 --- a/libraries/rush-lib/src/logic/npm/NpmShrinkwrapFile.ts +++ b/libraries/rush-lib/src/logic/npm/NpmShrinkwrapFile.ts @@ -134,7 +134,8 @@ export class NpmShrinkwrapFile extends BaseShrinkwrapFile { /** @override */ public async isWorkspaceProjectModifiedAsync( project: RushConfigurationProject, - subspace: Subspace + subspace: Subspace, + variant: string | undefined ): Promise { throw new InternalError('Not implemented'); } diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts b/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts index 3860200b737..5801d1dc1cc 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts @@ -809,7 +809,8 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { /** @override */ public async isWorkspaceProjectModifiedAsync( project: RushConfigurationProject, - subspace: Subspace + subspace: Subspace, + variant: string | undefined ): Promise { const importerKey: string = this.getImporterKeyByPath( subspace.getSubspaceTempFolderPath(), @@ -828,7 +829,8 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { if (!this._pnpmfileConfiguration) { this._pnpmfileConfiguration = await PnpmfileConfiguration.initializeAsync( project.rushConfiguration, - subspace + subspace, + variant ); } diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts b/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts index 6dece515a8a..1ffff5da450 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts @@ -29,7 +29,8 @@ export class PnpmfileConfiguration { public static async initializeAsync( rushConfiguration: RushConfiguration, - subspace: Subspace + subspace: Subspace, + variant: string | undefined ): Promise { if (rushConfiguration.packageManager !== 'pnpm') { throw new Error( @@ -42,7 +43,8 @@ export class PnpmfileConfiguration { log: (message: string) => {}, pnpmfileShimSettings: await PnpmfileConfiguration._getPnpmfileShimSettingsAsync( rushConfiguration, - subspace + subspace, + variant ) }; @@ -52,7 +54,8 @@ export class PnpmfileConfiguration { public static async writeCommonTempPnpmfileShimAsync( rushConfiguration: RushConfiguration, targetDir: string, - subspace: Subspace + subspace: Subspace, + variant: string | undefined ): Promise { if (rushConfiguration.packageManager !== 'pnpm') { throw new Error( @@ -72,7 +75,7 @@ export class PnpmfileConfiguration { }); const pnpmfileShimSettings: IPnpmfileShimSettings = - await PnpmfileConfiguration._getPnpmfileShimSettingsAsync(rushConfiguration, subspace); + await PnpmfileConfiguration._getPnpmfileShimSettingsAsync(rushConfiguration, subspace, variant); // Write the settings file used by the shim await JsonFile.saveAsync(pnpmfileShimSettings, path.join(targetDir, 'pnpmfileSettings.json'), { @@ -82,7 +85,8 @@ export class PnpmfileConfiguration { private static async _getPnpmfileShimSettingsAsync( rushConfiguration: RushConfiguration, - subspace: Subspace + subspace: Subspace, + variant: string | undefined ): Promise { let allPreferredVersions: { [dependencyName: string]: string } = {}; let allowedAlternativeVersions: { [dependencyName: string]: readonly string[] } = {}; @@ -90,7 +94,7 @@ export class PnpmfileConfiguration { // Only workspaces shims in the common versions using pnpmfile if ((rushConfiguration.packageManagerOptions as PnpmOptionsConfiguration).useWorkspaces) { - const commonVersionsConfiguration: CommonVersionsConfiguration = subspace.getCommonVersions(); + const commonVersionsConfiguration: CommonVersionsConfiguration = subspace.getCommonVersions(variant); const preferredVersions: Map = new Map(); MapExtensions.mergeFromMap( preferredVersions, diff --git a/libraries/rush-lib/src/logic/pnpm/SubspacePnpmfileConfiguration.ts b/libraries/rush-lib/src/logic/pnpm/SubspacePnpmfileConfiguration.ts index db11e43ac9e..327f1787de7 100644 --- a/libraries/rush-lib/src/logic/pnpm/SubspacePnpmfileConfiguration.ts +++ b/libraries/rush-lib/src/logic/pnpm/SubspacePnpmfileConfiguration.ts @@ -25,7 +25,8 @@ export class SubspacePnpmfileConfiguration { */ public static async writeCommonTempSubspaceGlobalPnpmfileAsync( rushConfiguration: RushConfiguration, - subspace: Subspace + subspace: Subspace, + variant: string | undefined ): Promise { if (rushConfiguration.packageManager !== 'pnpm') { throw new Error( @@ -43,7 +44,7 @@ export class SubspacePnpmfileConfiguration { }); const subspaceGlobalPnpmfileShimSettings: ISubspacePnpmfileShimSettings = - SubspacePnpmfileConfiguration.getSubspacePnpmfileShimSettings(rushConfiguration, subspace); + SubspacePnpmfileConfiguration.getSubspacePnpmfileShimSettings(rushConfiguration, subspace, variant); // Write the settings file used by the shim await JsonFile.saveAsync( @@ -57,7 +58,8 @@ export class SubspacePnpmfileConfiguration { public static getSubspacePnpmfileShimSettings( rushConfiguration: RushConfiguration, - subspace: Subspace + subspace: Subspace, + variant: string | undefined ): ISubspacePnpmfileShimSettings { const workspaceProjects: Record = {}; const subspaceProjects: Record = {}; @@ -85,7 +87,7 @@ export class SubspacePnpmfileConfiguration { // common/config/subspaces//.pnpmfile.cjs const userPnpmfilePath: string = path.join( - subspace.getSubspaceConfigFolderPath(), + subspace.getVariantDependentSubspaceConfigFolderPath(variant), (rushConfiguration.packageManagerWrapper as PnpmPackageManager).pnpmfileFilename ); if (FileSystem.exists(userPnpmfilePath)) { 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 b433514c8ce..91f787e57b5 100644 --- a/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapFile.test.ts +++ b/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapFile.test.ts @@ -136,7 +136,8 @@ describe(PnpmShrinkwrapFile.name, () => { await expect( pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( project, - project.rushConfiguration.defaultSubspace + project.rushConfiguration.defaultSubspace, + undefined ) ).resolves.toBe(false); }); @@ -149,7 +150,8 @@ describe(PnpmShrinkwrapFile.name, () => { await expect( pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( project, - project.rushConfiguration.defaultSubspace + project.rushConfiguration.defaultSubspace, + undefined ) ).resolves.toBe(true); }); @@ -162,7 +164,8 @@ describe(PnpmShrinkwrapFile.name, () => { await expect( pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( project, - project.rushConfiguration.defaultSubspace + project.rushConfiguration.defaultSubspace, + undefined ) ).resolves.toBe(false); }); @@ -177,7 +180,8 @@ describe(PnpmShrinkwrapFile.name, () => { await expect( pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( project, - project.rushConfiguration.defaultSubspace + project.rushConfiguration.defaultSubspace, + undefined ) ).resolves.toBe(false); }); @@ -190,7 +194,8 @@ describe(PnpmShrinkwrapFile.name, () => { await expect( pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( project, - project.rushConfiguration.defaultSubspace + project.rushConfiguration.defaultSubspace, + undefined ) ).resolves.toBe(true); }); @@ -203,7 +208,8 @@ describe(PnpmShrinkwrapFile.name, () => { await expect( pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( project, - project.rushConfiguration.defaultSubspace + project.rushConfiguration.defaultSubspace, + undefined ) ).resolves.toBe(false); }); @@ -216,7 +222,8 @@ describe(PnpmShrinkwrapFile.name, () => { await expect( pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( project, - project.rushConfiguration.defaultSubspace + project.rushConfiguration.defaultSubspace, + undefined ) ).resolves.toBe(false); }); diff --git a/libraries/rush-lib/src/logic/pnpm/test/PnpmfileConfiguration.test.ts b/libraries/rush-lib/src/logic/pnpm/test/PnpmfileConfiguration.test.ts index b1875dbde8f..23dafd9caf1 100644 --- a/libraries/rush-lib/src/logic/pnpm/test/PnpmfileConfiguration.test.ts +++ b/libraries/rush-lib/src/logic/pnpm/test/PnpmfileConfiguration.test.ts @@ -16,7 +16,8 @@ describe(PnpmfileConfiguration.name, () => { await PnpmfileConfiguration.writeCommonTempPnpmfileShimAsync( rushConfiguration, subspace.getSubspaceTempFolderPath(), - subspace + subspace, + undefined ); }); diff --git a/libraries/rush-lib/src/logic/policy/PolicyValidator.ts b/libraries/rush-lib/src/logic/policy/PolicyValidator.ts index a035a2ac802..b54e31d1295 100644 --- a/libraries/rush-lib/src/logic/policy/PolicyValidator.ts +++ b/libraries/rush-lib/src/logic/policy/PolicyValidator.ts @@ -16,6 +16,7 @@ export interface IPolicyValidatorOptions { export async function validatePolicyAsync( rushConfiguration: RushConfiguration, subspace: Subspace, + variant: string | undefined, options: IPolicyValidatorOptions ): Promise { if (!options.bypassPolicy) { @@ -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, subspace, options); + ShrinkwrapFilePolicy.validate(rushConfiguration, subspace, variant, options); } } } diff --git a/libraries/rush-lib/src/logic/policy/ShrinkwrapFilePolicy.ts b/libraries/rush-lib/src/logic/policy/ShrinkwrapFilePolicy.ts index 40788b8b00d..f774eae072b 100644 --- a/libraries/rush-lib/src/logic/policy/ShrinkwrapFilePolicy.ts +++ b/libraries/rush-lib/src/logic/policy/ShrinkwrapFilePolicy.ts @@ -18,6 +18,7 @@ export interface IShrinkwrapFilePolicyValidatorOptions extends IPolicyValidatorO export function validate( rushConfiguration: RushConfiguration, subspace: Subspace, + variant: string | undefined, options: IPolicyValidatorOptions ): void { // eslint-disable-next-line no-console @@ -25,7 +26,7 @@ export function validate( const shrinkwrapFile: BaseShrinkwrapFile | undefined = ShrinkwrapFileFactory.getShrinkwrapFile( rushConfiguration.packageManager, rushConfiguration.packageManagerOptions, - subspace.getCommittedShrinkwrapFilename() + subspace.getCommittedShrinkwrapFilePath(variant) ); if (!shrinkwrapFile) { diff --git a/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts b/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts index 5fb83455965..bd5031e79ab 100644 --- a/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts +++ b/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts @@ -16,13 +16,13 @@ import type { Subspace } from '../../api/Subspace'; const TRUNCATE_AFTER_PACKAGE_NAME_COUNT: number = 5; export interface IVersionMismatchFinderOptions { - subspace: Subspace; + subspace?: Subspace; + variant: string | undefined; } export interface IVersionMismatchFinderRushCheckOptions extends IVersionMismatchFinderOptions { printAsJson?: boolean | undefined; truncateLongPackageNameLists?: boolean | undefined; - subspace: Subspace; } export interface IVersionMismatchFinderEnsureConsistentVersionsOptions @@ -69,11 +69,10 @@ export class VersionMismatchFinder { public static rushCheck( rushConfiguration: RushConfiguration, terminal: ITerminal, - options: IVersionMismatchFinderRushCheckOptions = { - subspace: rushConfiguration.defaultSubspace - } + options: IVersionMismatchFinderRushCheckOptions ): void { VersionMismatchFinder._checkForInconsistentVersions(rushConfiguration, { + subspace: rushConfiguration.defaultSubspace, ...options, terminal, isRushCheckCommand: true @@ -83,11 +82,10 @@ export class VersionMismatchFinder { public static ensureConsistentVersions( rushConfiguration: RushConfiguration, terminal: ITerminal, - options: IVersionMismatchFinderEnsureConsistentVersionsOptions = { - subspace: rushConfiguration.defaultSubspace - } + options: IVersionMismatchFinderEnsureConsistentVersionsOptions ): void { VersionMismatchFinder._checkForInconsistentVersions(rushConfiguration, { + subspace: rushConfiguration.defaultSubspace, ...options, terminal, isRushCheckCommand: false, @@ -101,11 +99,10 @@ export class VersionMismatchFinder { */ public static getMismatches( rushConfiguration: RushConfiguration, - options: IVersionMismatchFinderOptions = { - subspace: rushConfiguration.defaultSubspace - } + options: IVersionMismatchFinderOptions ): VersionMismatchFinder { - const commonVersions: CommonVersionsConfiguration = options.subspace.getCommonVersions(); + const { subspace = rushConfiguration.defaultSubspace, variant } = options; + const commonVersions: CommonVersionsConfiguration = subspace.getCommonVersions(variant); const projects: VersionMismatchFinderEntity[] = []; @@ -114,7 +111,7 @@ export class VersionMismatchFinder { projects.push(new VersionMismatchFinderCommonVersions(commonVersions)); // If subspace is specified, only go through projects in that subspace - for (const project of options.subspace.getProjects()) { + for (const project of subspace.getProjects()) { projects.push(new VersionMismatchFinderProject(project)); } @@ -126,36 +123,39 @@ export class VersionMismatchFinder { options: { isRushCheckCommand: boolean; subspace: Subspace; + variant: string | undefined; printAsJson?: boolean | undefined; terminal: ITerminal; truncateLongPackageNameLists?: boolean | undefined; } ): void { - if (options.subspace.shouldEnsureConsistentVersions || options.isRushCheckCommand) { + const { variant, isRushCheckCommand, printAsJson, subspace, truncateLongPackageNameLists, terminal } = + options; + if (subspace.shouldEnsureConsistentVersions(variant) || isRushCheckCommand) { const mismatchFinder: VersionMismatchFinder = VersionMismatchFinder.getMismatches( rushConfiguration, options ); - if (options.printAsJson) { + if (printAsJson) { mismatchFinder.printAsJson(); } else { - mismatchFinder.print(options.truncateLongPackageNameLists); + mismatchFinder.print(truncateLongPackageNameLists); if (mismatchFinder.numberOfMismatches > 0) { // eslint-disable-next-line no-console console.log( Colorize.red( `Found ${mismatchFinder.numberOfMismatches} mis-matching dependencies ${ - options.subspace?.subspaceName ? `in subspace: ${options.subspace?.subspaceName}` : '' + subspace?.subspaceName ? `in subspace: ${subspace?.subspaceName}` : '' }` ) ); rushConfiguration.customTipsConfiguration._showErrorTip( - options.terminal, + terminal, CustomTipId.TIP_RUSH_INCONSISTENT_VERSIONS ); - if (!options.isRushCheckCommand && options.truncateLongPackageNameLists) { + if (!isRushCheckCommand && truncateLongPackageNameLists) { // There isn't a --verbose flag in `rush install`/`rush update`, so a long list will always be truncated. // eslint-disable-next-line no-console console.log( @@ -165,7 +165,7 @@ export class VersionMismatchFinder { throw new AlreadyReportedError(); } else { - if (options.isRushCheckCommand) { + if (isRushCheckCommand) { // eslint-disable-next-line no-console console.log(Colorize.green(`Found no mis-matching dependencies!`)); } diff --git a/libraries/rush-lib/src/schemas/rush.schema.json b/libraries/rush-lib/src/schemas/rush.schema.json index af9e2481548..dce5fcaae37 100644 --- a/libraries/rush-lib/src/schemas/rush.schema.json +++ b/libraries/rush-lib/src/schemas/rush.schema.json @@ -201,8 +201,22 @@ "additionalProperties": false }, "variants": { - "description": "DEPRECATED", - "type": "array" + "description": "Defines the list of installation variants for this repository. For more details about this feature, see this article: https://rushjs.io/pages/advanced/installation_variants/", + "type": "array", + "items": { + "type": "object", + "properties": { + "variantName": { + "description": "The name of the variant. Maps to common/rush/variants/{name} under the repository root.", + "type": "string" + }, + "description": { + "description": "", + "type": "string" + } + }, + "required": ["variantName", "description"] + } }, "repository": { "description": "The repository location", From 358158f178d7cc4b829c7d2f72b3d3ebedf92c48 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Wed, 18 Sep 2024 14:18:52 -0700 Subject: [PATCH 02/24] fixup! Bring back the Variants feature. --- .../__snapshots__/RushCommandLine.test.ts.snap | 16 ++++++++-------- .../__snapshots__/CommandLineHelp.test.ts.snap | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/libraries/rush-lib/src/api/test/__snapshots__/RushCommandLine.test.ts.snap b/libraries/rush-lib/src/api/test/__snapshots__/RushCommandLine.test.ts.snap index b630b43a6ad..e76411f4531 100644 --- a/libraries/rush-lib/src/api/test/__snapshots__/RushCommandLine.test.ts.snap +++ b/libraries/rush-lib/src/api/test/__snapshots__/RushCommandLine.test.ts.snap @@ -160,14 +160,6 @@ Object { Object { "actionName": "check", "parameters": Array [ - Object { - "description": "Run command using a variant installation configuration", - "environmentVariable": "RUSH_VARIANT", - "kind": "String", - "longName": "--variant", - "required": false, - "shortName": undefined, - }, Object { "description": "If this flag is specified, output will be in JSON format.", "environmentVariable": undefined, @@ -192,6 +184,14 @@ Object { "required": false, "shortName": undefined, }, + Object { + "description": "Run command using a variant installation configuration", + "environmentVariable": "RUSH_VARIANT", + "kind": "String", + "longName": "--variant", + "required": false, + "shortName": undefined, + }, ], }, Object { diff --git a/libraries/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap b/libraries/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap index ff2c10abf36..dd2e62cc644 100644 --- a/libraries/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap +++ b/libraries/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap @@ -346,8 +346,8 @@ Optional arguments: `; exports[`CommandLineHelp prints the help for each action: check 1`] = ` -"usage: rush check [-h] [--variant VARIANT] [--json] [--verbose] - [--subspace SUBSPACE_NAME] +"usage: rush check [-h] [--json] [--verbose] [--subspace SUBSPACE_NAME] + [--variant VARIANT] Checks each project's package.json files and ensures that all dependencies @@ -355,9 +355,6 @@ are of the same version throughout the repository. Optional arguments: -h, --help Show this help message and exit. - --variant VARIANT Run command using a variant installation - configuration. This parameter may alternatively be - specified via the RUSH_VARIANT environment variable. --json If this flag is specified, output will be in JSON format. --verbose If this flag is specified, long lists of package @@ -369,6 +366,9 @@ Optional arguments: within that subspace (ignoring other subspaces). This parameter is required when the \\"subspacesEnabled\\" setting is set to true in subspaces.json. + --variant VARIANT Run command using a variant installation + configuration. This parameter may alternatively be + specified via the RUSH_VARIANT environment variable. " `; From 9c34cb9887fd28fc77caf88163f562a5204b695c Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Wed, 18 Sep 2024 16:32:26 -0700 Subject: [PATCH 03/24] Some cleanup to Subspace.ts --- .../src/cli/lint/actions/CheckAction.ts | 2 +- common/reviews/api/rush-lib.api.md | 6 ++- libraries/rush-lib/src/api/Subspace.ts | 52 +++++++++++-------- .../versionMismatch/VersionMismatchFinder.ts | 12 +++-- .../src/afterInstallAsync.ts | 6 ++- 5 files changed, 48 insertions(+), 30 deletions(-) diff --git a/apps/lockfile-explorer/src/cli/lint/actions/CheckAction.ts b/apps/lockfile-explorer/src/cli/lint/actions/CheckAction.ts index e63bec03e7d..e04155d31a4 100644 --- a/apps/lockfile-explorer/src/cli/lint/actions/CheckAction.ts +++ b/apps/lockfile-explorer/src/cli/lint/actions/CheckAction.ts @@ -102,7 +102,7 @@ export class CheckAction extends CommandLineAction { const projectFolder: string = project.projectFolder; const subspace: Subspace = project.subspace; - const shrinkwrapFilename: string = subspace.getCommittedShrinkwrapFilename(); + const shrinkwrapFilename: string = subspace.getCommittedShrinkwrapFilePath(); let doc: Lockfile | LockfileV6; if (this._docMap.has(shrinkwrapFilename)) { doc = this._docMap.get(shrinkwrapFilename)!; diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index da54365669d..92807f51ee4 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1467,6 +1467,8 @@ export class Subspace { _addProject(project: RushConfigurationProject): void; // @beta contains(project: RushConfigurationProject): boolean; + // @deprecated (undocumented) + getCommittedShrinkwrapFilename(): string; // @beta getCommittedShrinkwrapFilePath(variant?: string): string; // @beta @@ -1495,9 +1497,11 @@ export class Subspace { getSubspaceTempFolderPath(): string; // @beta getTempShrinkwrapFilename(): string; - // @beta + // @deprecated (undocumented) getTempShrinkwrapPreinstallFilename(subspaceName?: string | undefined): string; // @beta + getTempShrinkwrapPreinstallFilePath(): string; + // @beta getVariantDependentSubspaceConfigFolderPath(variant: string | undefined): string; // @beta shouldEnsureConsistentVersions(variant?: string): boolean; diff --git a/libraries/rush-lib/src/api/Subspace.ts b/libraries/rush-lib/src/api/Subspace.ts index 22ded62cbef..879a90de2c4 100644 --- a/libraries/rush-lib/src/api/Subspace.ts +++ b/libraries/rush-lib/src/api/Subspace.ts @@ -30,8 +30,8 @@ interface ISubspaceDetail { subspaceConfigFolderPath: string; subspacePnpmPatchesFolderPath: string; subspaceTempFolderPath: string; - tempShrinkwrapFilename: string; - tempShrinkwrapPreinstallFilename: string; + tempShrinkwrapFilePath: string; + tempShrinkwrapPreinstallFilePath: string; } interface IPackageJsonLite extends Omit {} @@ -171,29 +171,25 @@ export class Subspace { let subspaceTempFolderPath: string; if (rushConfiguration.subspacesFeatureEnabled) { // Example: C:\MyRepo\common\temp\my-subspace - subspaceTempFolderPath = path.join(commonTempFolder, this.subspaceName); + subspaceTempFolderPath = `${commonTempFolder}/${this.subspaceName}`; } else { // Example: C:\MyRepo\common\temp subspaceTempFolderPath = commonTempFolder; } // Example: C:\MyRepo\common\temp\my-subspace\pnpm-lock.yaml - const tempShrinkwrapFilename: string = - subspaceTempFolderPath + `/${rushConfiguration.shrinkwrapFilename}`; + const tempShrinkwrapFilePath: string = `${subspaceTempFolderPath}/${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 - ); + const parsedPath: path.ParsedPath = path.parse(tempShrinkwrapFilePath); + const tempShrinkwrapPreinstallFilePath: string = `${parsedPath.dir}/${parsedPath.name}-preinstall${parsedPath.ext}`; this._detail = { subspaceConfigFolderPath, subspacePnpmPatchesFolderPath, subspaceTempFolderPath, - tempShrinkwrapFilename, - tempShrinkwrapPreinstallFilename + tempShrinkwrapFilePath, + tempShrinkwrapPreinstallFilePath }; } return this._detail; @@ -264,7 +260,14 @@ export class Subspace { * @beta */ public getTempShrinkwrapFilename(): string { - return this._ensureDetail().tempShrinkwrapFilename; + return this._ensureDetail().tempShrinkwrapFilePath; + } + + /** + * @deprecated - Use {@link Subspace.getTempShrinkwrapPreinstallFilePath} instead. + */ + public getTempShrinkwrapPreinstallFilename(subspaceName?: string | undefined): string { + return this.getTempShrinkwrapPreinstallFilePath(); } /** @@ -277,8 +280,8 @@ export class Subspace { * or `C:\MyRepo\common\temp\pnpm-lock-preinstall.yaml` * @beta */ - public getTempShrinkwrapPreinstallFilename(subspaceName?: string | undefined): string { - return this._ensureDetail().tempShrinkwrapPreinstallFilename; + public getTempShrinkwrapPreinstallFilePath(): string { + return this._ensureDetail().tempShrinkwrapPreinstallFilePath; } /** @@ -308,10 +311,10 @@ export class Subspace { * @beta */ public getCommonVersions(variant?: string): CommonVersionsConfiguration { - const commonVersionsFilename: string = this.getCommonVersionsFilePath(variant); + const commonVersionsFilePath: string = this.getCommonVersionsFilePath(variant); if (!this._commonVersionsConfiguration) { this._commonVersionsConfiguration = CommonVersionsConfiguration.loadFromFile( - commonVersionsFilename, + commonVersionsFilePath, this._rushConfiguration ); } @@ -351,8 +354,15 @@ export class Subspace { * @beta */ public getRepoState(): RepoStateFile { - const repoStateFilename: string = this.getRepoStateFilePath(); - return RepoStateFile.loadFromFile(repoStateFilename); + const repoStateFilePath: string = this.getRepoStateFilePath(); + return RepoStateFile.loadFromFile(repoStateFilePath); + } + + /** + * @deprecated - Use {@link Subspace.getCommittedShrinkwrapFilePath} instead. + */ + public getCommittedShrinkwrapFilename(): string { + return this.getCommittedShrinkwrapFilePath(); } /** @@ -362,7 +372,7 @@ export class Subspace { */ public getCommittedShrinkwrapFilePath(variant?: string): string { const subspaceConfigFolderPath: string = this.getVariantDependentSubspaceConfigFolderPath(variant); - return path.join(subspaceConfigFolderPath, this._rushConfiguration.shrinkwrapFilename); + return `${subspaceConfigFolderPath}/${this._rushConfiguration.shrinkwrapFilename}`; } /** @@ -378,7 +388,7 @@ export class Subspace { const pnpmFilename: string = (this._rushConfiguration.packageManagerWrapper as PnpmPackageManager) .pnpmfileFilename; - return path.join(subspaceConfigFolderPath, pnpmFilename); + return `${subspaceConfigFolderPath}/${pnpmFilename}`; } /** diff --git a/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts b/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts index bd5031e79ab..90312e83ea2 100644 --- a/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts +++ b/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts @@ -17,7 +17,7 @@ const TRUNCATE_AFTER_PACKAGE_NAME_COUNT: number = 5; export interface IVersionMismatchFinderOptions { subspace?: Subspace; - variant: string | undefined; + variant?: string; } export interface IVersionMismatchFinderRushCheckOptions extends IVersionMismatchFinderOptions { @@ -69,10 +69,11 @@ export class VersionMismatchFinder { public static rushCheck( rushConfiguration: RushConfiguration, terminal: ITerminal, - options: IVersionMismatchFinderRushCheckOptions + options?: IVersionMismatchFinderRushCheckOptions ): void { VersionMismatchFinder._checkForInconsistentVersions(rushConfiguration, { subspace: rushConfiguration.defaultSubspace, + variant: undefined, ...options, terminal, isRushCheckCommand: true @@ -82,10 +83,11 @@ export class VersionMismatchFinder { public static ensureConsistentVersions( rushConfiguration: RushConfiguration, terminal: ITerminal, - options: IVersionMismatchFinderEnsureConsistentVersionsOptions + options?: IVersionMismatchFinderEnsureConsistentVersionsOptions ): void { VersionMismatchFinder._checkForInconsistentVersions(rushConfiguration, { subspace: rushConfiguration.defaultSubspace, + variant: undefined, ...options, terminal, isRushCheckCommand: false, @@ -99,9 +101,9 @@ export class VersionMismatchFinder { */ public static getMismatches( rushConfiguration: RushConfiguration, - options: IVersionMismatchFinderOptions + options?: IVersionMismatchFinderOptions ): VersionMismatchFinder { - const { subspace = rushConfiguration.defaultSubspace, variant } = options; + const { subspace = rushConfiguration.defaultSubspace, variant } = options ?? {}; const commonVersions: CommonVersionsConfiguration = subspace.getCommonVersions(variant); const projects: VersionMismatchFinderEntity[] = []; diff --git a/rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts b/rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts index de6ba6402a1..44872a8a400 100644 --- a/rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts +++ b/rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts @@ -56,7 +56,7 @@ export async function afterInstallAsync( const { terminal } = logger; const rushRoot: string = `${rushConfiguration.rushJsonFolder}/`; - const lockFilePath: string = subspace.getCommittedShrinkwrapFilename(); + const lockFilePath: string = subspace.getCommittedShrinkwrapFilePath(); const workspaceRoot: string = subspace.getSubspaceTempFolderPath(); const projectByImporterPath: LookupByPath = @@ -117,7 +117,9 @@ export async function afterInstallAsync( const filteredFiles: string[] = Object.keys(files).filter((file) => file.endsWith('/package.json')); if (filteredFiles.length > 0) { - const nestedPackageDirs: string[] = filteredFiles.map((x) => x.slice(0, /* -'/package.json'.length */ -13)); + const nestedPackageDirs: string[] = filteredFiles.map((x) => + x.slice(0, /* -'/package.json'.length */ -13) + ); if (nestedPackageDirs.length > 0) { // eslint-disable-next-line require-atomic-updates From b3b7b9663432e3b659909056b91b71d55bf92c86 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Wed, 18 Sep 2024 16:55:06 -0700 Subject: [PATCH 04/24] Fix an issue where install is skipped if the variant is changed. --- common/reviews/api/rush-lib.api.md | 6 ++- .../rush-lib/src/api/RushConfiguration.ts | 5 ++- .../src/logic/base/BaseInstallManager.ts | 35 +++++++++------- .../installManager/RushInstallManager.ts | 10 +++-- .../installManager/WorkspaceInstallManager.ts | 10 +++-- libraries/rush-lib/src/utilities/Utilities.ts | 41 +++++++++++++------ 6 files changed, 71 insertions(+), 36 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 92807f51ee4..bfc597aa0d0 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1162,6 +1162,10 @@ export class RushConfiguration { // @deprecated get commonVersions(): CommonVersionsConfiguration; readonly currentVariantJsonFilePath: string; + // Warning: (ae-forgotten-export) The symbol "ICurrentVariantJson" needs to be exported by the entry point index.d.ts + // + // @internal (undocumented) + _currentVariantJsonLoadingPromise: Promise | undefined; // @beta readonly customTipsConfiguration: CustomTipsConfiguration; // @beta @@ -1206,8 +1210,6 @@ export class RushConfiguration { readonly gitTagSeparator: string | undefined; readonly gitVersionBumpCommitMessage: string | undefined; readonly hotfixChangeEnabled: boolean; - // Warning: (ae-forgotten-export) The symbol "ICurrentVariantJson" needs to be exported by the entry point index.d.ts - // // (undocumented) _loadCurrentVariantJsonAsync(): Promise; static loadFromConfigurationFile(rushJsonFilename: string): RushConfiguration; diff --git a/libraries/rush-lib/src/api/RushConfiguration.ts b/libraries/rush-lib/src/api/RushConfiguration.ts index f09ee08eb02..74263d2782e 100644 --- a/libraries/rush-lib/src/api/RushConfiguration.ts +++ b/libraries/rush-lib/src/api/RushConfiguration.ts @@ -228,7 +228,10 @@ export class RushConfiguration { private readonly _pathTrees: Map>; - private _currentVariantJsonLoadingPromise: Promise | undefined; + /** + * @internal + */ + public _currentVariantJsonLoadingPromise: Promise | undefined; // Lazily loaded when the projects() getter is called. private _projects: RushConfigurationProject[] | undefined; diff --git a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts index 0cd44fdef20..cf2d902ec16 100644 --- a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts @@ -156,11 +156,8 @@ export abstract class BaseInstallManager { .experimentsConfiguration.configuration.generateProjectImpactGraphDuringRushUpdate ? new ProjectImpactGraphGenerator(this._terminal, this.rushConfiguration) : undefined; - const { shrinkwrapIsUpToDate, npmrcHash, projectImpactGraphIsUpToDate } = await this.prepareAsync( - subspace, - variant, - projectImpactGraphGenerator - ); + const { shrinkwrapIsUpToDate, npmrcHash, projectImpactGraphIsUpToDate, variantIsUpToDate } = + await this.prepareAsync(subspace, variant, projectImpactGraphGenerator); if (this.options.checkOnly) { return; @@ -198,17 +195,18 @@ export abstract class BaseInstallManager { })); // Allow us to defer the file read until we need it - const canSkipInstall: () => boolean = () => { + const canSkipInstallAsync: () => Promise = async () => { // Based on timestamps, can we skip this install entirely? - const outputStats: FileSystemStats = FileSystem.getStatistics(commonTempInstallFlag.path); - return this.canSkipInstall(outputStats.mtime, subspace, variant); + const outputStats: FileSystemStats = await FileSystem.getStatisticsAsync(commonTempInstallFlag.path); + return this.canSkipInstallAsync(outputStats.mtime, subspace, variant); }; if ( resolutionOnly || cleanInstall || + !variantIsUpToDate || !shrinkwrapIsUpToDate || - !canSkipInstall() || + !(await canSkipInstallAsync()) || !projectImpactGraphIsUpToDate ) { // eslint-disable-next-line no-console @@ -380,7 +378,11 @@ export abstract class BaseInstallManager { protected abstract postInstallAsync(subspace: Subspace): Promise; - protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace, variant: string | undefined): boolean { + protected async canSkipInstallAsync( + lastModifiedDate: Date, + subspace: Subspace, + variant: string | undefined + ): Promise { // Based on timestamps, can we skip this install entirely? const potentiallyChangedFiles: string[] = []; @@ -402,14 +404,15 @@ export abstract class BaseInstallManager { if (this.rushConfiguration.packageManager === 'pnpm') { // If the repo is using pnpmfile.js, consider that also - const pnpmFileFilename: string = subspace.getPnpmfilePath(); + const pnpmFileFilePath: string = subspace.getPnpmfilePath(); + const pnpmFileExists: boolean = await FileSystem.existsAsync(pnpmFileFilePath); - if (FileSystem.exists(pnpmFileFilename)) { - potentiallyChangedFiles.push(pnpmFileFilename); + if (pnpmFileExists) { + potentiallyChangedFiles.push(pnpmFileFilePath); } } - return Utilities.isFileTimestampCurrent(lastModifiedDate, potentiallyChangedFiles); + return await Utilities.isFileTimestampCurrentAsync(lastModifiedDate, potentiallyChangedFiles); } protected async prepareAsync( @@ -420,6 +423,7 @@ export abstract class BaseInstallManager { shrinkwrapIsUpToDate: boolean; npmrcHash: string | undefined; projectImpactGraphIsUpToDate: boolean; + variantIsUpToDate: boolean; }> { const terminal: ITerminal = this._terminal; const { allowShrinkwrapUpdates } = this.options; @@ -495,6 +499,7 @@ export abstract class BaseInstallManager { onlyIfChanged: true } )); + this.rushConfiguration._currentVariantJsonLoadingPromise = undefined; if (this.options.variant) { terminal.writeLine(); @@ -666,7 +671,7 @@ export abstract class BaseInstallManager { throw new AlreadyReportedError(); } - return { shrinkwrapIsUpToDate, npmrcHash, projectImpactGraphIsUpToDate }; + return { shrinkwrapIsUpToDate, npmrcHash, projectImpactGraphIsUpToDate, variantIsUpToDate }; } /** diff --git a/libraries/rush-lib/src/logic/installManager/RushInstallManager.ts b/libraries/rush-lib/src/logic/installManager/RushInstallManager.ts index d97e3de4709..03b9c506d8d 100644 --- a/libraries/rush-lib/src/logic/installManager/RushInstallManager.ts +++ b/libraries/rush-lib/src/logic/installManager/RushInstallManager.ts @@ -440,8 +440,12 @@ export class RushInstallManager extends BaseInstallManager { * * @override */ - protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace, variant: string | undefined): boolean { - if (!super.canSkipInstall(lastModifiedDate, subspace, variant)) { + protected async canSkipInstallAsync( + lastModifiedDate: Date, + subspace: Subspace, + variant: string | undefined + ): Promise { + if (!(await super.canSkipInstallAsync(lastModifiedDate, subspace, variant))) { return false; } @@ -456,7 +460,7 @@ export class RushInstallManager extends BaseInstallManager { }) ); - return Utilities.isFileTimestampCurrent(lastModifiedDate, potentiallyChangedFiles); + return Utilities.isFileTimestampCurrentAsync(lastModifiedDate, potentiallyChangedFiles); } /** diff --git a/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts b/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts index cae9090d6d3..8308aef00c8 100644 --- a/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts +++ b/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts @@ -416,8 +416,12 @@ export class WorkspaceInstallManager extends BaseInstallManager { return packageExtensionsChecksum; } - protected canSkipInstall(lastModifiedDate: Date, subspace: Subspace, variant: string | undefined): boolean { - if (!super.canSkipInstall(lastModifiedDate, subspace, variant)) { + protected async canSkipInstallAsync( + lastModifiedDate: Date, + subspace: Subspace, + variant: string | undefined + ): Promise { + if (!(await super.canSkipInstallAsync(lastModifiedDate, subspace, variant))) { return false; } @@ -449,7 +453,7 @@ export class WorkspaceInstallManager extends BaseInstallManager { // NOTE: If any of the potentiallyChangedFiles does not exist, then isFileTimestampCurrent() // returns false. - return Utilities.isFileTimestampCurrent(lastModifiedDate, potentiallyChangedFiles); + return Utilities.isFileTimestampCurrentAsync(lastModifiedDate, potentiallyChangedFiles); } /** diff --git a/libraries/rush-lib/src/utilities/Utilities.ts b/libraries/rush-lib/src/utilities/Utilities.ts index b17dd397887..b6780ff3840 100644 --- a/libraries/rush-lib/src/utilities/Utilities.ts +++ b/libraries/rush-lib/src/utilities/Utilities.ts @@ -14,7 +14,8 @@ import { type FileSystemStats, SubprocessTerminator, Executable, - type IWaitForExitResult + type IWaitForExitResult, + Async } from '@rushstack/node-core-library'; import type { RushConfiguration } from '../api/RushConfiguration'; @@ -290,19 +291,35 @@ export class Utilities { * NOTE: The filenames can also be paths for directories, in which case the directory * timestamp is compared. */ - public static isFileTimestampCurrent(dateToCompare: Date, inputFilenames: string[]): boolean { - for (const inputFilename of inputFilenames) { - if (!FileSystem.exists(inputFilename)) { - return false; - } + public static async isFileTimestampCurrentAsync( + dateToCompare: Date, + inputFilePaths: string[] + ): Promise { + let anyAreOutOfDate: boolean = false; + await Async.forEachAsync( + inputFilePaths, + async (filePath) => { + if (!anyAreOutOfDate) { + let inputStats: FileSystemStats | undefined; + try { + inputStats = FileSystem.getStatistics(filePath); + } catch (e) { + if (FileSystem.isNotExistError(e)) { + anyAreOutOfDate = true; + } else { + throw e; + } + } - const inputStats: FileSystemStats = FileSystem.getStatistics(inputFilename); - if (dateToCompare < inputStats.mtime) { - return false; - } - } + if (inputStats && dateToCompare < inputStats.mtime) { + anyAreOutOfDate = true; + } + } + }, + { concurrency: 10 } + ); - return true; + return !anyAreOutOfDate; } public static async executeCommandAsync( From 3cf96b747e839a94fe0d3f7210b9582739684d88 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Wed, 18 Sep 2024 17:03:36 -0700 Subject: [PATCH 05/24] fixup! Bring back the Variants feature. --- common/reviews/api/rush-lib.api.md | 2 -- libraries/rush-lib/src/api/RushConfiguration.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index bfc597aa0d0..ca8d5dd4612 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1210,8 +1210,6 @@ export class RushConfiguration { readonly gitTagSeparator: string | undefined; readonly gitVersionBumpCommitMessage: string | undefined; readonly hotfixChangeEnabled: boolean; - // (undocumented) - _loadCurrentVariantJsonAsync(): Promise; static loadFromConfigurationFile(rushJsonFilename: string): RushConfiguration; // (undocumented) static loadFromDefaultLocation(options?: ITryFindRushJsonLocationOptions): RushConfiguration; diff --git a/libraries/rush-lib/src/api/RushConfiguration.ts b/libraries/rush-lib/src/api/RushConfiguration.ts index 74263d2782e..628de258833 100644 --- a/libraries/rush-lib/src/api/RushConfiguration.ts +++ b/libraries/rush-lib/src/api/RushConfiguration.ts @@ -1579,7 +1579,7 @@ export class RushConfiguration { return undefined; } - public async _loadCurrentVariantJsonAsync(): Promise { + private async _loadCurrentVariantJsonAsync(): Promise { try { return await JsonFile.loadAsync(this.currentVariantJsonFilePath); } catch (e) { From 1695855b59cc929836b82112e5ee97f07314e5c8 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Wed, 18 Sep 2024 17:05:23 -0700 Subject: [PATCH 06/24] fixup! Bring back the Variants feature. --- common/reviews/api/rush-lib.api.md | 2 +- libraries/rush-lib/src/api/Subspace.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index ca8d5dd4612..b14ab08758f 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1476,7 +1476,7 @@ export class Subspace { // @beta getCommonVersionsFilePath(variant?: string): string; // @beta - getPackageJsonInjectedDependenciesHash(variant: string | undefined): string | undefined; + getPackageJsonInjectedDependenciesHash(variant?: string): string | undefined; // @beta getPnpmConfigFilePath(): string; // @beta diff --git a/libraries/rush-lib/src/api/Subspace.ts b/libraries/rush-lib/src/api/Subspace.ts index 879a90de2c4..c9d980b396f 100644 --- a/libraries/rush-lib/src/api/Subspace.ts +++ b/libraries/rush-lib/src/api/Subspace.ts @@ -408,7 +408,7 @@ export class Subspace { * Returns hash value of injected dependencies in related package.json. * @beta */ - public getPackageJsonInjectedDependenciesHash(variant: string | undefined): string | undefined { + public getPackageJsonInjectedDependenciesHash(variant?: string): string | undefined { const allPackageJson: IPackageJsonLite[] = []; const relatedProjects: RushConfigurationProject[] = []; From 52165673f50fb1f3cf8b2b9840ae39b4e8932052 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Wed, 18 Sep 2024 17:08:23 -0700 Subject: [PATCH 07/24] fixup! Bring back the Variants feature. --- libraries/rush-lib/src/cli/actions/CheckAction.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/rush-lib/src/cli/actions/CheckAction.ts b/libraries/rush-lib/src/cli/actions/CheckAction.ts index f3bd0db0baa..0484995baf3 100644 --- a/libraries/rush-lib/src/cli/actions/CheckAction.ts +++ b/libraries/rush-lib/src/cli/actions/CheckAction.ts @@ -2,7 +2,7 @@ // See LICENSE in the project root for license information. import type { CommandLineFlagParameter, CommandLineStringParameter } from '@rushstack/ts-command-line'; -import { Colorize, ConsoleTerminalProvider, type ITerminal, Terminal } from '@rushstack/terminal'; +import { Colorize, type ITerminal } from '@rushstack/terminal'; import type { RushCommandLineParser } from '../RushCommandLineParser'; import { BaseRushAction } from './BaseRushAction'; @@ -29,7 +29,7 @@ export class CheckAction extends BaseRushAction { parser }); - this._terminal = new Terminal(new ConsoleTerminalProvider({ verboseEnabled: parser.isDebug })); + this._terminal = parser.terminal; this._jsonFlag = this.defineFlagParameter({ parameterLongName: '--json', description: 'If this flag is specified, output will be in JSON format.' @@ -62,7 +62,6 @@ export class CheckAction extends BaseRushAction { await this.rushConfiguration.getCurrentlyInstalledVariantAsync(); const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); if (!variant && currentlyInstalledVariant) { - // eslint-disable-next-line no-console this._terminal.writeWarningLine( Colorize.yellow( `Variant '${currentlyInstalledVariant}' has been installed, but 'rush check' is currently checking the default variant. ` + From abde636484ca452f9160249c48aa13bd8f25ac1b Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 19 Sep 2024 15:58:47 -0400 Subject: [PATCH 08/24] fixup! Some cleanup to Subspace.ts --- .../bring-back-variants_2024-09-19-19-58.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@rushstack/lockfile-explorer/bring-back-variants_2024-09-19-19-58.json diff --git a/common/changes/@rushstack/lockfile-explorer/bring-back-variants_2024-09-19-19-58.json b/common/changes/@rushstack/lockfile-explorer/bring-back-variants_2024-09-19-19-58.json new file mode 100644 index 00000000000..577a9d62ef7 --- /dev/null +++ b/common/changes/@rushstack/lockfile-explorer/bring-back-variants_2024-09-19-19-58.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/lockfile-explorer", + "comment": "Update to use a new API from rush-sdk.", + "type": "minor" + } + ], + "packageName": "@rushstack/lockfile-explorer" +} \ No newline at end of file From 46c4fdcfe8c94055e061145a335a98c80588c833 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 19 Sep 2024 18:36:13 -0400 Subject: [PATCH 09/24] API clean up and plumb variants into a few more places. --- common/reviews/api/rush-lib.api.md | 4 +-- .../rush-lib/src/api/RushConfiguration.ts | 4 +-- libraries/rush-lib/src/api/Subspace.ts | 2 +- .../src/api/test/RushConfiguration.test.ts | 4 +-- .../RushCommandLine.test.ts.snap | 16 ++++++++++++ .../rush-lib/src/cli/actions/AddAction.ts | 15 +++++++++-- .../src/cli/actions/BaseInstallAction.ts | 1 + .../rush-lib/src/cli/actions/InstallAction.ts | 5 ++-- .../rush-lib/src/cli/actions/RemoveAction.ts | 25 +++++++++++++------ .../rush-lib/src/cli/actions/UpdateAction.ts | 5 ++-- .../cli/scriptActions/PhasedScriptAction.ts | 5 ++-- .../CommandLineHelp.test.ts.snap | 10 ++++++-- .../rush-lib/src/logic/DependencyAnalyzer.ts | 15 ++++++----- .../rush-lib/src/logic/PackageJsonUpdater.ts | 2 +- .../src/logic/PackageJsonUpdaterTypes.ts | 2 +- .../src/logic/ProjectChangeAnalyzer.ts | 20 +++++++++++---- .../installManager/doBasicInstallAsync.ts | 5 +++- .../src/logic/pnpm/PnpmfileConfiguration.ts | 2 +- .../src/logic/test/DependencyAnalyzer.test.ts | 2 +- .../logic/test/ProjectChangeAnalyzer.test.ts | 10 +++++--- .../versionMismatch/VersionMismatchFinder.ts | 2 +- .../src/pluginFramework/RushLifeCycle.ts | 16 +++++++----- .../src/afterInstallAsync.ts | 3 ++- .../rush-resolver-cache-plugin/src/index.ts | 4 +-- 24 files changed, 123 insertions(+), 56 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index b14ab08758f..2a6c0dab078 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1400,8 +1400,8 @@ export class _RushInternals { // @beta export class RushLifecycleHooks { - readonly afterInstall: AsyncSeriesHook<[IRushCommand, Subspace]>; - readonly beforeInstall: AsyncSeriesHook<[IGlobalCommand, Subspace]>; + readonly afterInstall: AsyncSeriesHook<[IRushCommand, Subspace, string | undefined]>; + readonly beforeInstall: AsyncSeriesHook<[IGlobalCommand, Subspace, string | undefined]>; readonly flushTelemetry: AsyncParallelHook<[ReadonlyArray]>; readonly initialize: AsyncSeriesHook; readonly runAnyGlobalCustomCommand: AsyncSeriesHook; diff --git a/libraries/rush-lib/src/api/RushConfiguration.ts b/libraries/rush-lib/src/api/RushConfiguration.ts index 628de258833..396b648834d 100644 --- a/libraries/rush-lib/src/api/RushConfiguration.ts +++ b/libraries/rush-lib/src/api/RushConfiguration.ts @@ -1464,7 +1464,7 @@ export class RushConfiguration { const dependencyAnalyzer: DependencyAnalyzerModuleType.DependencyAnalyzer = DependencyAnalyzerModule.DependencyAnalyzer.forRushConfiguration(this); const dependencyAnalysis: DependencyAnalyzerModuleType.IDependencyAnalysis = - dependencyAnalyzer.getAnalysis(subspace, variant); + dependencyAnalyzer.getAnalysis(subspace, variant, false); return dependencyAnalysis.implicitlyPreferredVersionByPackageName; } @@ -1490,7 +1490,7 @@ export class RushConfiguration { } /** - * @deprecated Use {@link Subspace.getRepoStateFilePath} instead + * @deprecated Use {@link Subspace.getPnpmfilePath} instead */ public getPnpmfilePath(subspace?: Subspace): string { return (subspace ?? this.defaultSubspace).getPnpmfilePath(); diff --git a/libraries/rush-lib/src/api/Subspace.ts b/libraries/rush-lib/src/api/Subspace.ts index c9d980b396f..b56b2e968c5 100644 --- a/libraries/rush-lib/src/api/Subspace.ts +++ b/libraries/rush-lib/src/api/Subspace.ts @@ -362,7 +362,7 @@ export class Subspace { * @deprecated - Use {@link Subspace.getCommittedShrinkwrapFilePath} instead. */ public getCommittedShrinkwrapFilename(): string { - return this.getCommittedShrinkwrapFilePath(); + return this.getCommittedShrinkwrapFilePath(undefined); } /** diff --git a/libraries/rush-lib/src/api/test/RushConfiguration.test.ts b/libraries/rush-lib/src/api/test/RushConfiguration.test.ts index b20596e935f..6331bf68425 100644 --- a/libraries/rush-lib/src/api/test/RushConfiguration.test.ts +++ b/libraries/rush-lib/src/api/test/RushConfiguration.test.ts @@ -118,7 +118,7 @@ describe(RushConfiguration.name, () => { expect(rushConfiguration.shrinkwrapFilename).toEqual('pnpm-lock.yaml'); assertPathProperty( 'getPnpmfilePath', - rushConfiguration.getPnpmfilePath(), + rushConfiguration.defaultSubspace.getPnpmfilePath(), './repo/common/config/rush/.pnpmfile.cjs' ); assertPathProperty('commonFolder', rushConfiguration.commonFolder, './repo/common'); @@ -185,7 +185,7 @@ describe(RushConfiguration.name, () => { expect(rushConfiguration.shrinkwrapFilename).toEqual('pnpm-lock.yaml'); assertPathProperty( 'getPnpmfilePath', - rushConfiguration.getPnpmfilePath(), + rushConfiguration.defaultSubspace.getPnpmfilePath(), './repo/common/config/rush/pnpmfile.js' ); expect(rushConfiguration.repositoryUrls).toEqual(['someFakeUrl', 'otherFakeUrl']); diff --git a/libraries/rush-lib/src/api/test/__snapshots__/RushCommandLine.test.ts.snap b/libraries/rush-lib/src/api/test/__snapshots__/RushCommandLine.test.ts.snap index e76411f4531..f36ce6efc6f 100644 --- a/libraries/rush-lib/src/api/test/__snapshots__/RushCommandLine.test.ts.snap +++ b/libraries/rush-lib/src/api/test/__snapshots__/RushCommandLine.test.ts.snap @@ -70,6 +70,14 @@ Object { "required": false, "shortName": undefined, }, + Object { + "description": "Run command using a variant installation configuration", + "environmentVariable": "RUSH_VARIANT", + "kind": "String", + "longName": "--variant", + "required": false, + "shortName": undefined, + }, ], }, Object { @@ -815,6 +823,14 @@ Object { "required": false, "shortName": undefined, }, + Object { + "description": "Run command using a variant installation configuration", + "environmentVariable": "RUSH_VARIANT", + "kind": "String", + "longName": "--variant", + "required": false, + "shortName": undefined, + }, ], }, Object { diff --git a/libraries/rush-lib/src/cli/actions/AddAction.ts b/libraries/rush-lib/src/cli/actions/AddAction.ts index eca166513a8..2358f5f2e05 100644 --- a/libraries/rush-lib/src/cli/actions/AddAction.ts +++ b/libraries/rush-lib/src/cli/actions/AddAction.ts @@ -2,7 +2,11 @@ // See LICENSE in the project root for license information. import * as semver from 'semver'; -import type { CommandLineFlagParameter, CommandLineStringListParameter } from '@rushstack/ts-command-line'; +import type { + CommandLineFlagParameter, + CommandLineStringListParameter, + CommandLineStringParameter +} from '@rushstack/ts-command-line'; import { BaseAddAndRemoveAction } from './BaseAddAndRemoveAction'; import type { RushCommandLineParser } from '../RushCommandLineParser'; @@ -13,6 +17,7 @@ import { type IPackageJsonUpdaterRushAddOptions, SemVerStyle } from '../../logic/PackageJsonUpdaterTypes'; +import { getVariant, VARIANT_PARAMETER } from '../../api/Variants'; export class AddAction extends BaseAddAndRemoveAction { protected readonly _allFlag: CommandLineFlagParameter; @@ -22,6 +27,7 @@ export class AddAction extends BaseAddAndRemoveAction { private readonly _devDependencyFlag: CommandLineFlagParameter; private readonly _peerDependencyFlag: CommandLineFlagParameter; private readonly _makeConsistentFlag: CommandLineFlagParameter; + private readonly _variantParameter: CommandLineStringParameter; public constructor(parser: RushCommandLineParser) { const documentation: string = [ @@ -85,6 +91,7 @@ export class AddAction extends BaseAddAndRemoveAction { parameterLongName: '--all', description: 'If specified, the dependency will be added to all projects.' }); + this._variantParameter = this.defineStringParameter(VARIANT_PARAMETER); } public getUpdateOptions(): IPackageJsonUpdaterRushAddOptions { @@ -149,6 +156,9 @@ export class AddAction extends BaseAddAndRemoveAction { packagesToAdd.push({ packageName, version, rangeStyle }); } + + const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); + return { projects: projects, packagesToUpdate: packagesToAdd, @@ -157,7 +167,8 @@ export class AddAction extends BaseAddAndRemoveAction { updateOtherPackages: this._makeConsistentFlag.value, skipUpdate: this._skipUpdateFlag.value, debugInstall: this.parser.isDebug, - actionName: this.actionName + actionName: this.actionName, + variant }; } } diff --git a/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts b/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts index 6460b8e5530..72666805f78 100644 --- a/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts +++ b/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts @@ -180,6 +180,7 @@ export abstract class BaseInstallAction extends BaseRushAction { } } else { VersionMismatchFinder.ensureConsistentVersions(this.rushConfiguration, this._terminal, { + subspace: undefined, variant }); } diff --git a/libraries/rush-lib/src/cli/actions/InstallAction.ts b/libraries/rush-lib/src/cli/actions/InstallAction.ts index 63e9e1f89b0..a8c843d7449 100644 --- a/libraries/rush-lib/src/cli/actions/InstallAction.ts +++ b/libraries/rush-lib/src/cli/actions/InstallAction.ts @@ -86,8 +86,9 @@ export class InstallAction extends BaseInstallAction { checkOnly: this._checkOnlyParameter.value, resolutionOnly: this._resolutionOnlyParameter?.value, beforeInstallAsync: (subspace: Subspace) => - this.rushSession.hooks.beforeInstall.promise(this, subspace), - afterInstallAsync: (subspace: Subspace) => this.rushSession.hooks.afterInstall.promise(this, subspace), + this.rushSession.hooks.beforeInstall.promise(this, subspace, variant), + afterInstallAsync: (subspace: Subspace) => + this.rushSession.hooks.afterInstall.promise(this, subspace, variant), terminal: this._terminal }; } diff --git a/libraries/rush-lib/src/cli/actions/RemoveAction.ts b/libraries/rush-lib/src/cli/actions/RemoveAction.ts index 4b953fe4b54..cd49dfecff0 100644 --- a/libraries/rush-lib/src/cli/actions/RemoveAction.ts +++ b/libraries/rush-lib/src/cli/actions/RemoveAction.ts @@ -1,8 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { ConsoleTerminalProvider, Terminal, type ITerminal } from '@rushstack/terminal'; -import type { CommandLineFlagParameter, CommandLineStringListParameter } from '@rushstack/ts-command-line'; +import type { ITerminal } from '@rushstack/terminal'; +import type { + CommandLineFlagParameter, + CommandLineStringListParameter, + CommandLineStringParameter +} from '@rushstack/ts-command-line'; import { BaseAddAndRemoveAction } from './BaseAddAndRemoveAction'; import type { RushCommandLineParser } from '../RushCommandLineParser'; @@ -11,12 +15,13 @@ import type { IPackageForRushRemove, IPackageJsonUpdaterRushRemoveOptions } from '../../logic/PackageJsonUpdaterTypes'; +import { getVariant, VARIANT_PARAMETER } from '../../api/Variants'; export class RemoveAction extends BaseAddAndRemoveAction { protected readonly _allFlag: CommandLineFlagParameter; protected readonly _packageNameList: CommandLineStringListParameter; - private _terminalProvider: ConsoleTerminalProvider; - private _terminal: ITerminal; + private readonly _variantParameter: CommandLineStringParameter; + private readonly _terminal: ITerminal; public constructor(parser: RushCommandLineParser) { const documentation: string = [ @@ -30,8 +35,8 @@ export class RemoveAction extends BaseAddAndRemoveAction { safeForSimultaneousRushProcesses: false, parser }); - this._terminalProvider = new ConsoleTerminalProvider(); - this._terminal = new Terminal(this._terminalProvider); + + this._terminal = parser.terminal; this._packageNameList = this.defineStringListParameter({ parameterLongName: '--package', @@ -46,6 +51,7 @@ export class RemoveAction extends BaseAddAndRemoveAction { parameterLongName: '--all', description: 'If specified, the dependency will be removed from all projects that declare it.' }); + this._variantParameter = this.defineStringParameter(VARIANT_PARAMETER); } public getUpdateOptions(): IPackageJsonUpdaterRushRemoveOptions { @@ -69,7 +75,7 @@ export class RemoveAction extends BaseAddAndRemoveAction { !project.packageJsonEditor.tryGetDevDependency(packageName) ) { this._terminal.writeLine( - `The project "${project.packageName}" do not have ${packageName} in package.json.` + `The project "${project.packageName}" does not have "${packageName}" in package.json.` ); } } @@ -77,12 +83,15 @@ export class RemoveAction extends BaseAddAndRemoveAction { packagesToRemove.push({ packageName }); } + const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); + return { projects: projects, packagesToUpdate: packagesToRemove, skipUpdate: this._skipUpdateFlag.value, debugInstall: this.parser.isDebug, - actionName: this.actionName + actionName: this.actionName, + variant }; } } diff --git a/libraries/rush-lib/src/cli/actions/UpdateAction.ts b/libraries/rush-lib/src/cli/actions/UpdateAction.ts index 3e6c8d71b95..3bea78d0aa1 100644 --- a/libraries/rush-lib/src/cli/actions/UpdateAction.ts +++ b/libraries/rush-lib/src/cli/actions/UpdateAction.ts @@ -108,8 +108,9 @@ export class UpdateAction extends BaseInstallAction { (await this._selectionParameters?.getPnpmFilterArgumentValuesAsync(this._terminal)) ?? [], checkOnly: false, beforeInstallAsync: (subspace: Subspace) => - this.rushSession.hooks.beforeInstall.promise(this, subspace), - afterInstallAsync: (subspace: Subspace) => this.rushSession.hooks.afterInstall.promise(this, subspace), + this.rushSession.hooks.beforeInstall.promise(this, subspace, variant), + afterInstallAsync: (subspace: Subspace) => + this.rushSession.hooks.afterInstall.promise(this, subspace, variant), terminal: this._terminal }; } diff --git a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts index 2f4d752227f..03485db93c8 100644 --- a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts @@ -304,8 +304,9 @@ export class PhasedScriptAction extends BaseScriptAction { isDebug: this.parser.isDebug, variant: currentlyInstalledVariant, beforeInstallAsync: (subspace: Subspace) => - this.rushSession.hooks.beforeInstall.promise(this, subspace), - afterInstallAsync: (subspace: Subspace) => this.rushSession.hooks.afterInstall.promise(this, subspace) + this.rushSession.hooks.beforeInstall.promise(this, subspace, currentlyInstalledVariant), + afterInstallAsync: (subspace: Subspace) => + this.rushSession.hooks.afterInstall.promise(this, subspace, currentlyInstalledVariant) }); } diff --git a/libraries/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap b/libraries/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap index dd2e62cc644..007ff234fc5 100644 --- a/libraries/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap +++ b/libraries/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap @@ -83,7 +83,7 @@ Optional arguments: exports[`CommandLineHelp prints the help for each action: add 1`] = ` "usage: rush add [-h] [-s] -p PACKAGE [--exact] [--caret] [--dev] [--peer] [-m] - [--all] + [--all] [--variant VARIANT] Adds specified package(s) to the dependencies of the current project (as @@ -126,6 +126,9 @@ Optional arguments: same version of the dependency. --all If specified, the dependency will be added to all projects. + --variant VARIANT Run command using a variant installation + configuration. This parameter may alternatively be + specified via the RUSH_VARIANT environment variable. " `; @@ -1149,7 +1152,7 @@ Optional arguments: `; exports[`CommandLineHelp prints the help for each action: remove 1`] = ` -"usage: rush remove [-h] [-s] -p PACKAGE [--all] +"usage: rush remove [-h] [-s] -p PACKAGE [--all] [--variant VARIANT] Removes specified package(s) from the dependencies of the current project (as determined by the current working directory) and then runs \\"rush update\\". @@ -1164,6 +1167,9 @@ Optional arguments: foo --package bar\\". --all If specified, the dependency will be removed from all projects that declare it. + --variant VARIANT Run command using a variant installation + configuration. This parameter may alternatively be + specified via the RUSH_VARIANT environment variable. " `; diff --git a/libraries/rush-lib/src/logic/DependencyAnalyzer.ts b/libraries/rush-lib/src/logic/DependencyAnalyzer.ts index 16b70ff66ca..531cd46b69f 100644 --- a/libraries/rush-lib/src/logic/DependencyAnalyzer.ts +++ b/libraries/rush-lib/src/logic/DependencyAnalyzer.ts @@ -54,7 +54,11 @@ export class DependencyAnalyzer { return analyzer; } - public getAnalysis(subspace?: Subspace, variant?: string, addAction?: boolean): IDependencyAnalysis { + public getAnalysis( + subspace: Subspace | undefined, + variant: string | undefined, + addAction: boolean + ): IDependencyAnalysis { // Use an empty string as the key when no variant provided. Anything else would possibly conflict // with a variant created by the user const variantKey: string = variant || ''; @@ -73,13 +77,8 @@ export class DependencyAnalyzer { } let analysisForSubspace: IDependencyAnalysis | undefined = analysisForVariant.get(subspaceToAnalyze); - if (!analysisForSubspace) { - analysisForSubspace = this._getAnalysisInternal( - subspace || this._rushConfiguration.defaultSubspace, - variant, - addAction - ); + analysisForSubspace = this._getAnalysisInternal(subspaceToAnalyze, variant, addAction); analysisForVariant.set(subspaceToAnalyze, analysisForSubspace); } @@ -96,7 +95,7 @@ export class DependencyAnalyzer { private _getAnalysisInternal( subspace: Subspace, variant: string | undefined, - addAction?: boolean + addAction: boolean ): IDependencyAnalysis { const commonVersionsConfiguration: CommonVersionsConfiguration = subspace.getCommonVersions(variant); const allVersionsByPackageName: Map> = new Map(); diff --git a/libraries/rush-lib/src/logic/PackageJsonUpdater.ts b/libraries/rush-lib/src/logic/PackageJsonUpdater.ts index ffcfcb54098..7f24813d06b 100644 --- a/libraries/rush-lib/src/logic/PackageJsonUpdater.ts +++ b/libraries/rush-lib/src/logic/PackageJsonUpdater.ts @@ -128,7 +128,7 @@ export class PackageJsonUpdater { allVersionsByPackageName, implicitlyPreferredVersionByPackageName, commonVersionsConfiguration - }: IDependencyAnalysis = dependencyAnalyzer.getAnalysis(); + }: IDependencyAnalysis = dependencyAnalyzer.getAnalysis(undefined, variant, false); const dependenciesToUpdate: Record = {}; const devDependenciesToUpdate: Record = {}; diff --git a/libraries/rush-lib/src/logic/PackageJsonUpdaterTypes.ts b/libraries/rush-lib/src/logic/PackageJsonUpdaterTypes.ts index 00d4af19661..bdc9ee6b285 100644 --- a/libraries/rush-lib/src/logic/PackageJsonUpdaterTypes.ts +++ b/libraries/rush-lib/src/logic/PackageJsonUpdaterTypes.ts @@ -56,7 +56,7 @@ export interface IPackageJsonUpdaterRushBaseUpdateOptions { /** * The variant to consider when performing installations and validating shrinkwrap updates. */ - variant?: string | undefined; + variant: string | undefined | undefined; } /** diff --git a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts index bc2c1408ce0..b70219a18b5 100644 --- a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts +++ b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts @@ -236,10 +236,15 @@ export class ProjectChangeAnalyzer { // Even though changing the installed version of a nested dependency merits a change file, // ignore lockfile changes for `rush change` for the moment - const fullShrinkwrapPath: string = rushConfiguration.getCommittedShrinkwrapFilename(); + const currentVariant: string | undefined = + await this._rushConfiguration.getCurrentlyInstalledVariantAsync(); + const fullShrinkwrapPath: string = + rushConfiguration.defaultSubspace.getCommittedShrinkwrapFilePath(currentVariant); - const shrinkwrapFile: string = Path.convertToSlashes(path.relative(repoRoot, fullShrinkwrapPath)); - const shrinkwrapStatus: IFileDiffStatus | undefined = repoChanges.get(shrinkwrapFile); + const relativeShrinkwrapFilePath: string = Path.convertToSlashes( + path.relative(repoRoot, fullShrinkwrapPath) + ); + const shrinkwrapStatus: IFileDiffStatus | undefined = repoChanges.get(relativeShrinkwrapFilePath); if (shrinkwrapStatus) { if (shrinkwrapStatus.status !== 'M') { @@ -259,7 +264,7 @@ export class ProjectChangeAnalyzer { const oldShrinkwrapText: string = await this._git.getBlobContentAsync({ // : syntax: https://git-scm.com/docs/gitrevisions - blobSpec: `${mergeCommit}:${shrinkwrapFile}`, + blobSpec: `${mergeCommit}:${relativeShrinkwrapFilePath}`, repositoryRoot: repoRoot }); const oldShrinkWrap: PnpmShrinkwrapFile = PnpmShrinkwrapFile.loadFromString(oldShrinkwrapText); @@ -354,9 +359,14 @@ export class ProjectChangeAnalyzer { // Currently, only pnpm handles project shrinkwraps if (this._rushConfiguration.packageManager !== 'pnpm') { + const currentVariant: string | undefined = + await this._rushConfiguration.getCurrentlyInstalledVariantAsync(); // Add the shrinkwrap file to every project's dependencies const shrinkwrapFile: string = Path.convertToSlashes( - path.relative(rootDir, this._rushConfiguration.getCommittedShrinkwrapFilename()) + path.relative( + rootDir, + this._rushConfiguration.defaultSubspace.getCommittedShrinkwrapFilePath(currentVariant) + ) ); const shrinkwrapHash: string | undefined = repoDeps.get(shrinkwrapFile); diff --git a/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts b/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts index 58bd6148f8d..2a5a2cc0629 100644 --- a/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts +++ b/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts @@ -33,7 +33,10 @@ export async function doBasicInstallAsync(options: IRunInstallOptions): Promise< afterInstallAsync } = options; - VersionMismatchFinder.ensureConsistentVersions(rushConfiguration, terminal, { variant }); + VersionMismatchFinder.ensureConsistentVersions(rushConfiguration, terminal, { + variant, + subspace: undefined + }); SetupChecks.validate(rushConfiguration); const purgeManager: typeof PurgeManager.prototype = new PurgeManager(rushConfiguration, rushGlobalFolder); diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts b/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts index 1ffff5da450..de09bb658f7 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts @@ -98,7 +98,7 @@ export class PnpmfileConfiguration { const preferredVersions: Map = new Map(); MapExtensions.mergeFromMap( preferredVersions, - rushConfiguration.getImplicitlyPreferredVersions(subspace) + rushConfiguration.getImplicitlyPreferredVersions(subspace, variant) ); for (const [name, version] of commonVersionsConfiguration.getAllPreferredVersions()) { // Use the most restrictive version range available diff --git a/libraries/rush-lib/src/logic/test/DependencyAnalyzer.test.ts b/libraries/rush-lib/src/logic/test/DependencyAnalyzer.test.ts index 392e3819221..04f192fef8a 100644 --- a/libraries/rush-lib/src/logic/test/DependencyAnalyzer.test.ts +++ b/libraries/rush-lib/src/logic/test/DependencyAnalyzer.test.ts @@ -10,7 +10,7 @@ describe(DependencyAnalyzer.name, () => { `${__dirname}/DependencyAnalyzerTestRepos/${repoName}/rush.json` ); const dependencyAnalyzer: DependencyAnalyzer = DependencyAnalyzer.forRushConfiguration(rushConfiguration); - const analysis: IDependencyAnalysis = dependencyAnalyzer.getAnalysis(); + const analysis: IDependencyAnalysis = dependencyAnalyzer.getAnalysis(undefined, undefined, false); return analysis; } diff --git a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts index 1c328acc28b..8e980ad98c8 100644 --- a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts +++ b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts @@ -10,6 +10,7 @@ import { EnvironmentConfiguration } from '../../api/EnvironmentConfiguration'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; import { RushProjectConfiguration } from '../../api/RushProjectConfiguration'; import { UNINITIALIZED } from '../../utilities/Utilities'; +import type { Subspace } from '../../api/Subspace'; describe(ProjectChangeAnalyzer.name, () => { beforeEach(() => { @@ -29,8 +30,10 @@ describe(ProjectChangeAnalyzer.name, () => { commonRushConfigFolder: '', projects, rushJsonFolder: '', - getCommittedShrinkwrapFilename(): string { - return 'common/config/rush/pnpm-lock.yaml'; + defaultSubspace: { + getCommittedShrinkwrapFilePath(variant: string | undefined): string { + return 'common/config/rush/pnpm-lock.yaml'; + } }, getProjectLookupForRoot(root: string): LookupByPath { const lookup: LookupByPath = new LookupByPath(); @@ -41,7 +44,8 @@ describe(ProjectChangeAnalyzer.name, () => { }, getProjectByName(name: string): RushConfigurationProject | undefined { return projects.find((project) => project.packageName === name); - } + }, + getCurrentlyInstalledVariantAsync: () => Promise.resolve(undefined) } as RushConfiguration; const subject: ProjectChangeAnalyzer = new ProjectChangeAnalyzer(rushConfiguration); diff --git a/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts b/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts index 90312e83ea2..70e26c61826 100644 --- a/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts +++ b/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts @@ -17,7 +17,7 @@ const TRUNCATE_AFTER_PACKAGE_NAME_COUNT: number = 5; export interface IVersionMismatchFinderOptions { subspace?: Subspace; - variant?: string; + variant: string | undefined; } export interface IVersionMismatchFinderRushCheckOptions extends IVersionMismatchFinderOptions { diff --git a/libraries/rush-lib/src/pluginFramework/RushLifeCycle.ts b/libraries/rush-lib/src/pluginFramework/RushLifeCycle.ts index a371be204f7..73555e1ae94 100644 --- a/libraries/rush-lib/src/pluginFramework/RushLifeCycle.ts +++ b/libraries/rush-lib/src/pluginFramework/RushLifeCycle.ts @@ -86,16 +86,20 @@ export class RushLifecycleHooks { /** * The hook to run between preparing the common/temp folder and invoking the package manager during "rush install" or "rush update". */ - public readonly beforeInstall: AsyncSeriesHook<[IGlobalCommand, Subspace]> = new AsyncSeriesHook< - [IGlobalCommand, Subspace] - >(['command', 'subspace'], 'beforeInstall'); + public readonly beforeInstall: AsyncSeriesHook<[IGlobalCommand, Subspace, string | undefined]> = + new AsyncSeriesHook<[IGlobalCommand, Subspace, string | undefined]>( + ['command', 'subspace', 'variant'], + 'beforeInstall' + ); /** * The hook to run after a successful install. */ - public readonly afterInstall: AsyncSeriesHook<[IRushCommand, Subspace]> = new AsyncSeriesHook< - [IRushCommand, Subspace] - >(['command', 'subspace'], 'afterInstall'); + public readonly afterInstall: AsyncSeriesHook<[IRushCommand, Subspace, string | undefined]> = + new AsyncSeriesHook<[IRushCommand, Subspace, string | undefined]>( + ['command', 'subspace', 'variant'], + 'afterInstall' + ); /** * A hook to allow plugins to hook custom logic to process telemetry data. diff --git a/rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts b/rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts index 44872a8a400..3cf29ddf628 100644 --- a/rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts +++ b/rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts @@ -51,12 +51,13 @@ export async function afterInstallAsync( rushSession: RushSession, rushConfiguration: RushConfiguration, subspace: Subspace, + variant: string | undefined, logger: ILogger ): Promise { const { terminal } = logger; const rushRoot: string = `${rushConfiguration.rushJsonFolder}/`; - const lockFilePath: string = subspace.getCommittedShrinkwrapFilePath(); + const lockFilePath: string = subspace.getCommittedShrinkwrapFilePath(variant); const workspaceRoot: string = subspace.getSubspaceTempFolderPath(); const projectByImporterPath: LookupByPath = diff --git a/rush-plugins/rush-resolver-cache-plugin/src/index.ts b/rush-plugins/rush-resolver-cache-plugin/src/index.ts index b90fcae88c8..8f032b3e939 100644 --- a/rush-plugins/rush-resolver-cache-plugin/src/index.ts +++ b/rush-plugins/rush-resolver-cache-plugin/src/index.ts @@ -20,7 +20,7 @@ export default class RushResolverCachePlugin implements IRushPlugin { public apply(rushSession: RushSession, rushConfiguration: RushConfiguration): void { rushSession.hooks.afterInstall.tapPromise( this.pluginName, - async (command: IRushCommand, subspace: Subspace) => { + async (command: IRushCommand, subspace: Subspace, variant: string | undefined) => { const logger: ILogger = rushSession.getLogger('RushResolverCachePlugin'); if (rushConfiguration.packageManager !== 'pnpm') { @@ -47,7 +47,7 @@ export default class RushResolverCachePlugin implements IRushPlugin { './afterInstallAsync' ); - await afterInstallAsync(rushSession, rushConfiguration, subspace, logger); + await afterInstallAsync(rushSession, rushConfiguration, subspace, variant, logger); } ); } From 59a1bf3aa7b8a9cfd9a68677b079543b0a77f587 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 19 Sep 2024 18:41:11 -0400 Subject: [PATCH 10/24] fixup! API clean up and plumb variants into a few more places. --- libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts index 8e980ad98c8..473b81c5444 100644 --- a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts +++ b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts @@ -10,7 +10,6 @@ import { EnvironmentConfiguration } from '../../api/EnvironmentConfiguration'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; import { RushProjectConfiguration } from '../../api/RushProjectConfiguration'; import { UNINITIALIZED } from '../../utilities/Utilities'; -import type { Subspace } from '../../api/Subspace'; describe(ProjectChangeAnalyzer.name, () => { beforeEach(() => { From 21adbd8efe08d20ed8e1a956dc0e78cca66cf0f2 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 19 Sep 2024 18:47:06 -0400 Subject: [PATCH 11/24] fixup! API clean up and plumb variants into a few more places. --- rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts b/rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts index 3cf29ddf628..e7e347c7fb3 100644 --- a/rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts +++ b/rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts @@ -45,6 +45,7 @@ function getPlatformInfo(): IPlatformInfo { * @param rushSession - The Rush Session * @param rushConfiguration - The Rush Configuration * @param subspace - The subspace that was just installed + * @param variant - The variant that was just installed * @param logger - The initialized logger */ export async function afterInstallAsync( From efcb3f92b53cffb48307d20ea8415252529ce395 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Fri, 20 Sep 2024 14:28:27 -0400 Subject: [PATCH 12/24] Grab the currently installed variant in a few cases. --- libraries/rush-lib/src/api/Variants.ts | 13 +++++++++---- libraries/rush-lib/src/cli/actions/AddAction.ts | 10 +++++++--- .../src/cli/actions/BaseAddAndRemoveAction.ts | 5 +++-- .../rush-lib/src/cli/actions/BaseInstallAction.ts | 8 ++++++-- libraries/rush-lib/src/cli/actions/CheckAction.ts | 8 ++++++-- libraries/rush-lib/src/cli/actions/InstallAction.ts | 8 ++++++-- libraries/rush-lib/src/cli/actions/RemoveAction.ts | 10 +++++++--- libraries/rush-lib/src/cli/actions/UpdateAction.ts | 8 ++++++-- .../src/cli/actions/UpgradeInteractiveAction.ts | 10 ++++++---- 9 files changed, 56 insertions(+), 24 deletions(-) diff --git a/libraries/rush-lib/src/api/Variants.ts b/libraries/rush-lib/src/api/Variants.ts index 57bb125c6a2..c2bfe8c56c5 100644 --- a/libraries/rush-lib/src/api/Variants.ts +++ b/libraries/rush-lib/src/api/Variants.ts @@ -17,14 +17,19 @@ export const VARIANT_PARAMETER: ICommandLineStringDefinition = { environmentVariable: EnvironmentVariableNames.RUSH_VARIANT }; -export function getVariant( +export async function getVariantAsync( variantsParameter: CommandLineStringParameter, - rushConfiguration: RushConfiguration -): string | undefined { - const variant: string | undefined = variantsParameter.value; + rushConfiguration: RushConfiguration, + defaultToCurrentlyInstalledVariant: boolean +): Promise { + let variant: string | undefined = variantsParameter.value; if (variant && !rushConfiguration.variants.has(variant)) { throw new Error(`The variant "${variant}" is not defined in ${RushConstants.rushJsonFilename}`); } + if (!variant && defaultToCurrentlyInstalledVariant) { + variant = await rushConfiguration.getCurrentlyInstalledVariantAsync(); + } + return variant; } diff --git a/libraries/rush-lib/src/cli/actions/AddAction.ts b/libraries/rush-lib/src/cli/actions/AddAction.ts index 2358f5f2e05..f0c2fe25593 100644 --- a/libraries/rush-lib/src/cli/actions/AddAction.ts +++ b/libraries/rush-lib/src/cli/actions/AddAction.ts @@ -17,7 +17,7 @@ import { type IPackageJsonUpdaterRushAddOptions, SemVerStyle } from '../../logic/PackageJsonUpdaterTypes'; -import { getVariant, VARIANT_PARAMETER } from '../../api/Variants'; +import { getVariantAsync, VARIANT_PARAMETER } from '../../api/Variants'; export class AddAction extends BaseAddAndRemoveAction { protected readonly _allFlag: CommandLineFlagParameter; @@ -94,7 +94,7 @@ export class AddAction extends BaseAddAndRemoveAction { this._variantParameter = this.defineStringParameter(VARIANT_PARAMETER); } - public getUpdateOptions(): IPackageJsonUpdaterRushAddOptions { + public async getUpdateOptionsAsync(): Promise { const projects: RushConfigurationProject[] = super.getProjects(); if (this._caretFlag.value && this._exactFlag.value) { @@ -157,7 +157,11 @@ export class AddAction extends BaseAddAndRemoveAction { packagesToAdd.push({ packageName, version, rangeStyle }); } - const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); + const variant: string | undefined = await getVariantAsync( + this._variantParameter, + this.rushConfiguration, + true + ); return { projects: projects, diff --git a/libraries/rush-lib/src/cli/actions/BaseAddAndRemoveAction.ts b/libraries/rush-lib/src/cli/actions/BaseAddAndRemoveAction.ts index 1398164a096..71b84b3cd39 100644 --- a/libraries/rush-lib/src/cli/actions/BaseAddAndRemoveAction.ts +++ b/libraries/rush-lib/src/cli/actions/BaseAddAndRemoveAction.ts @@ -54,7 +54,7 @@ export abstract class BaseAddAndRemoveAction extends BaseRushAction { }); } - protected abstract getUpdateOptions(): IPackageJsonUpdaterRushBaseUpdateOptions; + protected abstract getUpdateOptionsAsync(): Promise; protected getProjects(): RushConfigurationProject[] { if (this._allFlag.value) { @@ -83,6 +83,7 @@ export abstract class BaseAddAndRemoveAction extends BaseRushAction { this.rushGlobalFolder ); - await updater.doRushUpdateAsync(this.getUpdateOptions()); + const updateOptions: IPackageJsonUpdaterRushBaseUpdateOptions = await this.getUpdateOptionsAsync(); + await updater.doRushUpdateAsync(updateOptions); } } diff --git a/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts b/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts index 72666805f78..82865908a69 100644 --- a/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts +++ b/libraries/rush-lib/src/cli/actions/BaseInstallAction.ts @@ -23,7 +23,7 @@ import { RushConstants } from '../../logic/RushConstants'; import { SUBSPACE_LONG_ARG_NAME, type SelectionParameterSet } from '../parsing/SelectionParameterSet'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; import type { Subspace } from '../../api/Subspace'; -import { getVariant, VARIANT_PARAMETER } from '../../api/Variants'; +import { getVariantAsync, VARIANT_PARAMETER } from '../../api/Variants'; /** * Temporary data structure used by `BaseInstallAction.runAsync()` @@ -169,7 +169,11 @@ export abstract class BaseInstallAction extends BaseRushAction { } } - const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); + const variant: string | undefined = await getVariantAsync( + this._variantParameter, + this.rushConfiguration, + false + ); if (selectedSubspaces) { // Check each subspace for version inconsistencies for (const subspace of selectedSubspaces) { diff --git a/libraries/rush-lib/src/cli/actions/CheckAction.ts b/libraries/rush-lib/src/cli/actions/CheckAction.ts index 0484995baf3..2a4d769bfab 100644 --- a/libraries/rush-lib/src/cli/actions/CheckAction.ts +++ b/libraries/rush-lib/src/cli/actions/CheckAction.ts @@ -7,7 +7,7 @@ import { Colorize, type ITerminal } from '@rushstack/terminal'; import type { RushCommandLineParser } from '../RushCommandLineParser'; import { BaseRushAction } from './BaseRushAction'; import { VersionMismatchFinder } from '../../logic/versionMismatch/VersionMismatchFinder'; -import { getVariant, VARIANT_PARAMETER } from '../../api/Variants'; +import { getVariantAsync, VARIANT_PARAMETER } from '../../api/Variants'; export class CheckAction extends BaseRushAction { private readonly _terminal: ITerminal; @@ -60,7 +60,11 @@ export class CheckAction extends BaseRushAction { const currentlyInstalledVariant: string | undefined = await this.rushConfiguration.getCurrentlyInstalledVariantAsync(); - const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); + const variant: string | undefined = await getVariantAsync( + this._variantParameter, + this.rushConfiguration, + true + ); if (!variant && currentlyInstalledVariant) { this._terminal.writeWarningLine( Colorize.yellow( diff --git a/libraries/rush-lib/src/cli/actions/InstallAction.ts b/libraries/rush-lib/src/cli/actions/InstallAction.ts index a8c843d7449..945c12337ca 100644 --- a/libraries/rush-lib/src/cli/actions/InstallAction.ts +++ b/libraries/rush-lib/src/cli/actions/InstallAction.ts @@ -9,7 +9,7 @@ import type { RushCommandLineParser } from '../RushCommandLineParser'; import { SelectionParameterSet } from '../parsing/SelectionParameterSet'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; import type { Subspace } from '../../api/Subspace'; -import { getVariant } from '../../api/Variants'; +import { getVariantAsync } from '../../api/Variants'; export class InstallAction extends BaseInstallAction { private readonly _checkOnlyParameter: CommandLineFlagParameter; @@ -62,7 +62,11 @@ export class InstallAction extends BaseInstallAction { (await this._selectionParameters?.getSelectedProjectsAsync(this._terminal)) ?? new Set(this.rushConfiguration.projects); - const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); + const variant: string | undefined = await getVariantAsync( + this._variantParameter, + this.rushConfiguration, + false + ); return { debug: this.parser.isDebug, diff --git a/libraries/rush-lib/src/cli/actions/RemoveAction.ts b/libraries/rush-lib/src/cli/actions/RemoveAction.ts index cd49dfecff0..7eb3358f8b0 100644 --- a/libraries/rush-lib/src/cli/actions/RemoveAction.ts +++ b/libraries/rush-lib/src/cli/actions/RemoveAction.ts @@ -15,7 +15,7 @@ import type { IPackageForRushRemove, IPackageJsonUpdaterRushRemoveOptions } from '../../logic/PackageJsonUpdaterTypes'; -import { getVariant, VARIANT_PARAMETER } from '../../api/Variants'; +import { getVariantAsync, VARIANT_PARAMETER } from '../../api/Variants'; export class RemoveAction extends BaseAddAndRemoveAction { protected readonly _allFlag: CommandLineFlagParameter; @@ -54,7 +54,7 @@ export class RemoveAction extends BaseAddAndRemoveAction { this._variantParameter = this.defineStringParameter(VARIANT_PARAMETER); } - public getUpdateOptions(): IPackageJsonUpdaterRushRemoveOptions { + public async getUpdateOptionsAsync(): Promise { const projects: RushConfigurationProject[] = super.getProjects(); const packagesToRemove: IPackageForRushRemove[] = []; @@ -83,7 +83,11 @@ export class RemoveAction extends BaseAddAndRemoveAction { packagesToRemove.push({ packageName }); } - const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); + const variant: string | undefined = await getVariantAsync( + this._variantParameter, + this.rushConfiguration, + true + ); return { projects: projects, diff --git a/libraries/rush-lib/src/cli/actions/UpdateAction.ts b/libraries/rush-lib/src/cli/actions/UpdateAction.ts index 3bea78d0aa1..837ce149c17 100644 --- a/libraries/rush-lib/src/cli/actions/UpdateAction.ts +++ b/libraries/rush-lib/src/cli/actions/UpdateAction.ts @@ -9,7 +9,7 @@ import type { RushCommandLineParser } from '../RushCommandLineParser'; import { SelectionParameterSet } from '../parsing/SelectionParameterSet'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; import type { Subspace } from '../../api/Subspace'; -import { getVariant } from '../../api/Variants'; +import { getVariantAsync } from '../../api/Variants'; export class UpdateAction extends BaseInstallAction { private readonly _fullParameter: CommandLineFlagParameter; @@ -85,7 +85,11 @@ export class UpdateAction extends BaseInstallAction { (await this._selectionParameters?.getSelectedProjectsAsync(this._terminal)) ?? new Set(this.rushConfiguration.projects); - const variant: string | undefined = getVariant(this._variantParameter, this.rushConfiguration); + const variant: string | undefined = await getVariantAsync( + this._variantParameter, + this.rushConfiguration, + false + ); return { debug: this.parser.isDebug, diff --git a/libraries/rush-lib/src/cli/actions/UpgradeInteractiveAction.ts b/libraries/rush-lib/src/cli/actions/UpgradeInteractiveAction.ts index ffc1468f7c8..4c1666b28a6 100644 --- a/libraries/rush-lib/src/cli/actions/UpgradeInteractiveAction.ts +++ b/libraries/rush-lib/src/cli/actions/UpgradeInteractiveAction.ts @@ -7,7 +7,7 @@ import { BaseRushAction } from './BaseRushAction'; import type * as PackageJsonUpdaterType from '../../logic/PackageJsonUpdater'; import type * as InteractiveUpgraderType from '../../logic/InteractiveUpgrader'; -import { getVariant, VARIANT_PARAMETER } from '../../api/Variants'; +import { getVariantAsync, VARIANT_PARAMETER } from '../../api/Variants'; export class UpgradeInteractiveAction extends BaseRushAction { private _makeConsistentFlag: CommandLineFlagParameter; @@ -62,9 +62,11 @@ export class UpgradeInteractiveAction extends BaseRushAction { this.rushConfiguration ); - const variant: string | undefined = - getVariant(this._variantParameter, this.rushConfiguration) ?? - (await this.rushConfiguration.getCurrentlyInstalledVariantAsync()); + const variant: string | undefined = await getVariantAsync( + this._variantParameter, + this.rushConfiguration, + true + ); const shouldMakeConsistent: boolean = this.rushConfiguration.defaultSubspace.shouldEnsureConsistentVersions(variant) || this._makeConsistentFlag.value; From 12186a2c1167ff26cd10f159b0d7522d51b7d5da Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Sun, 22 Sep 2024 15:43:17 -0400 Subject: [PATCH 13/24] Fix an issue with ensuring consistent versions with a default subspace. --- .../versionMismatch/VersionMismatchFinder.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts b/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts index 70e26c61826..567f1678f95 100644 --- a/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts +++ b/libraries/rush-lib/src/logic/versionMismatch/VersionMismatchFinder.ts @@ -71,10 +71,18 @@ export class VersionMismatchFinder { terminal: ITerminal, options?: IVersionMismatchFinderRushCheckOptions ): void { + const { + variant, + subspace = rushConfiguration.defaultSubspace, + printAsJson, + truncateLongPackageNameLists + } = options ?? {}; + VersionMismatchFinder._checkForInconsistentVersions(rushConfiguration, { - subspace: rushConfiguration.defaultSubspace, - variant: undefined, - ...options, + variant, + subspace, + printAsJson, + truncateLongPackageNameLists, terminal, isRushCheckCommand: true }); @@ -85,10 +93,11 @@ export class VersionMismatchFinder { terminal: ITerminal, options?: IVersionMismatchFinderEnsureConsistentVersionsOptions ): void { + const { variant, subspace = rushConfiguration.defaultSubspace } = options ?? {}; + VersionMismatchFinder._checkForInconsistentVersions(rushConfiguration, { - subspace: rushConfiguration.defaultSubspace, - variant: undefined, - ...options, + subspace, + variant, terminal, isRushCheckCommand: false, truncateLongPackageNameLists: true From dfea81105fac585460982b5f7ca96babe274bf3a Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 26 Sep 2024 16:04:18 -0400 Subject: [PATCH 14/24] fixup! Bring back the Variants feature. --- libraries/rush-lib/src/api/RushConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/rush-lib/src/api/RushConfiguration.ts b/libraries/rush-lib/src/api/RushConfiguration.ts index 396b648834d..cbcf03a153d 100644 --- a/libraries/rush-lib/src/api/RushConfiguration.ts +++ b/libraries/rush-lib/src/api/RushConfiguration.ts @@ -362,7 +362,7 @@ export class RushConfiguration { /** * The filename of the variant dependency data file. By default this is - * called 'current-variant.json' resides in the Rush common folder. + * called 'current-variant.json' and resides in the Rush common folder. * Its data structure is defined by ICurrentVariantJson. * * Example: `C:\MyRepo\common\temp\current-variant.json` From 6218f2a55ee1f8a8e0ba39f7ce391d4e6cfa1d02 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 26 Sep 2024 16:06:04 -0400 Subject: [PATCH 15/24] fixup! Bring back the Variants feature. --- libraries/rush-lib/src/api/RushConfiguration.ts | 2 +- libraries/rush-lib/src/logic/RushConstants.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/rush-lib/src/api/RushConfiguration.ts b/libraries/rush-lib/src/api/RushConfiguration.ts index cbcf03a153d..9ac0227f8d7 100644 --- a/libraries/rush-lib/src/api/RushConfiguration.ts +++ b/libraries/rush-lib/src/api/RushConfiguration.ts @@ -646,7 +646,7 @@ export class RushConfiguration { this.changesFolder = path.join(this.commonFolder, RushConstants.changeFilesFolderName); - this.currentVariantJsonFilePath = path.join(this.commonTempFolder, 'current-variant.json'); + this.currentVariantJsonFilePath = path.join(this.commonTempFolder, RushConstants.currentVariantsFilename); this.suppressNodeLtsWarning = !!rushConfigurationJson.suppressNodeLtsWarning; diff --git a/libraries/rush-lib/src/logic/RushConstants.ts b/libraries/rush-lib/src/logic/RushConstants.ts index fc1130f5593..ec09faced21 100644 --- a/libraries/rush-lib/src/logic/RushConstants.ts +++ b/libraries/rush-lib/src/logic/RushConstants.ts @@ -342,4 +342,9 @@ export class RushConstants { * The filename for the machine-generated file that tracks state for Rush alerts. */ public static readonly rushAlertsStateFilename: 'rush-alerts-state.json' = 'rush-alerts-state.json'; + + /** + * The filename for the file that tracks which variant is currently installed. + */ + public static readonly currentVariantsFilename: 'current-variants.json' = 'current-variants.json'; } From b18e4cb81b11f153824a34f46d77be5a2273ae1b Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 26 Sep 2024 16:07:11 -0400 Subject: [PATCH 16/24] fixup! Fix an issue where install is skipped if the variant is changed. --- libraries/rush-lib/src/utilities/Utilities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/rush-lib/src/utilities/Utilities.ts b/libraries/rush-lib/src/utilities/Utilities.ts index b6780ff3840..d9d2b8f092c 100644 --- a/libraries/rush-lib/src/utilities/Utilities.ts +++ b/libraries/rush-lib/src/utilities/Utilities.ts @@ -302,7 +302,7 @@ export class Utilities { if (!anyAreOutOfDate) { let inputStats: FileSystemStats | undefined; try { - inputStats = FileSystem.getStatistics(filePath); + inputStats = await FileSystem.getStatisticsAsync(filePath); } catch (e) { if (FileSystem.isNotExistError(e)) { anyAreOutOfDate = true; From 9a20156998fe20269227ebb0818a913fef184fc5 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 26 Sep 2024 16:13:32 -0400 Subject: [PATCH 17/24] fixup! Fix an issue where install is skipped if the variant is changed. --- .../rush-lib/src/cli/scriptActions/PhasedScriptAction.ts | 4 +++- .../src/logic/installManager/doBasicInstallAsync.ts | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts index 03485db93c8..fe8c8a220f0 100644 --- a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts @@ -306,7 +306,9 @@ export class PhasedScriptAction extends BaseScriptAction { beforeInstallAsync: (subspace: Subspace) => this.rushSession.hooks.beforeInstall.promise(this, subspace, currentlyInstalledVariant), afterInstallAsync: (subspace: Subspace) => - this.rushSession.hooks.afterInstall.promise(this, subspace, currentlyInstalledVariant) + this.rushSession.hooks.afterInstall.promise(this, subspace, currentlyInstalledVariant), + // Eventually we may want to allow a subspace to be selected here + subspace: this.rushConfiguration.defaultSubspace }); } diff --git a/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts b/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts index 2a5a2cc0629..8c830471f5d 100644 --- a/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts +++ b/libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts @@ -11,6 +11,7 @@ import { InstallManagerFactory } from '../InstallManagerFactory'; import { SetupChecks } from '../SetupChecks'; import { PurgeManager } from '../PurgeManager'; import { VersionMismatchFinder } from '../versionMismatch/VersionMismatchFinder'; +import type { Subspace } from '../../api/Subspace'; export interface IRunInstallOptions { afterInstallAsync?: IInstallManagerOptions['afterInstallAsync']; @@ -20,6 +21,7 @@ export interface IRunInstallOptions { isDebug: boolean; terminal: ITerminal; variant: string | undefined; + subspace: Subspace; } export async function doBasicInstallAsync(options: IRunInstallOptions): Promise { @@ -30,7 +32,8 @@ export async function doBasicInstallAsync(options: IRunInstallOptions): Promise< variant, terminal, beforeInstallAsync, - afterInstallAsync + afterInstallAsync, + subspace } = options; VersionMismatchFinder.ensureConsistentVersions(rushConfiguration, terminal, { @@ -59,7 +62,7 @@ export async function doBasicInstallAsync(options: IRunInstallOptions): Promise< selectedProjects: new Set(rushConfiguration.projects), maxInstallAttempts: 1, networkConcurrency: undefined, - subspace: rushConfiguration.defaultSubspace, + subspace, terminal, variant, afterInstallAsync, From 12cc61d83bd90ab36ebf1ca4c7d62a4b21601d5d Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 26 Sep 2024 16:17:07 -0400 Subject: [PATCH 18/24] Include pnpmfile in the files that are variant-dependant. --- common/reviews/api/rush-lib.api.md | 5 +++-- libraries/rush-lib/src/api/RushConfiguration.ts | 4 ++-- libraries/rush-lib/src/api/Subspace.ts | 4 ++-- libraries/rush-lib/src/api/test/RushConfiguration.test.ts | 4 ++-- libraries/rush-lib/src/logic/base/BaseInstallManager.ts | 2 +- libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 2a6c0dab078..aba9d3e619b 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1191,7 +1191,7 @@ export class RushConfiguration { getCurrentlyInstalledVariantAsync(): Promise; getImplicitlyPreferredVersions(subspace?: Subspace, variant?: string): Map; // @deprecated (undocumented) - getPnpmfilePath(subspace?: Subspace): string; + getPnpmfilePath(subspace?: Subspace, variant?: string): string; getProjectByName(projectName: string): RushConfigurationProject | undefined; // @beta (undocumented) getProjectLookupForRoot(rootPath: string): LookupByPath; @@ -1334,6 +1334,7 @@ export class RushConstants { static readonly commandLineFilename: 'command-line.json'; static readonly commonFolderName: 'common'; static readonly commonVersionsFilename: 'common-versions.json'; + static readonly currentVariantsFilename: 'current-variants.json'; static readonly customTipsFilename: 'custom-tips.json'; static readonly defaultMaxInstallAttempts: 1; static readonly defaultSubspaceName: 'default'; @@ -1480,7 +1481,7 @@ export class Subspace { // @beta getPnpmConfigFilePath(): string; // @beta - getPnpmfilePath(): string; + getPnpmfilePath(variant: string | undefined): string; // @beta getPnpmOptions(): PnpmOptionsConfiguration | undefined; // @beta diff --git a/libraries/rush-lib/src/api/RushConfiguration.ts b/libraries/rush-lib/src/api/RushConfiguration.ts index 9ac0227f8d7..deee29cb4ae 100644 --- a/libraries/rush-lib/src/api/RushConfiguration.ts +++ b/libraries/rush-lib/src/api/RushConfiguration.ts @@ -1492,8 +1492,8 @@ export class RushConfiguration { /** * @deprecated Use {@link Subspace.getPnpmfilePath} instead */ - public getPnpmfilePath(subspace?: Subspace): string { - return (subspace ?? this.defaultSubspace).getPnpmfilePath(); + public getPnpmfilePath(subspace?: Subspace, variant?: string): string { + return (subspace ?? this.defaultSubspace).getPnpmfilePath(variant); } /** diff --git a/libraries/rush-lib/src/api/Subspace.ts b/libraries/rush-lib/src/api/Subspace.ts index b56b2e968c5..42bf74f373e 100644 --- a/libraries/rush-lib/src/api/Subspace.ts +++ b/libraries/rush-lib/src/api/Subspace.ts @@ -382,8 +382,8 @@ export class Subspace { * The file path is returned even if PNPM is not configured as the package manager. * @beta */ - public getPnpmfilePath(): string { - const subspaceConfigFolderPath: string = this.getSubspaceConfigFolderPath(); + public getPnpmfilePath(variant: string | undefined): string { + const subspaceConfigFolderPath: string = this.getVariantDependentSubspaceConfigFolderPath(variant); const pnpmFilename: string = (this._rushConfiguration.packageManagerWrapper as PnpmPackageManager) .pnpmfileFilename; diff --git a/libraries/rush-lib/src/api/test/RushConfiguration.test.ts b/libraries/rush-lib/src/api/test/RushConfiguration.test.ts index 6331bf68425..f5edac12662 100644 --- a/libraries/rush-lib/src/api/test/RushConfiguration.test.ts +++ b/libraries/rush-lib/src/api/test/RushConfiguration.test.ts @@ -118,7 +118,7 @@ describe(RushConfiguration.name, () => { expect(rushConfiguration.shrinkwrapFilename).toEqual('pnpm-lock.yaml'); assertPathProperty( 'getPnpmfilePath', - rushConfiguration.defaultSubspace.getPnpmfilePath(), + rushConfiguration.defaultSubspace.getPnpmfilePath(undefined), './repo/common/config/rush/.pnpmfile.cjs' ); assertPathProperty('commonFolder', rushConfiguration.commonFolder, './repo/common'); @@ -185,7 +185,7 @@ describe(RushConfiguration.name, () => { expect(rushConfiguration.shrinkwrapFilename).toEqual('pnpm-lock.yaml'); assertPathProperty( 'getPnpmfilePath', - rushConfiguration.defaultSubspace.getPnpmfilePath(), + rushConfiguration.defaultSubspace.getPnpmfilePath(undefined), './repo/common/config/rush/pnpmfile.js' ); expect(rushConfiguration.repositoryUrls).toEqual(['someFakeUrl', 'otherFakeUrl']); diff --git a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts index cf2d902ec16..3a0b0689eb5 100644 --- a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts @@ -404,7 +404,7 @@ export abstract class BaseInstallManager { if (this.rushConfiguration.packageManager === 'pnpm') { // If the repo is using pnpmfile.js, consider that also - const pnpmFileFilePath: string = subspace.getPnpmfilePath(); + const pnpmFileFilePath: string = subspace.getPnpmfilePath(variant); const pnpmFileExists: boolean = await FileSystem.existsAsync(pnpmFileFilePath); if (pnpmFileExists) { diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts b/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts index de09bb658f7..9b89c94ced0 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts @@ -124,7 +124,7 @@ export class PnpmfileConfiguration { }; // Use the provided path if available. Otherwise, use the default path. - const userPnpmfilePath: string | undefined = subspace.getPnpmfilePath(); + const userPnpmfilePath: string | undefined = subspace.getPnpmfilePath(variant); if (userPnpmfilePath && FileSystem.exists(userPnpmfilePath)) { settings.userPnpmfilePath = userPnpmfilePath; } From e104dd83f28279e16c03106626b00b26a1bbe8b2 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 26 Sep 2024 16:19:06 -0400 Subject: [PATCH 19/24] fixup! Include pnpmfile in the files that are variant-dependant. --- common/reviews/api/rush-lib.api.md | 2 +- libraries/rush-lib/src/api/Subspace.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index aba9d3e619b..f77e2363d6e 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1481,7 +1481,7 @@ export class Subspace { // @beta getPnpmConfigFilePath(): string; // @beta - getPnpmfilePath(variant: string | undefined): string; + getPnpmfilePath(variant?: string): string; // @beta getPnpmOptions(): PnpmOptionsConfiguration | undefined; // @beta diff --git a/libraries/rush-lib/src/api/Subspace.ts b/libraries/rush-lib/src/api/Subspace.ts index 42bf74f373e..7bf81b4327a 100644 --- a/libraries/rush-lib/src/api/Subspace.ts +++ b/libraries/rush-lib/src/api/Subspace.ts @@ -382,7 +382,7 @@ export class Subspace { * The file path is returned even if PNPM is not configured as the package manager. * @beta */ - public getPnpmfilePath(variant: string | undefined): string { + public getPnpmfilePath(variant?: string): string { const subspaceConfigFolderPath: string = this.getVariantDependentSubspaceConfigFolderPath(variant); const pnpmFilename: string = (this._rushConfiguration.packageManagerWrapper as PnpmPackageManager) From 0bedc7e69cd5b2142db39f7a74569fd8d26a7111 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 26 Sep 2024 16:20:25 -0400 Subject: [PATCH 20/24] Include pnpm-config.json in the set of files that are variant-dependent. --- common/reviews/api/rush-lib.api.md | 2 +- libraries/rush-lib/src/api/Subspace.ts | 5 +++-- libraries/rush-lib/src/logic/base/BaseInstallManager.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index f77e2363d6e..581d7892235 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1479,7 +1479,7 @@ export class Subspace { // @beta getPackageJsonInjectedDependenciesHash(variant?: string): string | undefined; // @beta - getPnpmConfigFilePath(): string; + getPnpmConfigFilePath(variant?: string): string; // @beta getPnpmfilePath(variant?: string): string; // @beta diff --git a/libraries/rush-lib/src/api/Subspace.ts b/libraries/rush-lib/src/api/Subspace.ts index 7bf81b4327a..0d08f19489f 100644 --- a/libraries/rush-lib/src/api/Subspace.ts +++ b/libraries/rush-lib/src/api/Subspace.ts @@ -207,6 +207,7 @@ export class Subspace { * - Lockfiles: (i.e. - `pnpm-lock.yaml`, `npm-shrinkwrap.json`, `yarn.lock`, etc) * - 'common-versions.json' * - 'pnpmfile.js'/'.pnpmfile.cjs' + * - 'pnpm-config.js' */ public getVariantDependentSubspaceConfigFolderPath(variant: string | undefined): string { const subspaceConfigFolderPath: string = this.getSubspaceConfigFolderPath(); @@ -302,8 +303,8 @@ export class Subspace { * Example: `C:\MyRepo\common\subspaces\my-subspace\pnpm-config.json` * @beta */ - public getPnpmConfigFilePath(): string { - return this.getSubspaceConfigFolderPath() + '/' + RushConstants.pnpmConfigFilename; + public getPnpmConfigFilePath(variant?: string): string { + return this.getVariantDependentSubspaceConfigFolderPath(variant) + '/' + RushConstants.pnpmConfigFilename; } /** diff --git a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts index 3a0b0689eb5..e42e37157f6 100644 --- a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts @@ -400,7 +400,7 @@ export abstract class BaseInstallManager { potentiallyChangedFiles.push(subspace.getCommonVersionsFilePath(variant)); // Add pnpm-config.json file to the potentially changed files list. - potentiallyChangedFiles.push(subspace.getPnpmConfigFilePath()); + potentiallyChangedFiles.push(subspace.getPnpmConfigFilePath(variant)); if (this.rushConfiguration.packageManager === 'pnpm') { // If the repo is using pnpmfile.js, consider that also From ccb407c6e667fafeb412c4ef7a7dd0ffedd129a3 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 26 Sep 2024 16:22:41 -0400 Subject: [PATCH 21/24] Allow variant to be passed into getChangedProjectsAsync --- common/reviews/api/rush-lib.api.md | 2 ++ libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 581d7892235..cb3f63148d3 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -512,6 +512,8 @@ export interface IGetChangedProjectsOptions { targetBranchName: string; // (undocumented) terminal: ITerminal; + // (undocumented) + variant?: string; } // @beta diff --git a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts index b70219a18b5..9b068940425 100644 --- a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts +++ b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts @@ -31,6 +31,7 @@ export interface IGetChangedProjectsOptions { targetBranchName: string; terminal: ITerminal; shouldFetch?: boolean; + variant?: string; /** * If set to `true`, consider a project's external dependency installation layout as defined in the @@ -217,7 +218,8 @@ export class ProjectChangeAnalyzer { ): Promise> { const { _rushConfiguration: rushConfiguration } = this; - const { targetBranchName, terminal, includeExternalDependencies, enableFiltering, shouldFetch } = options; + const { targetBranchName, terminal, includeExternalDependencies, enableFiltering, shouldFetch, variant } = + options; const gitPath: string = this._git.getGitPathOrThrow(); const repoRoot: string = getRepoRoot(rushConfiguration.rushJsonFolder); @@ -236,10 +238,10 @@ export class ProjectChangeAnalyzer { // Even though changing the installed version of a nested dependency merits a change file, // ignore lockfile changes for `rush change` for the moment - const currentVariant: string | undefined = - await this._rushConfiguration.getCurrentlyInstalledVariantAsync(); + const variantToUse: string | undefined = + variant ?? (await this._rushConfiguration.getCurrentlyInstalledVariantAsync()); const fullShrinkwrapPath: string = - rushConfiguration.defaultSubspace.getCommittedShrinkwrapFilePath(currentVariant); + rushConfiguration.defaultSubspace.getCommittedShrinkwrapFilePath(variantToUse); const relativeShrinkwrapFilePath: string = Path.convertToSlashes( path.relative(repoRoot, fullShrinkwrapPath) From 4307055d510f22138015f5fc432468a838d58ff4 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 26 Sep 2024 16:33:33 -0400 Subject: [PATCH 22/24] Include the --variant parameter with the --install parameter in PhasedScriptAction. --- libraries/rush-lib/src/api/Variants.ts | 4 +-- .../cli/scriptActions/PhasedScriptAction.ts | 26 ++++++++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/libraries/rush-lib/src/api/Variants.ts b/libraries/rush-lib/src/api/Variants.ts index c2bfe8c56c5..06825d73281 100644 --- a/libraries/rush-lib/src/api/Variants.ts +++ b/libraries/rush-lib/src/api/Variants.ts @@ -18,11 +18,11 @@ export const VARIANT_PARAMETER: ICommandLineStringDefinition = { }; export async function getVariantAsync( - variantsParameter: CommandLineStringParameter, + variantsParameter: CommandLineStringParameter | undefined, rushConfiguration: RushConfiguration, defaultToCurrentlyInstalledVariant: boolean ): Promise { - let variant: string | undefined = variantsParameter.value; + let variant: string | undefined = variantsParameter?.value; if (variant && !rushConfiguration.variants.has(variant)) { throw new Error(`The variant "${variant}" is not defined in ${RushConstants.rushJsonFilename}`); } diff --git a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts index fe8c8a220f0..90a01d3e463 100644 --- a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts @@ -51,6 +51,7 @@ import { ShardedPhasedOperationPlugin } from '../../logic/operations/ShardedPhas import type { ProjectWatcher } from '../../logic/ProjectWatcher'; import { FlagFile } from '../../api/FlagFile'; import { WeightedOperationPlugin } from '../../logic/operations/WeightedOperationPlugin'; +import { getVariantAsync, VARIANT_PARAMETER } from '../../api/Variants'; /** * Constructor parameters for PhasedScriptAction. @@ -144,6 +145,7 @@ export class PhasedScriptAction extends BaseScriptAction { private readonly _timelineParameter: CommandLineFlagParameter | undefined; private readonly _cobuildPlanParameter: CommandLineFlagParameter | undefined; private readonly _installParameter: CommandLineFlagParameter | undefined; + private readonly _variantParameter: CommandLineStringParameter | undefined; private readonly _noIPCParameter: CommandLineFlagParameter | undefined; public constructor(options: IPhasedScriptActionOptions) { @@ -258,6 +260,8 @@ export class PhasedScriptAction extends BaseScriptAction { 'Normally a phased command expects "rush install" to have been manually run first. If this flag is specified, ' + 'Rush will automatically perform an install before processing the current command.' }); + + this._variantParameter = this.defineStringParameter(VARIANT_PARAMETER); } if ( @@ -289,24 +293,26 @@ export class PhasedScriptAction extends BaseScriptAction { public async runAsync(): Promise { if (this._alwaysInstall || this._installParameter?.value) { - const [{ doBasicInstallAsync }, currentlyInstalledVariant] = await Promise.all([ - import( - /* webpackChunkName: 'doBasicInstallAsync' */ - '../../logic/installManager/doBasicInstallAsync' - ), - this.rushConfiguration.getCurrentlyInstalledVariantAsync() - ]); + const { doBasicInstallAsync } = await import( + /* webpackChunkName: 'doBasicInstallAsync' */ + '../../logic/installManager/doBasicInstallAsync' + ); + const variant: string | undefined = await getVariantAsync( + this._variantParameter, + this.rushConfiguration, + true + ); await doBasicInstallAsync({ terminal: this._terminal, rushConfiguration: this.rushConfiguration, rushGlobalFolder: this.rushGlobalFolder, isDebug: this.parser.isDebug, - variant: currentlyInstalledVariant, + variant, beforeInstallAsync: (subspace: Subspace) => - this.rushSession.hooks.beforeInstall.promise(this, subspace, currentlyInstalledVariant), + this.rushSession.hooks.beforeInstall.promise(this, subspace, variant), afterInstallAsync: (subspace: Subspace) => - this.rushSession.hooks.afterInstall.promise(this, subspace, currentlyInstalledVariant), + this.rushSession.hooks.afterInstall.promise(this, subspace, variant), // Eventually we may want to allow a subspace to be selected here subspace: this.rushConfiguration.defaultSubspace }); From bd93fc9df351ed956130cf1c69f87a60ca97cc22 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 26 Sep 2024 16:35:19 -0400 Subject: [PATCH 23/24] Use named parameters in the beforeInstall and afterInstall hooks. --- common/reviews/api/rush-lib.api.md | 12 ++++++++++-- .../src/pluginFramework/RushLifeCycle.ts | 16 ++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index cb3f63148d3..31cb1da9f8a 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1403,8 +1403,16 @@ export class _RushInternals { // @beta export class RushLifecycleHooks { - readonly afterInstall: AsyncSeriesHook<[IRushCommand, Subspace, string | undefined]>; - readonly beforeInstall: AsyncSeriesHook<[IGlobalCommand, Subspace, string | undefined]>; + readonly afterInstall: AsyncSeriesHook<[ + command: IRushCommand, + subspace: Subspace, + variant: string | undefined + ]>; + readonly beforeInstall: AsyncSeriesHook<[ + command: IGlobalCommand, + subspace: Subspace, + variant: string | undefined + ]>; readonly flushTelemetry: AsyncParallelHook<[ReadonlyArray]>; readonly initialize: AsyncSeriesHook; readonly runAnyGlobalCustomCommand: AsyncSeriesHook; diff --git a/libraries/rush-lib/src/pluginFramework/RushLifeCycle.ts b/libraries/rush-lib/src/pluginFramework/RushLifeCycle.ts index 73555e1ae94..dd0e274526b 100644 --- a/libraries/rush-lib/src/pluginFramework/RushLifeCycle.ts +++ b/libraries/rush-lib/src/pluginFramework/RushLifeCycle.ts @@ -86,20 +86,16 @@ export class RushLifecycleHooks { /** * The hook to run between preparing the common/temp folder and invoking the package manager during "rush install" or "rush update". */ - public readonly beforeInstall: AsyncSeriesHook<[IGlobalCommand, Subspace, string | undefined]> = - new AsyncSeriesHook<[IGlobalCommand, Subspace, string | undefined]>( - ['command', 'subspace', 'variant'], - 'beforeInstall' - ); + public readonly beforeInstall: AsyncSeriesHook< + [command: IGlobalCommand, subspace: Subspace, variant: string | undefined] + > = new AsyncSeriesHook(['command', 'subspace', 'variant'], 'beforeInstall'); /** * The hook to run after a successful install. */ - public readonly afterInstall: AsyncSeriesHook<[IRushCommand, Subspace, string | undefined]> = - new AsyncSeriesHook<[IRushCommand, Subspace, string | undefined]>( - ['command', 'subspace', 'variant'], - 'afterInstall' - ); + public readonly afterInstall: AsyncSeriesHook< + [command: IRushCommand, subspace: Subspace, variant: string | undefined] + > = new AsyncSeriesHook(['command', 'subspace', 'variant'], 'afterInstall'); /** * A hook to allow plugins to hook custom logic to process telemetry data. From 10a708b70e63db9bf6827412b05adb3306e3666a Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 26 Sep 2024 16:36:15 -0400 Subject: [PATCH 24/24] fixup! Fix an issue where install is skipped if the variant is changed. --- libraries/rush-lib/src/utilities/Utilities.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/rush-lib/src/utilities/Utilities.ts b/libraries/rush-lib/src/utilities/Utilities.ts index d9d2b8f092c..f4f8901b00c 100644 --- a/libraries/rush-lib/src/utilities/Utilities.ts +++ b/libraries/rush-lib/src/utilities/Utilities.ts @@ -305,6 +305,7 @@ export class Utilities { inputStats = await FileSystem.getStatisticsAsync(filePath); } catch (e) { if (FileSystem.isNotExistError(e)) { + // eslint-disable-next-line require-atomic-updates anyAreOutOfDate = true; } else { throw e; @@ -312,6 +313,7 @@ export class Utilities { } if (inputStats && dateToCompare < inputStats.mtime) { + // eslint-disable-next-line require-atomic-updates anyAreOutOfDate = true; } }