From eaee160f943fb0591f36908dfb9deecb40b9afa2 Mon Sep 17 00:00:00 2001 From: mrzepinski Date: Tue, 7 Oct 2025 12:43:14 +0200 Subject: [PATCH 1/6] [rush-lib] Add support for PNPM's minimumReleaseAge setting This change adds support for PNPM's minimumReleaseAge setting in Rush's pnpm-config.json file to help mitigate supply chain attacks by requiring a minimum age (in minutes) for package versions before installation. Fixes #5372 --- ...-release-age-support_2025-10-07-10-30.json | 10 +++++++++ ...-release-age-support_2025-10-07-10-49.json | 10 +++++++++ common/reviews/api/rush-lib.api.md | 1 + .../common/config/rush/pnpm-config.json | 17 ++++++++++++++ .../src/logic/base/BaseInstallManager.ts | 22 +++++++++++++++++++ .../logic/pnpm/PnpmOptionsConfiguration.ts | 19 ++++++++++++++++ .../test/PnpmOptionsConfiguration.test.ts | 9 ++++++++ .../pnpm-config-minimumReleaseAge.json | 3 +++ .../src/schemas/pnpm-config.schema.json | 5 +++++ 9 files changed, 96 insertions(+) create mode 100644 common/changes/@microsoft/rush-lib/add-minimum-release-age-support_2025-10-07-10-30.json create mode 100644 common/changes/@microsoft/rush/feature-add-minimum-release-age-support_2025-10-07-10-49.json create mode 100644 libraries/rush-lib/src/logic/pnpm/test/jsonFiles/pnpm-config-minimumReleaseAge.json diff --git a/common/changes/@microsoft/rush-lib/add-minimum-release-age-support_2025-10-07-10-30.json b/common/changes/@microsoft/rush-lib/add-minimum-release-age-support_2025-10-07-10-30.json new file mode 100644 index 00000000000..ecab7f99a0c --- /dev/null +++ b/common/changes/@microsoft/rush-lib/add-minimum-release-age-support_2025-10-07-10-30.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush-lib", + "comment": "Add support for PNPM's minimumReleaseAge setting to help mitigate supply chain attacks", + "type": "minor" + } + ], + "packageName": "@microsoft/rush-lib" +} diff --git a/common/changes/@microsoft/rush/feature-add-minimum-release-age-support_2025-10-07-10-49.json b/common/changes/@microsoft/rush/feature-add-minimum-release-age-support_2025-10-07-10-49.json new file mode 100644 index 00000000000..addb3ae3ac1 --- /dev/null +++ b/common/changes/@microsoft/rush/feature-add-minimum-release-age-support_2025-10-07-10-49.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Add support for PNPM's minimumReleaseAge setting to help mitigate supply chain attacks", + "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 cdad7dd3b7a..641c65734a6 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1188,6 +1188,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration static loadFromJsonFileOrThrow(jsonFilePath: string, commonTempFolder: string): PnpmOptionsConfiguration; // @internal (undocumented) static loadFromJsonObject(json: _IPnpmOptionsJson, commonTempFolder: string): PnpmOptionsConfiguration; + readonly minimumReleaseAge: number | undefined; readonly pnpmLockfilePolicies: IPnpmLockfilePolicies | undefined; readonly pnpmStore: PnpmStoreLocation; readonly pnpmStorePath: string; diff --git a/libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json b/libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json index 1f9e1ab6065..87bc893cbbd 100644 --- a/libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json +++ b/libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json @@ -67,6 +67,23 @@ */ /*[LINE "DEMO"]*/ "autoInstallPeers": false, + /** + * The minimum number of minutes that must pass after a version is published before pnpm will install it. + * This setting helps reduce the risk of installing compromised packages, as malicious releases are typically + * discovered and removed within a short time frame. + * + * For example, the following setting ensures that only packages released at least one day ago can be installed: + * + * "minimumReleaseAge": 1440 + * + * (SUPPORTED ONLY IN PNPM 10.16.0 AND NEWER) + * + * PNPM documentation: https://pnpm.io/settings#minimumreleaseage + * + * The default value is 0 (disabled). + */ + /*[LINE "HYPOTHETICAL"]*/ "minimumReleaseAge": 1440, + /** * If true, then Rush will add the `--strict-peer-dependencies` command-line parameter when * invoking PNPM. This causes `rush update` to fail if there are unsatisfied peer dependencies, diff --git a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts index a38af503f97..aa9be35a8ca 100644 --- a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts @@ -948,6 +948,28 @@ ${gitLfsHookHandling} args.push(`--config.auto-install-peers=${autoInstallPeers}`); } + /* + If user set minimum-release-age in pnpm-config.json only, use the value in pnpm-config.json + If user set minimum-release-age in pnpm-config.json and .npmrc, use the value in pnpm-config.json + If user set minimum-release-age in .npmrc only, do nothing, let pnpm handle it + */ + const isMinimumReleaseAgeInNpmrc: boolean = isVariableSetInNpmrcFile( + subspace.getSubspaceConfigFolderPath(), + 'minimum-release-age', + this.rushConfiguration.isPnpm + ); + + const minimumReleaseAge: number | undefined = this.rushConfiguration.pnpmOptions.minimumReleaseAge; + if (minimumReleaseAge !== undefined) { + if (isMinimumReleaseAgeInNpmrc) { + this._terminal.writeWarningLine( + `Warning: PNPM's minimum-release-age is specified in both .npmrc and pnpm-config.json. ` + + `The value in pnpm-config.json will take precedence.` + ); + } + args.push(`--config.minimumReleaseAge=${minimumReleaseAge}`); + } + /* If user set resolution-mode in pnpm-config.json only, use the value in pnpm-config.json If user set resolution-mode in pnpm-config.json and .npmrc, use the value in pnpm-config.json diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts b/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts index 571a7855b3c..8d4131bfef0 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts @@ -138,6 +138,10 @@ export interface IPnpmOptionsJson extends IPackageManagerOptionsJsonBase { * {@inheritDoc PnpmOptionsConfiguration.autoInstallPeers} */ autoInstallPeers?: boolean; + /** + * {@inheritDoc PnpmOptionsConfiguration.minimumReleaseAge} + */ + minimumReleaseAge?: number; /** * {@inheritDoc PnpmOptionsConfiguration.alwaysInjectDependenciesFromOtherSubspaces} */ @@ -258,6 +262,20 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration */ public readonly autoInstallPeers: boolean | undefined; + /** + * The minimum number of minutes that must pass after a version is published before pnpm will install it. + * This setting helps reduce the risk of installing compromised packages, as malicious releases are typically + * discovered and removed within a short time frame. + * + * @remarks + * (SUPPORTED ONLY IN PNPM 10.16.0 AND NEWER) + * + * PNPM documentation: https://pnpm.io/settings#minimumreleaseage + * + * The default value is 0 (disabled). + */ + public readonly minimumReleaseAge: number | undefined; + /** * If true, then `rush update` add injected install options for all cross-subspace * workspace dependencies, to avoid subspace doppelganger issue. @@ -425,6 +443,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration this._globalPatchedDependencies = json.globalPatchedDependencies; this.resolutionMode = json.resolutionMode; this.autoInstallPeers = json.autoInstallPeers; + this.minimumReleaseAge = json.minimumReleaseAge; this.alwaysInjectDependenciesFromOtherSubspaces = json.alwaysInjectDependenciesFromOtherSubspaces; this.alwaysFullInstall = json.alwaysFullInstall; this.pnpmLockfilePolicies = json.pnpmLockfilePolicies; diff --git a/libraries/rush-lib/src/logic/pnpm/test/PnpmOptionsConfiguration.test.ts b/libraries/rush-lib/src/logic/pnpm/test/PnpmOptionsConfiguration.test.ts index 4d4ab4a7bb2..7f22a4b0f2d 100644 --- a/libraries/rush-lib/src/logic/pnpm/test/PnpmOptionsConfiguration.test.ts +++ b/libraries/rush-lib/src/logic/pnpm/test/PnpmOptionsConfiguration.test.ts @@ -73,4 +73,13 @@ describe(PnpmOptionsConfiguration.name, () => { 'level' ]); }); + + it('loads minimumReleaseAge', () => { + const pnpmConfiguration: PnpmOptionsConfiguration = PnpmOptionsConfiguration.loadFromJsonFileOrThrow( + `${__dirname}/jsonFiles/pnpm-config-minimumReleaseAge.json`, + fakeCommonTempFolder + ); + + expect(pnpmConfiguration.minimumReleaseAge).toEqual(1440); + }); }); diff --git a/libraries/rush-lib/src/logic/pnpm/test/jsonFiles/pnpm-config-minimumReleaseAge.json b/libraries/rush-lib/src/logic/pnpm/test/jsonFiles/pnpm-config-minimumReleaseAge.json new file mode 100644 index 00000000000..6075cf453e3 --- /dev/null +++ b/libraries/rush-lib/src/logic/pnpm/test/jsonFiles/pnpm-config-minimumReleaseAge.json @@ -0,0 +1,3 @@ +{ + "minimumReleaseAge": 1440 +} diff --git a/libraries/rush-lib/src/schemas/pnpm-config.schema.json b/libraries/rush-lib/src/schemas/pnpm-config.schema.json index a5256c98228..ec7b3f00568 100644 --- a/libraries/rush-lib/src/schemas/pnpm-config.schema.json +++ b/libraries/rush-lib/src/schemas/pnpm-config.schema.json @@ -191,6 +191,11 @@ "type": "boolean" }, + "minimumReleaseAge": { + "description": "The minimum number of minutes that must pass after a version is published before pnpm will install it. This setting helps reduce the risk of installing compromised packages, as malicious releases are typically discovered and removed within a short time frame.\n\n(SUPPORTED ONLY IN PNPM 10.16.0 AND NEWER)\n\nPNPM documentation: https://pnpm.io/settings#minimumreleaseage\n\nThe default value is 0 (disabled).", + "type": "number" + }, + "alwaysFullInstall": { "description": "(EXPERIMENTAL) If 'true', then filtered installs ('rush install --to my-project') * will be disregarded, instead always performing a full installation of the lockfile.", "type": "boolean" From 6def020ba554cecccd2266abb87a217b01702ef9 Mon Sep 17 00:00:00 2001 From: mrzepinski Date: Tue, 7 Oct 2025 13:43:14 +0200 Subject: [PATCH 2/6] Revert manual API changes - let API Extractor regenerate --- common/reviews/api/rush-lib.api.md | 1 - 1 file changed, 1 deletion(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 641c65734a6..cdad7dd3b7a 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1188,7 +1188,6 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration static loadFromJsonFileOrThrow(jsonFilePath: string, commonTempFolder: string): PnpmOptionsConfiguration; // @internal (undocumented) static loadFromJsonObject(json: _IPnpmOptionsJson, commonTempFolder: string): PnpmOptionsConfiguration; - readonly minimumReleaseAge: number | undefined; readonly pnpmLockfilePolicies: IPnpmLockfilePolicies | undefined; readonly pnpmStore: PnpmStoreLocation; readonly pnpmStorePath: string; From e0da1d1fc6a85c0abfb8f544595cb2d9544aabb4 Mon Sep 17 00:00:00 2001 From: mrzepinski Date: Tue, 7 Oct 2025 13:45:41 +0200 Subject: [PATCH 3/6] Revert "Revert manual API changes - let API Extractor regenerate" This reverts commit 6def020ba554cecccd2266abb87a217b01702ef9. --- common/reviews/api/rush-lib.api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index cdad7dd3b7a..641c65734a6 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1188,6 +1188,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration static loadFromJsonFileOrThrow(jsonFilePath: string, commonTempFolder: string): PnpmOptionsConfiguration; // @internal (undocumented) static loadFromJsonObject(json: _IPnpmOptionsJson, commonTempFolder: string): PnpmOptionsConfiguration; + readonly minimumReleaseAge: number | undefined; readonly pnpmLockfilePolicies: IPnpmLockfilePolicies | undefined; readonly pnpmStore: PnpmStoreLocation; readonly pnpmStorePath: string; From 14d66ef2e9419582e059ac12c3d4cb0c419cc3de Mon Sep 17 00:00:00 2001 From: mrzepinski <435196+mrzepinski@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:12:35 +0200 Subject: [PATCH 4/6] Update API documentation with minimumReleaseAge property --- common/reviews/api/rush-lib.api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 641c65734a6..c3e7f00d952 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -773,6 +773,7 @@ export interface _IPnpmOptionsJson extends IPackageManagerOptionsJsonBase { globalPackageExtensions?: Record; globalPatchedDependencies?: Record; globalPeerDependencyRules?: IPnpmPeerDependencyRules; + minimumReleaseAge?: number; pnpmLockfilePolicies?: IPnpmLockfilePolicies; pnpmStore?: PnpmStoreLocation; preventManualShrinkwrapChanges?: boolean; From bf487cf118a6e9aefa682505e23f8ccce6e426b4 Mon Sep 17 00:00:00 2001 From: mrzepinski <435196+mrzepinski@users.noreply.github.com> Date: Wed, 8 Oct 2025 06:22:26 +0200 Subject: [PATCH 5/6] Remove rush-lib change file as requested by reviewer --- ...d-minimum-release-age-support_2025-10-07-10-30.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 common/changes/@microsoft/rush-lib/add-minimum-release-age-support_2025-10-07-10-30.json diff --git a/common/changes/@microsoft/rush-lib/add-minimum-release-age-support_2025-10-07-10-30.json b/common/changes/@microsoft/rush-lib/add-minimum-release-age-support_2025-10-07-10-30.json deleted file mode 100644 index ecab7f99a0c..00000000000 --- a/common/changes/@microsoft/rush-lib/add-minimum-release-age-support_2025-10-07-10-30.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "changes": [ - { - "packageName": "@microsoft/rush-lib", - "comment": "Add support for PNPM's minimumReleaseAge setting to help mitigate supply chain attacks", - "type": "minor" - } - ], - "packageName": "@microsoft/rush-lib" -} From 8cccb1c15f060e76c638db8e42d6d47e25fad9ba Mon Sep 17 00:00:00 2001 From: mrzepinski <435196+mrzepinski@users.noreply.github.com> Date: Wed, 8 Oct 2025 06:49:21 +0200 Subject: [PATCH 6/6] Add minimumReleaseAgeExclude support and refactor to use package.json - Add minimumReleaseAgeExclude property to PnpmOptionsConfiguration - Update pnpm-config.schema.json with new property definition - Add documentation to template pnpm-config.json - Write minimumReleaseAge and minimumReleaseAgeExclude to package.json instead of passing as CLI args - Add PNPM version check (10.16.0+) in InstallHelpers - Remove command-line argument passing from BaseInstallManager - Update tests to verify minimumReleaseAgeExclude functionality - Fix TSDoc warning for @ character escaping Addresses code review feedback from PR #5405 --- common/reviews/api/rush-lib.api.md | 2 ++ .../common/config/rush/pnpm-config.json | 15 +++++++++++ .../src/logic/base/BaseInstallManager.ts | 22 ---------------- .../logic/installManager/InstallHelpers.ts | 26 +++++++++++++++++++ .../logic/pnpm/PnpmOptionsConfiguration.ts | 18 +++++++++++++ .../test/PnpmOptionsConfiguration.test.ts | 4 +++ .../pnpm-config-minimumReleaseAge.json | 3 ++- .../src/schemas/pnpm-config.schema.json | 9 +++++++ 8 files changed, 76 insertions(+), 23 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index c3e7f00d952..3fa9d1b178b 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -774,6 +774,7 @@ export interface _IPnpmOptionsJson extends IPackageManagerOptionsJsonBase { globalPatchedDependencies?: Record; globalPeerDependencyRules?: IPnpmPeerDependencyRules; minimumReleaseAge?: number; + minimumReleaseAgeExclude?: string[]; pnpmLockfilePolicies?: IPnpmLockfilePolicies; pnpmStore?: PnpmStoreLocation; preventManualShrinkwrapChanges?: boolean; @@ -1190,6 +1191,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration // @internal (undocumented) static loadFromJsonObject(json: _IPnpmOptionsJson, commonTempFolder: string): PnpmOptionsConfiguration; readonly minimumReleaseAge: number | undefined; + readonly minimumReleaseAgeExclude: string[] | undefined; readonly pnpmLockfilePolicies: IPnpmLockfilePolicies | undefined; readonly pnpmStore: PnpmStoreLocation; readonly pnpmStorePath: string; diff --git a/libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json b/libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json index 87bc893cbbd..d677fa2179f 100644 --- a/libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json +++ b/libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json @@ -84,6 +84,21 @@ */ /*[LINE "HYPOTHETICAL"]*/ "minimumReleaseAge": 1440, + /** + * An array of package names or patterns to exclude from the minimumReleaseAge check. + * This allows certain trusted packages to be installed immediately after publication. + * Patterns are supported using glob syntax (e.g., "@myorg/*" to exclude all packages from an organization). + * + * For example: + * + * "minimumReleaseAgeExclude": ["webpack", "react", "@myorg/*"] + * + * (SUPPORTED ONLY IN PNPM 10.16.0 AND NEWER) + * + * PNPM documentation: https://pnpm.io/settings#minimumreleaseageexclude + */ + /*[LINE "HYPOTHETICAL"]*/ "minimumReleaseAgeExclude": ["@myorg/*"], + /** * If true, then Rush will add the `--strict-peer-dependencies` command-line parameter when * invoking PNPM. This causes `rush update` to fail if there are unsatisfied peer dependencies, diff --git a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts index aa9be35a8ca..a38af503f97 100644 --- a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts @@ -948,28 +948,6 @@ ${gitLfsHookHandling} args.push(`--config.auto-install-peers=${autoInstallPeers}`); } - /* - If user set minimum-release-age in pnpm-config.json only, use the value in pnpm-config.json - If user set minimum-release-age in pnpm-config.json and .npmrc, use the value in pnpm-config.json - If user set minimum-release-age in .npmrc only, do nothing, let pnpm handle it - */ - const isMinimumReleaseAgeInNpmrc: boolean = isVariableSetInNpmrcFile( - subspace.getSubspaceConfigFolderPath(), - 'minimum-release-age', - this.rushConfiguration.isPnpm - ); - - const minimumReleaseAge: number | undefined = this.rushConfiguration.pnpmOptions.minimumReleaseAge; - if (minimumReleaseAge !== undefined) { - if (isMinimumReleaseAgeInNpmrc) { - this._terminal.writeWarningLine( - `Warning: PNPM's minimum-release-age is specified in both .npmrc and pnpm-config.json. ` + - `The value in pnpm-config.json will take precedence.` - ); - } - args.push(`--config.minimumReleaseAge=${minimumReleaseAge}`); - } - /* If user set resolution-mode in pnpm-config.json only, use the value in pnpm-config.json If user set resolution-mode in pnpm-config.json and .npmrc, use the value in pnpm-config.json diff --git a/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts b/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts index 65a640c69db..8f850b4e73d 100644 --- a/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts +++ b/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts @@ -34,6 +34,8 @@ interface ICommonPackageJson extends IPackageJson { ignoredOptionalDependencies?: typeof PnpmOptionsConfiguration.prototype.globalIgnoredOptionalDependencies; allowedDeprecatedVersions?: typeof PnpmOptionsConfiguration.prototype.globalAllowedDeprecatedVersions; patchedDependencies?: typeof PnpmOptionsConfiguration.prototype.globalPatchedDependencies; + minimumReleaseAge?: typeof PnpmOptionsConfiguration.prototype.minimumReleaseAge; + minimumReleaseAgeExclude?: typeof PnpmOptionsConfiguration.prototype.minimumReleaseAgeExclude; }; } @@ -100,6 +102,30 @@ export class InstallHelpers { commonPackageJson.pnpm.patchedDependencies = pnpmOptions.globalPatchedDependencies; } + if (pnpmOptions.minimumReleaseAge !== undefined || pnpmOptions.minimumReleaseAgeExclude) { + if ( + rushConfiguration.rushConfigurationJson.pnpmVersion !== undefined && + semver.lt(rushConfiguration.rushConfigurationJson.pnpmVersion, '10.16.0') + ) { + terminal.writeWarningLine( + Colorize.yellow( + `Your version of pnpm (${rushConfiguration.rushConfigurationJson.pnpmVersion}) ` + + `doesn't support the "minimumReleaseAge" or "minimumReleaseAgeExclude" fields in ` + + `${rushConfiguration.commonRushConfigFolder}/${RushConstants.pnpmConfigFilename}. ` + + 'Remove these fields or upgrade to pnpm 10.16.0 or newer.' + ) + ); + } + + if (pnpmOptions.minimumReleaseAge !== undefined) { + commonPackageJson.pnpm.minimumReleaseAge = pnpmOptions.minimumReleaseAge; + } + + if (pnpmOptions.minimumReleaseAgeExclude) { + commonPackageJson.pnpm.minimumReleaseAgeExclude = pnpmOptions.minimumReleaseAgeExclude; + } + } + if (pnpmOptions.unsupportedPackageJsonSettings) { merge(commonPackageJson, pnpmOptions.unsupportedPackageJsonSettings); } diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts b/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts index 8d4131bfef0..4b249e7a883 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts @@ -142,6 +142,10 @@ export interface IPnpmOptionsJson extends IPackageManagerOptionsJsonBase { * {@inheritDoc PnpmOptionsConfiguration.minimumReleaseAge} */ minimumReleaseAge?: number; + /** + * {@inheritDoc PnpmOptionsConfiguration.minimumReleaseAgeExclude} + */ + minimumReleaseAgeExclude?: string[]; /** * {@inheritDoc PnpmOptionsConfiguration.alwaysInjectDependenciesFromOtherSubspaces} */ @@ -276,6 +280,19 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration */ public readonly minimumReleaseAge: number | undefined; + /** + * List of package names or patterns that are excluded from the minimumReleaseAge check. + * These packages will always install the newest version immediately, even if minimumReleaseAge is set. + * + * @remarks + * (SUPPORTED ONLY IN PNPM 10.16.0 AND NEWER) + * + * PNPM documentation: https://pnpm.io/settings#minimumreleaseageexclude + * + * Example: ["webpack", "react", "\@myorg/*"] + */ + public readonly minimumReleaseAgeExclude: string[] | undefined; + /** * If true, then `rush update` add injected install options for all cross-subspace * workspace dependencies, to avoid subspace doppelganger issue. @@ -444,6 +461,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration this.resolutionMode = json.resolutionMode; this.autoInstallPeers = json.autoInstallPeers; this.minimumReleaseAge = json.minimumReleaseAge; + this.minimumReleaseAgeExclude = json.minimumReleaseAgeExclude; this.alwaysInjectDependenciesFromOtherSubspaces = json.alwaysInjectDependenciesFromOtherSubspaces; this.alwaysFullInstall = json.alwaysFullInstall; this.pnpmLockfilePolicies = json.pnpmLockfilePolicies; diff --git a/libraries/rush-lib/src/logic/pnpm/test/PnpmOptionsConfiguration.test.ts b/libraries/rush-lib/src/logic/pnpm/test/PnpmOptionsConfiguration.test.ts index 7f22a4b0f2d..75b9e3e874f 100644 --- a/libraries/rush-lib/src/logic/pnpm/test/PnpmOptionsConfiguration.test.ts +++ b/libraries/rush-lib/src/logic/pnpm/test/PnpmOptionsConfiguration.test.ts @@ -81,5 +81,9 @@ describe(PnpmOptionsConfiguration.name, () => { ); expect(pnpmConfiguration.minimumReleaseAge).toEqual(1440); + expect(TestUtilities.stripAnnotations(pnpmConfiguration.minimumReleaseAgeExclude)).toEqual([ + 'webpack', + '@myorg/*' + ]); }); }); diff --git a/libraries/rush-lib/src/logic/pnpm/test/jsonFiles/pnpm-config-minimumReleaseAge.json b/libraries/rush-lib/src/logic/pnpm/test/jsonFiles/pnpm-config-minimumReleaseAge.json index 6075cf453e3..54efb4b31ff 100644 --- a/libraries/rush-lib/src/logic/pnpm/test/jsonFiles/pnpm-config-minimumReleaseAge.json +++ b/libraries/rush-lib/src/logic/pnpm/test/jsonFiles/pnpm-config-minimumReleaseAge.json @@ -1,3 +1,4 @@ { - "minimumReleaseAge": 1440 + "minimumReleaseAge": 1440, + "minimumReleaseAgeExclude": ["webpack", "@myorg/*"] } diff --git a/libraries/rush-lib/src/schemas/pnpm-config.schema.json b/libraries/rush-lib/src/schemas/pnpm-config.schema.json index ec7b3f00568..0501173b754 100644 --- a/libraries/rush-lib/src/schemas/pnpm-config.schema.json +++ b/libraries/rush-lib/src/schemas/pnpm-config.schema.json @@ -196,6 +196,15 @@ "type": "number" }, + "minimumReleaseAgeExclude": { + "description": "List of package names or patterns that are excluded from the minimumReleaseAge check. These packages will always install the newest version immediately, even if minimumReleaseAge is set. Supports glob patterns (e.g., \"@myorg/*\").\n\n(SUPPORTED ONLY IN PNPM 10.16.0 AND NEWER)\n\nPNPM documentation: https://pnpm.io/settings#minimumreleaseageexclude\n\nExample: [\"webpack\", \"react\", \"@myorg/*\"]", + "type": "array", + "items": { + "description": "Package name or pattern", + "type": "string" + } + }, + "alwaysFullInstall": { "description": "(EXPERIMENTAL) If 'true', then filtered installs ('rush install --to my-project') * will be disregarded, instead always performing a full installation of the lockfile.", "type": "boolean"