diff --git a/common/changes/@microsoft/rush/approve-builds_2026-01-26-21-57.json b/common/changes/@microsoft/rush/approve-builds_2026-01-26-21-57.json new file mode 100644 index 0000000000..0a04580e4b --- /dev/null +++ b/common/changes/@microsoft/rush/approve-builds_2026-01-26-21-57.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Add support for `rush-pnpm approve-builds` command to persist `globalOnlyBuiltDependencies` in 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 e0f49f952c..22ee65f07d 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -1176,6 +1176,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration readonly resolutionMode: PnpmResolutionMode | undefined; readonly strictPeerDependencies: boolean; readonly unsupportedPackageJsonSettings: unknown | undefined; + updateGlobalOnlyBuiltDependencies(onlyBuiltDependencies: string[] | undefined): void; updateGlobalPatchedDependencies(patchedDependencies: Record | undefined): void; readonly useWorkspaces: boolean; } diff --git a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts index 7c7787a436..1980c57f08 100644 --- a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts +++ b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts @@ -359,6 +359,36 @@ export class RushPnpmCommandLineParser { } break; } + case 'approve-builds': { + const semver: typeof import('semver') = await import('semver'); + /** + * The "approve-builds" command was introduced in pnpm version 10.1.0 + * to approve packages for running build scripts when onlyBuiltDependencies is used + */ + if (semver.lt(this._rushConfiguration.packageManagerToolVersion, '10.1.0')) { + this._terminal.writeErrorLine( + PrintUtilities.wrapWords( + `Error: The "pnpm approve-builds" command is added after pnpm@10.1.0.` + + ` Please update "pnpmVersion" >= 10.1.0 in ${RushConstants.rushJsonFilename} file and run "rush update" to use this command.` + ) + '\n' + ); + throw new AlreadyReportedError(); + } + const pnpmOptionsJsonFilename: string = path.join( + this._rushConfiguration.commonRushConfigFolder, + RushConstants.pnpmConfigFilename + ); + if (this._rushConfiguration.rushConfigurationJson.pnpmOptions) { + this._terminal.writeErrorLine( + PrintUtilities.wrapWords( + `Error: The "pnpm approve-builds" command is incompatible with specifying "pnpmOptions" in ${RushConstants.rushJsonFilename} file.` + + ` Please move the content of "pnpmOptions" in ${RushConstants.rushJsonFilename} file to ${pnpmOptionsJsonFilename}` + ) + '\n' + ); + throw new AlreadyReportedError(); + } + break; + } // Known safe case 'audit': @@ -532,6 +562,39 @@ export class RushPnpmCommandLineParser { } break; } + case 'approve-builds': { + if (this._subspace.getPnpmOptions() === undefined) { + const subspaceConfigFolder: string = this._subspace.getSubspaceConfigFolderPath(); + this._terminal.writeErrorLine( + `The "rush-pnpm approve-builds" command cannot proceed without a pnpm-config.json file.` + + ` Create one in this folder: ${subspaceConfigFolder}` + ); + break; + } + + // Example: "C:\MyRepo\common\temp\package.json" + const commonPackageJsonFilename: string = `${subspaceTempFolder}/${FileConstants.PackageJson}`; + const commonPackageJson: JsonObject = await JsonFile.loadAsync(commonPackageJsonFilename); + const newGlobalOnlyBuiltDependencies: string[] | undefined = + commonPackageJson?.pnpm?.onlyBuiltDependencies; + const pnpmOptions: PnpmOptionsConfiguration | undefined = this._subspace.getPnpmOptions(); + const currentGlobalOnlyBuiltDependencies: string[] | undefined = + pnpmOptions?.globalOnlyBuiltDependencies; + + if (!Objects.areDeepEqual(currentGlobalOnlyBuiltDependencies, newGlobalOnlyBuiltDependencies)) { + // Update onlyBuiltDependencies to pnpm configuration file + pnpmOptions?.updateGlobalOnlyBuiltDependencies(newGlobalOnlyBuiltDependencies); + + // Rerun installation to update + await this._doRushUpdateAsync(); + + this._terminal.writeWarningLine( + `Rush refreshed the ${RushConstants.pnpmConfigFilename} and shrinkwrap file.\n` + + ' Please commit this change to Git.' + ); + } + break; + } } } diff --git a/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts b/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts index 0c603aef59..4be7bb4a91 100644 --- a/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts +++ b/libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts @@ -600,17 +600,28 @@ export class WorkspaceInstallManager extends BaseInstallManager { } } - const onPnpmStdoutChunk: ((chunk: string) => void) | undefined = - pnpmTips.length > 0 - ? (chunk: string): void => { - // Iterate over the supported custom tip metadata and try to match the chunk. - for (const { isMatch, tipId } of pnpmTips) { - if (isMatch?.(chunk)) { - tipIDsToBePrinted.add(tipId); - } - } + const onPnpmStdoutChunk: ((chunk: string) => string | void) | undefined = ( + chunk: string + ): string | void => { + // Iterate over the supported custom tip metadata and try to match the chunk. + if (pnpmTips.length > 0) { + for (const { isMatch, tipId } of pnpmTips) { + if (isMatch?.(chunk)) { + tipIDsToBePrinted.add(tipId); } - : undefined; + } + } + + // Replace `pnpm approve-builds` with `rush-pnpm approve-builds` when running + // `rush install` or `rush update` to instruct users to use the correct command + const modifiedChunk: string = chunk.replace( + /pnpm approve-builds/g, + `rush-pnpm --subspace ${subspace.subspaceName} approve-builds` + ); + + // Return modified chunk if it was changed, otherwise return void to keep original + return modifiedChunk !== chunk ? modifiedChunk : undefined; + }; try { await Utilities.executeCommandWithRetryAsync( { diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts b/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts index 7e59c637b6..026ecd2933 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts @@ -539,4 +539,14 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration JsonFile.save(this._json, this.jsonFilename, { updateExistingFile: true }); } } + + /** + * Updates globalOnlyBuiltDependencies field of the PNPM options in the common/config/rush/pnpm-config.json file. + */ + public updateGlobalOnlyBuiltDependencies(onlyBuiltDependencies: string[] | undefined): void { + this._json.globalOnlyBuiltDependencies = onlyBuiltDependencies; + if (this.jsonFilename) { + JsonFile.save(this._json, this.jsonFilename, { updateExistingFile: true }); + } + } }