Skip to content

Commit acc286e

Browse files
Copiloticlanton
andauthored
[rush] Implement rush-pnpm approve-builds to persist globalOnlyBuiltDependencies (#5576)
* Initial plan * Add approve-builds command support to rush-pnpm Co-authored-by: iclanton <[email protected]> * Fix linter warnings and update API file Co-authored-by: iclanton <[email protected]> * Add changelog entry for approve-builds command Co-authored-by: iclanton <[email protected]> * DRY up duplicate code and use async JSON loading Co-authored-by: iclanton <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: iclanton <[email protected]>
1 parent 293efd9 commit acc286e

File tree

5 files changed

+105
-10
lines changed

5 files changed

+105
-10
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/rush",
5+
"comment": "Add support for `rush-pnpm approve-builds` command to persist `globalOnlyBuiltDependencies` in pnpm-config.json",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush"
10+
}

common/reviews/api/rush-lib.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
11761176
readonly resolutionMode: PnpmResolutionMode | undefined;
11771177
readonly strictPeerDependencies: boolean;
11781178
readonly unsupportedPackageJsonSettings: unknown | undefined;
1179+
updateGlobalOnlyBuiltDependencies(onlyBuiltDependencies: string[] | undefined): void;
11791180
updateGlobalPatchedDependencies(patchedDependencies: Record<string, string> | undefined): void;
11801181
readonly useWorkspaces: boolean;
11811182
}

libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,36 @@ export class RushPnpmCommandLineParser {
359359
}
360360
break;
361361
}
362+
case 'approve-builds': {
363+
const semver: typeof import('semver') = await import('semver');
364+
/**
365+
* The "approve-builds" command was introduced in pnpm version 10.1.0
366+
* to approve packages for running build scripts when onlyBuiltDependencies is used
367+
*/
368+
if (semver.lt(this._rushConfiguration.packageManagerToolVersion, '10.1.0')) {
369+
this._terminal.writeErrorLine(
370+
PrintUtilities.wrapWords(
371+
`Error: The "pnpm approve-builds" command is added after [email protected].` +
372+
` Please update "pnpmVersion" >= 10.1.0 in ${RushConstants.rushJsonFilename} file and run "rush update" to use this command.`
373+
) + '\n'
374+
);
375+
throw new AlreadyReportedError();
376+
}
377+
const pnpmOptionsJsonFilename: string = path.join(
378+
this._rushConfiguration.commonRushConfigFolder,
379+
RushConstants.pnpmConfigFilename
380+
);
381+
if (this._rushConfiguration.rushConfigurationJson.pnpmOptions) {
382+
this._terminal.writeErrorLine(
383+
PrintUtilities.wrapWords(
384+
`Error: The "pnpm approve-builds" command is incompatible with specifying "pnpmOptions" in ${RushConstants.rushJsonFilename} file.` +
385+
` Please move the content of "pnpmOptions" in ${RushConstants.rushJsonFilename} file to ${pnpmOptionsJsonFilename}`
386+
) + '\n'
387+
);
388+
throw new AlreadyReportedError();
389+
}
390+
break;
391+
}
362392

363393
// Known safe
364394
case 'audit':
@@ -532,6 +562,39 @@ export class RushPnpmCommandLineParser {
532562
}
533563
break;
534564
}
565+
case 'approve-builds': {
566+
if (this._subspace.getPnpmOptions() === undefined) {
567+
const subspaceConfigFolder: string = this._subspace.getSubspaceConfigFolderPath();
568+
this._terminal.writeErrorLine(
569+
`The "rush-pnpm approve-builds" command cannot proceed without a pnpm-config.json file.` +
570+
` Create one in this folder: ${subspaceConfigFolder}`
571+
);
572+
break;
573+
}
574+
575+
// Example: "C:\MyRepo\common\temp\package.json"
576+
const commonPackageJsonFilename: string = `${subspaceTempFolder}/${FileConstants.PackageJson}`;
577+
const commonPackageJson: JsonObject = await JsonFile.loadAsync(commonPackageJsonFilename);
578+
const newGlobalOnlyBuiltDependencies: string[] | undefined =
579+
commonPackageJson?.pnpm?.onlyBuiltDependencies;
580+
const pnpmOptions: PnpmOptionsConfiguration | undefined = this._subspace.getPnpmOptions();
581+
const currentGlobalOnlyBuiltDependencies: string[] | undefined =
582+
pnpmOptions?.globalOnlyBuiltDependencies;
583+
584+
if (!Objects.areDeepEqual(currentGlobalOnlyBuiltDependencies, newGlobalOnlyBuiltDependencies)) {
585+
// Update onlyBuiltDependencies to pnpm configuration file
586+
pnpmOptions?.updateGlobalOnlyBuiltDependencies(newGlobalOnlyBuiltDependencies);
587+
588+
// Rerun installation to update
589+
await this._doRushUpdateAsync();
590+
591+
this._terminal.writeWarningLine(
592+
`Rush refreshed the ${RushConstants.pnpmConfigFilename} and shrinkwrap file.\n` +
593+
' Please commit this change to Git.'
594+
);
595+
}
596+
break;
597+
}
535598
}
536599
}
537600

libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -600,17 +600,28 @@ export class WorkspaceInstallManager extends BaseInstallManager {
600600
}
601601
}
602602

603-
const onPnpmStdoutChunk: ((chunk: string) => void) | undefined =
604-
pnpmTips.length > 0
605-
? (chunk: string): void => {
606-
// Iterate over the supported custom tip metadata and try to match the chunk.
607-
for (const { isMatch, tipId } of pnpmTips) {
608-
if (isMatch?.(chunk)) {
609-
tipIDsToBePrinted.add(tipId);
610-
}
611-
}
603+
const onPnpmStdoutChunk: ((chunk: string) => string | void) | undefined = (
604+
chunk: string
605+
): string | void => {
606+
// Iterate over the supported custom tip metadata and try to match the chunk.
607+
if (pnpmTips.length > 0) {
608+
for (const { isMatch, tipId } of pnpmTips) {
609+
if (isMatch?.(chunk)) {
610+
tipIDsToBePrinted.add(tipId);
612611
}
613-
: undefined;
612+
}
613+
}
614+
615+
// Replace `pnpm approve-builds` with `rush-pnpm approve-builds` when running
616+
// `rush install` or `rush update` to instruct users to use the correct command
617+
const modifiedChunk: string = chunk.replace(
618+
/pnpm approve-builds/g,
619+
`rush-pnpm --subspace ${subspace.subspaceName} approve-builds`
620+
);
621+
622+
// Return modified chunk if it was changed, otherwise return void to keep original
623+
return modifiedChunk !== chunk ? modifiedChunk : undefined;
624+
};
614625
try {
615626
await Utilities.executeCommandWithRetryAsync(
616627
{

libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,4 +539,14 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
539539
JsonFile.save(this._json, this.jsonFilename, { updateExistingFile: true });
540540
}
541541
}
542+
543+
/**
544+
* Updates globalOnlyBuiltDependencies field of the PNPM options in the common/config/rush/pnpm-config.json file.
545+
*/
546+
public updateGlobalOnlyBuiltDependencies(onlyBuiltDependencies: string[] | undefined): void {
547+
this._json.globalOnlyBuiltDependencies = onlyBuiltDependencies;
548+
if (this.jsonFilename) {
549+
JsonFile.save(this._json, this.jsonFilename, { updateExistingFile: true });
550+
}
551+
}
542552
}

0 commit comments

Comments
 (0)