diff --git a/common/changes/@microsoft/rush/globalOnlyBuiltDependencies_2025-12-28-19-40.json b/common/changes/@microsoft/rush/globalOnlyBuiltDependencies_2025-12-28-19-40.json new file mode 100644 index 00000000000..9bd224034d3 --- /dev/null +++ b/common/changes/@microsoft/rush/globalOnlyBuiltDependencies_2025-12-28-19-40.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Add support for the `globalOnlyBuiltDependencies` PNPM 10.x option to specify an allowlist of packages permitted to run build scripts to `common/config/rush/pnpm-config.json`.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index da3c87d6bd4..e0f49f952c7 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -743,6 +743,7 @@ export interface _IPnpmOptionsJson extends IPackageManagerOptionsJsonBase { globalCatalogs?: Record>; globalIgnoredOptionalDependencies?: string[]; globalNeverBuiltDependencies?: string[]; + globalOnlyBuiltDependencies?: string[]; globalOverrides?: Record; globalPackageExtensions?: Record; globalPatchedDependencies?: Record; @@ -1155,6 +1156,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration readonly globalCatalogs: Record> | undefined; readonly globalIgnoredOptionalDependencies: string[] | undefined; readonly globalNeverBuiltDependencies: string[] | undefined; + readonly globalOnlyBuiltDependencies: string[] | undefined; readonly globalOverrides: Record | undefined; readonly globalPackageExtensions: Record | undefined; get globalPatchedDependencies(): Record | undefined; 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 d677fa2179f..5aeba49a31c 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 @@ -306,6 +306,31 @@ /*[LINE "HYPOTHETICAL"]*/ "fsevents" ], + /** + * The `globalOnlyBuiltDependencies` setting specifies which dependencies are permitted to run + * build scripts (`preinstall`, `install`, and `postinstall` lifecycle events). This is the inverse + * of `globalNeverBuiltDependencies`. In PNPM 10.x, build scripts are disabled by default for + * security, so this setting is required to explicitly permit specific packages to run their + * build scripts. The settings are written to the `onlyBuiltDependencies` field of the + * `pnpm-workspace.yaml` file that is generated by Rush during installation. + * + * (SUPPORTED ONLY IN PNPM 10.1.0 AND NEWER) + * + * PNPM documentation: https://pnpm.io/settings#onlybuiltdependencies + * + * Example: + * "globalOnlyBuiltDependencies": [ + * "esbuild", + * "playwright", + * "@swc/core" + * ] + */ + /*[BEGIN "HYPOTHETICAL"]*/ + "globalOnlyBuiltDependencies": [ + "esbuild" + ], + /*[END "HYPOTHETICAL"]*/ + /** * The `globalIgnoredOptionalDependencies` setting suppresses the installation of optional NPM * dependencies specified in the list. This is useful when certain optional dependencies are diff --git a/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts b/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts index 8f850b4e73d..35a1998c6a9 100644 --- a/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts +++ b/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts @@ -31,6 +31,7 @@ interface ICommonPackageJson extends IPackageJson { packageExtensions?: typeof PnpmOptionsConfiguration.prototype.globalPackageExtensions; peerDependencyRules?: typeof PnpmOptionsConfiguration.prototype.globalPeerDependencyRules; neverBuiltDependencies?: typeof PnpmOptionsConfiguration.prototype.globalNeverBuiltDependencies; + onlyBuiltDependencies?: typeof PnpmOptionsConfiguration.prototype.globalOnlyBuiltDependencies; ignoredOptionalDependencies?: typeof PnpmOptionsConfiguration.prototype.globalIgnoredOptionalDependencies; allowedDeprecatedVersions?: typeof PnpmOptionsConfiguration.prototype.globalAllowedDeprecatedVersions; patchedDependencies?: typeof PnpmOptionsConfiguration.prototype.globalPatchedDependencies; @@ -76,6 +77,24 @@ export class InstallHelpers { commonPackageJson.pnpm.neverBuiltDependencies = pnpmOptions.globalNeverBuiltDependencies; } + if (pnpmOptions.globalOnlyBuiltDependencies) { + if ( + rushConfiguration.rushConfigurationJson.pnpmVersion !== undefined && + semver.lt(rushConfiguration.rushConfigurationJson.pnpmVersion, '10.1.0') + ) { + terminal.writeWarningLine( + Colorize.yellow( + `Your version of pnpm (${rushConfiguration.rushConfigurationJson.pnpmVersion}) ` + + `doesn't support the "globalOnlyBuiltDependencies" field in ` + + `${rushConfiguration.commonRushConfigFolder}/${RushConstants.pnpmConfigFilename}. ` + + 'Remove this field or upgrade to pnpm 10.1.0 or newer.' + ) + ); + } + + commonPackageJson.pnpm.onlyBuiltDependencies = pnpmOptions.globalOnlyBuiltDependencies; + } + if (pnpmOptions.globalIgnoredOptionalDependencies) { if ( rushConfiguration.rushConfigurationJson.pnpmVersion !== undefined && diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts b/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts index 555f02e7dd0..7e59c637b6b 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts @@ -114,6 +114,10 @@ export interface IPnpmOptionsJson extends IPackageManagerOptionsJsonBase { * {@inheritDoc PnpmOptionsConfiguration.globalNeverBuiltDependencies} */ globalNeverBuiltDependencies?: string[]; + /** + * {@inheritDoc PnpmOptionsConfiguration.globalOnlyBuiltDependencies} + */ + globalOnlyBuiltDependencies?: string[]; /** * {@inheritDoc PnpmOptionsConfiguration.globalIgnoredOptionalDependencies} */ @@ -365,6 +369,20 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration */ public readonly globalNeverBuiltDependencies: string[] | undefined; + /** + * The `globalOnlyBuiltDependencies` setting specifies an allowlist of dependencies that are permitted + * to run build scripts (`preinstall`, `install`, and `postinstall` lifecycle events). This is the inverse + * of `globalNeverBuiltDependencies`. In PNPM 10.x, build scripts are disabled by default for security, + * so this setting is required to explicitly permit specific packages to run their build scripts. + * The settings are copied into the `pnpm.onlyBuiltDependencies` field of the `common/temp/package.json` + * file that is generated by Rush during installation. + * + * (SUPPORTED ONLY IN PNPM 10.1.0 AND NEWER) + * + * PNPM documentation: https://pnpm.io/package_json#pnpmonlybuiltdependencies + */ + public readonly globalOnlyBuiltDependencies: string[] | undefined; + /** * The ignoredOptionalDependencies setting allows you to exclude certain optional dependencies from being installed * during the Rush installation process. This can be useful when optional dependencies are not required or are @@ -468,6 +486,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration this.globalPeerDependencyRules = json.globalPeerDependencyRules; this.globalPackageExtensions = json.globalPackageExtensions; this.globalNeverBuiltDependencies = json.globalNeverBuiltDependencies; + this.globalOnlyBuiltDependencies = json.globalOnlyBuiltDependencies; this.globalIgnoredOptionalDependencies = json.globalIgnoredOptionalDependencies; this.globalAllowedDeprecatedVersions = json.globalAllowedDeprecatedVersions; this.unsupportedPackageJsonSettings = json.unsupportedPackageJsonSettings; 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 c590ca689fe..369054709ad 100644 --- a/libraries/rush-lib/src/logic/pnpm/test/PnpmOptionsConfiguration.test.ts +++ b/libraries/rush-lib/src/logic/pnpm/test/PnpmOptionsConfiguration.test.ts @@ -74,6 +74,19 @@ describe(PnpmOptionsConfiguration.name, () => { ]); }); + it('loads onlyBuiltDependencies', () => { + const pnpmConfiguration: PnpmOptionsConfiguration = PnpmOptionsConfiguration.loadFromJsonFileOrThrow( + `${__dirname}/jsonFiles/pnpm-config-onlyBuiltDependencies.json`, + fakeCommonTempFolder + ); + + expect(TestUtilities.stripAnnotations(pnpmConfiguration.globalOnlyBuiltDependencies)).toEqual([ + 'esbuild', + 'playwright', + '@swc/core' + ]); + }); + it('loads minimumReleaseAge', () => { const pnpmConfiguration: PnpmOptionsConfiguration = PnpmOptionsConfiguration.loadFromJsonFileOrThrow( `${__dirname}/jsonFiles/pnpm-config-minimumReleaseAge.json`, diff --git a/libraries/rush-lib/src/logic/pnpm/test/jsonFiles/pnpm-config-onlyBuiltDependencies.json b/libraries/rush-lib/src/logic/pnpm/test/jsonFiles/pnpm-config-onlyBuiltDependencies.json new file mode 100644 index 00000000000..46b45a3dd41 --- /dev/null +++ b/libraries/rush-lib/src/logic/pnpm/test/jsonFiles/pnpm-config-onlyBuiltDependencies.json @@ -0,0 +1,3 @@ +{ + "globalOnlyBuiltDependencies": ["esbuild", "playwright", "@swc/core"] +} diff --git a/libraries/rush-lib/src/logic/test/InstallHelpers.test.ts b/libraries/rush-lib/src/logic/test/InstallHelpers.test.ts index d161d5da632..31acfc54516 100644 --- a/libraries/rush-lib/src/logic/test/InstallHelpers.test.ts +++ b/libraries/rush-lib/src/logic/test/InstallHelpers.test.ts @@ -67,6 +67,7 @@ describe('InstallHelpers', () => { } }, neverBuiltDependencies: ['fsevents', 'level'], + onlyBuiltDependencies: ['esbuild', 'playwright'], pnpmFutureFeature: true } }) diff --git a/libraries/rush-lib/src/logic/test/pnpmConfig/common/config/rush/pnpm-config.json b/libraries/rush-lib/src/logic/test/pnpmConfig/common/config/rush/pnpm-config.json index 1636e755728..2a51adf500b 100644 --- a/libraries/rush-lib/src/logic/test/pnpmConfig/common/config/rush/pnpm-config.json +++ b/libraries/rush-lib/src/logic/test/pnpmConfig/common/config/rush/pnpm-config.json @@ -13,6 +13,7 @@ } }, "globalNeverBuiltDependencies": ["fsevents", "level"], + "globalOnlyBuiltDependencies": ["esbuild", "playwright"], "globalCatalogs": { "default": { "react": "^18.0.0", diff --git a/libraries/rush-lib/src/logic/test/pnpmConfig/rush.json b/libraries/rush-lib/src/logic/test/pnpmConfig/rush.json index 332b70494a4..db85227290b 100644 --- a/libraries/rush-lib/src/logic/test/pnpmConfig/rush.json +++ b/libraries/rush-lib/src/logic/test/pnpmConfig/rush.json @@ -1,5 +1,5 @@ { - "pnpmVersion": "6.23.1", + "pnpmVersion": "10.1.0", "rushVersion": "5.58.0", "projects": [] } diff --git a/libraries/rush-lib/src/schemas/pnpm-config.schema.json b/libraries/rush-lib/src/schemas/pnpm-config.schema.json index c7e628af851..4646fe2398e 100644 --- a/libraries/rush-lib/src/schemas/pnpm-config.schema.json +++ b/libraries/rush-lib/src/schemas/pnpm-config.schema.json @@ -150,6 +150,15 @@ } }, + "globalOnlyBuiltDependencies": { + "description": "This field allows specifying which dependencies are permitted to run build scripts (preinstall, install, postinstall). In PNPM 10.x, build scripts are disabled by default for security. Use this allowlist to explicitly permit specific packages to run their build scripts.\n\n(SUPPORTED ONLY IN PNPM 10.1.0 AND NEWER)\n\nPNPM documentation: https://pnpm.io/settings#onlybuiltdependencies", + "type": "array", + "items": { + "description": "Specify package name of the dependency allowed to run build scripts", + "type": "string" + } + }, + "globalIgnoredOptionalDependencies": { "description": "This field allows you to skip the installation of specific optional dependencies. The listed packages will be treated as if they are not present in the dependency tree during installation, meaning they will not be installed even if required by other packages.\n\n(SUPPORTED ONLY IN PNPM 9.0.0 AND NEWER)\n\nPNPM documentation: https://pnpm.io/package_json#pnpmalloweddeprecatedversions", "type": "array",