Skip to content

Commit 460ea0f

Browse files
authored
feat: add globalAllowBuilds support for pnpm 11+, deprecate globalNeverBuiltDependencies
1 parent 86d53ab commit 460ea0f

12 files changed

Lines changed: 274 additions & 5 deletions

File tree

libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,8 @@
344344
},
345345

346346
/**
347+
* @deprecated Use `globalAllowBuilds` instead.
348+
*
347349
* The `globalNeverBuiltDependencies` setting suppresses the `preinstall`, `install`, and `postinstall`
348350
* lifecycle events for the specified NPM dependencies. This is useful for scripts with poor practices
349351
* such as downloading large binaries without retries or attempting to invoke OS tools such as
@@ -352,12 +354,42 @@
352354
* The settings are copied into the `pnpm.neverBuiltDependencies` field of the `common/temp/package.json`
353355
* file that is generated by Rush during installation.
354356
*
357+
* NOTE: This setting is not supported in pnpm 11.0.0+. Use `globalAllowBuilds` instead.
358+
*
355359
* PNPM documentation: https://pnpm.io/package_json#pnpmneverbuiltdependencies
356360
*/
357361
"globalNeverBuiltDependencies": [
358362
/*[LINE "HYPOTHETICAL"]*/ "fsevents"
359363
],
360364

365+
/**
366+
* The `globalAllowBuilds` setting is a map of package names to booleans that controls which
367+
* dependencies are permitted to run build scripts (`preinstall`, `install`, `postinstall`
368+
* lifecycle events). A value of `true` explicitly permits a package to run build scripts;
369+
* a value of `false` explicitly blocks it. Packages not listed inherit the default behavior.
370+
*
371+
* This is the replacement for `globalNeverBuiltDependencies` and `globalOnlyBuiltDependencies`,
372+
* and is the only way to control build permissions in pnpm 11+. The settings are written to the
373+
* `allowBuilds` field of the `pnpm-workspace.yaml` file that is generated by Rush during
374+
* installation.
375+
*
376+
* (SUPPORTED ONLY IN PNPM 11.0.0 AND NEWER)
377+
*
378+
* PNPM documentation: https://pnpm.io/settings#allowbuilds
379+
*
380+
* Example:
381+
* "globalAllowBuilds": {
382+
* "esbuild": true,
383+
* "playwright": true,
384+
* "core-js": false
385+
* }
386+
*/
387+
/*[BEGIN "HYPOTHETICAL"]*/
388+
"globalAllowBuilds": {
389+
"esbuild": true
390+
},
391+
/*[END "HYPOTHETICAL"]*/
392+
361393
/**
362394
* The `globalOnlyBuiltDependencies` setting specifies which dependencies are permitted to run
363395
* build scripts (`preinstall`, `install`, and `postinstall` lifecycle events). This is the inverse

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,32 @@ export class InstallHelpers {
7777
}
7878

7979
if (pnpmOptions.globalNeverBuiltDependencies) {
80-
commonPackageJson.pnpm.neverBuiltDependencies = pnpmOptions.globalNeverBuiltDependencies;
80+
if (
81+
rushConfiguration.rushConfigurationJson.pnpmVersion !== undefined &&
82+
semver.gte(rushConfiguration.rushConfigurationJson.pnpmVersion, '11.0.0')
83+
) {
84+
terminal.writeWarningLine(
85+
Colorize.yellow(
86+
`The "globalNeverBuiltDependencies" field in ` +
87+
`${rushConfiguration.commonRushConfigFolder}/${RushConstants.pnpmConfigFilename} ` +
88+
`is deprecated and is not supported by pnpm ${rushConfiguration.rushConfigurationJson.pnpmVersion}. ` +
89+
'Migrate to "globalAllowBuilds" instead. ' +
90+
`For example, replace "globalNeverBuiltDependencies": ["pkg"] ` +
91+
`with "globalAllowBuilds": { "pkg": false }.`
92+
)
93+
);
94+
} else {
95+
terminal.writeWarningLine(
96+
Colorize.yellow(
97+
`The "globalNeverBuiltDependencies" field in ` +
98+
`${rushConfiguration.commonRushConfigFolder}/${RushConstants.pnpmConfigFilename} ` +
99+
'is deprecated. Migrate to "globalAllowBuilds" instead. ' +
100+
`For example, replace "globalNeverBuiltDependencies": ["pkg"] ` +
101+
`with "globalAllowBuilds": { "pkg": false }.`
102+
)
103+
);
104+
commonPackageJson.pnpm!.neverBuiltDependencies = pnpmOptions.globalNeverBuiltDependencies;
105+
}
81106
}
82107

83108
if (pnpmOptions.globalOnlyBuiltDependencies) {

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,25 @@ export class WorkspaceInstallManager extends BaseInstallManager {
469469
workspaceFile.setCatalogs(catalogs);
470470
}
471471

472+
// Set allowBuilds in the workspace file if specified (requires pnpm 11.0.0+)
473+
if (pnpmOptions.globalAllowBuilds) {
474+
if (
475+
this.rushConfiguration.rushConfigurationJson.pnpmVersion !== undefined &&
476+
semver.lt(this.rushConfiguration.rushConfigurationJson.pnpmVersion, '11.0.0')
477+
) {
478+
this._terminal.writeWarningLine(
479+
Colorize.yellow(
480+
`Your version of pnpm (${this.rushConfiguration.rushConfigurationJson.pnpmVersion}) ` +
481+
`doesn't support the "globalAllowBuilds" field in ` +
482+
`${this.rushConfiguration.commonRushConfigFolder}/${RushConstants.pnpmConfigFilename}. ` +
483+
'Remove this field or upgrade to pnpm 11.0.0 or newer.'
484+
)
485+
);
486+
}
487+
488+
workspaceFile.setAllowBuilds(pnpmOptions.globalAllowBuilds);
489+
}
490+
472491
// Save the generated workspace file. Don't update the file timestamp unless the content has changed,
473492
// since "rush install" will consider this timestamp
474493
workspaceFile.save(workspaceFile.workspaceFilename, { onlyIfChanged: true });

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ export interface IPnpmOptionsJson extends IPackageManagerOptionsJsonBase {
121121
*/
122122
globalPackageExtensions?: Record<string, IPnpmPackageExtension>;
123123
/**
124+
* {@inheritDoc PnpmOptionsConfiguration.globalAllowBuilds}
125+
*/
126+
globalAllowBuilds?: Record<string, boolean>;
127+
/**
128+
* @deprecated Use {@link IPnpmOptionsJson.globalAllowBuilds} instead.
124129
* {@inheritDoc PnpmOptionsConfiguration.globalNeverBuiltDependencies}
125130
*/
126131
globalNeverBuiltDependencies?: string[];
@@ -425,6 +430,23 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
425430
*/
426431
public readonly globalPackageExtensions: Record<string, IPnpmPackageExtension> | undefined;
427432

433+
/**
434+
* The `globalAllowBuilds` setting is a map of package names to booleans that controls which
435+
* dependencies are permitted to run build scripts (`preinstall`, `install`, `postinstall`
436+
* lifecycle events). A value of `true` explicitly permits a package to run build scripts;
437+
* a value of `false` explicitly blocks it. Packages not listed inherit the default behavior.
438+
*
439+
* This is the replacement for `globalNeverBuiltDependencies` and `globalOnlyBuiltDependencies`,
440+
* and is the only way to control build permissions in pnpm 11+. The settings are written to the
441+
* `allowBuilds` field of the `pnpm-workspace.yaml` file that is generated by Rush during
442+
* installation.
443+
*
444+
* (SUPPORTED ONLY IN PNPM 11.0.0 AND NEWER)
445+
*
446+
* PNPM documentation: https://pnpm.io/settings#allowbuilds
447+
*/
448+
public readonly globalAllowBuilds: Record<string, boolean> | undefined;
449+
428450
/**
429451
* The `globalNeverBuiltDependencies` setting suppresses the `preinstall`, `install`, and `postinstall`
430452
* lifecycle events for the specified NPM dependencies. This is useful for scripts with poor practices
@@ -434,6 +456,8 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
434456
* The settings are copied into the `pnpm.neverBuiltDependencies` field of the `common/temp/package.json`
435457
* file that is generated by Rush during installation.
436458
*
459+
* @deprecated Use {@link PnpmOptionsConfiguration.globalAllowBuilds} instead.
460+
*
437461
* PNPM documentation: https://pnpm.io/package_json#pnpmneverbuiltdependencies
438462
*/
439463
public readonly globalNeverBuiltDependencies: string[] | undefined;
@@ -554,6 +578,14 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
554578
this.globalOverrides = json.globalOverrides;
555579
this.globalPeerDependencyRules = json.globalPeerDependencyRules;
556580
this.globalPackageExtensions = json.globalPackageExtensions;
581+
582+
if (json.globalNeverBuiltDependencies !== undefined && json.globalAllowBuilds !== undefined) {
583+
throw new Error(
584+
'The "globalNeverBuiltDependencies" setting is deprecated. Use "globalAllowBuilds" instead.' +
585+
' Both settings cannot be specified together in pnpm-config.json.'
586+
);
587+
}
588+
this.globalAllowBuilds = json.globalAllowBuilds;
557589
this.globalNeverBuiltDependencies = json.globalNeverBuiltDependencies;
558590
this.globalOnlyBuiltDependencies = json.globalOnlyBuiltDependencies;
559591
this.globalIgnoredOptionalDependencies = json.globalIgnoredOptionalDependencies;

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ interface IPnpmWorkspaceYaml {
3131
packages: string[];
3232
/** Catalog definitions for centralized version management */
3333
catalogs?: Record<string, Record<string, string>>;
34+
/** Per-package build permission map. True permits build scripts, false blocks them. (pnpm 11+) */
35+
allowBuilds?: Record<string, boolean>;
3436
}
3537

3638
export class PnpmWorkspaceFile extends BaseWorkspaceFile {
@@ -41,6 +43,7 @@ export class PnpmWorkspaceFile extends BaseWorkspaceFile {
4143

4244
private _workspacePackages: Set<string>;
4345
private _catalogs: Record<string, Record<string, string>> | undefined;
46+
private _allowBuilds: Record<string, boolean> | undefined;
4447

4548
/**
4649
* The PNPM workspace file is used to specify the location of workspaces relative to the root
@@ -54,6 +57,7 @@ export class PnpmWorkspaceFile extends BaseWorkspaceFile {
5457
// If we need to support manual customization, that should be an additional parameter for "base file"
5558
this._workspacePackages = new Set<string>();
5659
this._catalogs = undefined;
60+
this._allowBuilds = undefined;
5761
}
5862

5963
/**
@@ -64,6 +68,19 @@ export class PnpmWorkspaceFile extends BaseWorkspaceFile {
6468
this._catalogs = catalogs;
6569
}
6670

71+
/**
72+
* Sets the `allowBuilds` map for the workspace. Each key is a package name and each value
73+
* is `true` (permit build scripts) or `false` (block build scripts).
74+
*
75+
* @remarks
76+
* This writes to the `allowBuilds` field in `pnpm-workspace.yaml`, which requires pnpm 11.0.0+.
77+
*
78+
* @param allowBuilds - A map of package name to boolean permission flag
79+
*/
80+
public setAllowBuilds(allowBuilds: Record<string, boolean> | undefined): void {
81+
this._allowBuilds = allowBuilds;
82+
}
83+
6784
/** @override */
6885
public addPackage(packagePath: string): void {
6986
// Ensure the path is relative to the pnpm-workspace.yaml file
@@ -89,6 +106,10 @@ export class PnpmWorkspaceFile extends BaseWorkspaceFile {
89106
workspaceYaml.catalogs = this._catalogs;
90107
}
91108

109+
if (this._allowBuilds && Object.keys(this._allowBuilds).length > 0) {
110+
workspaceYaml.allowBuilds = this._allowBuilds;
111+
}
112+
92113
return yamlModule.dump(workspaceYaml, PNPM_SHRINKWRAP_YAML_FORMAT);
93114
}
94115
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,29 @@ describe(PnpmOptionsConfiguration.name, () => {
8787
]);
8888
});
8989

90+
it('loads allowBuilds', () => {
91+
const pnpmConfiguration: PnpmOptionsConfiguration = PnpmOptionsConfiguration.loadFromJsonFileOrThrow(
92+
`${__dirname}/jsonFiles/pnpm-config-allowBuilds.json`,
93+
fakeCommonTempFolder
94+
);
95+
96+
expect(TestUtilities.stripAnnotations(pnpmConfiguration.globalAllowBuilds)).toEqual({
97+
esbuild: true,
98+
playwright: true,
99+
'core-js': false,
100+
'@swc/core': true
101+
});
102+
});
103+
104+
it('throws if both globalNeverBuiltDependencies and globalAllowBuilds are specified', () => {
105+
expect(() =>
106+
PnpmOptionsConfiguration.loadFromJsonFileOrThrow(
107+
`${__dirname}/jsonFiles/pnpm-config-allowBuilds-conflict.json`,
108+
fakeCommonTempFolder
109+
)
110+
).toThrow(/Both settings cannot be specified together/);
111+
});
112+
90113
it('loads minimumReleaseAgeMinutes', () => {
91114
const pnpmConfiguration: PnpmOptionsConfiguration = PnpmOptionsConfiguration.loadFromJsonFileOrThrow(
92115
`${__dirname}/jsonFiles/pnpm-config-minimumReleaseAge.json`,

libraries/rush-lib/src/logic/pnpm/test/PnpmWorkspaceFile.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,67 @@ describe(PnpmWorkspaceFile.name, () => {
180180
expect(content).toMatchSnapshot();
181181
});
182182
});
183+
184+
describe('allowBuilds functionality', () => {
185+
it('generates workspace file with allowBuilds', () => {
186+
const workspaceFile: PnpmWorkspaceFile = new PnpmWorkspaceFile(workspaceFilePath);
187+
workspaceFile.addPackage(path.join(projectsDir, 'app1'));
188+
189+
workspaceFile.setAllowBuilds({
190+
esbuild: true,
191+
playwright: true,
192+
'core-js': false
193+
});
194+
195+
workspaceFile.save(workspaceFilePath, { onlyIfChanged: true });
196+
197+
const content: string = FileSystem.readFile(workspaceFilePath);
198+
expect(content).toMatchSnapshot();
199+
});
200+
201+
it('handles empty allowBuilds object', () => {
202+
const workspaceFile: PnpmWorkspaceFile = new PnpmWorkspaceFile(workspaceFilePath);
203+
workspaceFile.addPackage(path.join(projectsDir, 'app1'));
204+
205+
workspaceFile.setAllowBuilds({});
206+
207+
workspaceFile.save(workspaceFilePath, { onlyIfChanged: true });
208+
209+
const content: string = FileSystem.readFile(workspaceFilePath);
210+
expect(content).toMatchSnapshot();
211+
});
212+
213+
it('handles undefined allowBuilds', () => {
214+
const workspaceFile: PnpmWorkspaceFile = new PnpmWorkspaceFile(workspaceFilePath);
215+
workspaceFile.addPackage(path.join(projectsDir, 'app1'));
216+
217+
workspaceFile.setAllowBuilds(undefined);
218+
219+
workspaceFile.save(workspaceFilePath, { onlyIfChanged: true });
220+
221+
const content: string = FileSystem.readFile(workspaceFilePath);
222+
expect(content).toMatchSnapshot();
223+
});
224+
225+
it('generates workspace file with both catalogs and allowBuilds', () => {
226+
const workspaceFile: PnpmWorkspaceFile = new PnpmWorkspaceFile(workspaceFilePath);
227+
workspaceFile.addPackage(path.join(projectsDir, 'app1'));
228+
229+
workspaceFile.setCatalogs({
230+
default: {
231+
react: '^18.0.0'
232+
}
233+
});
234+
235+
workspaceFile.setAllowBuilds({
236+
esbuild: true,
237+
'core-js': false
238+
});
239+
240+
workspaceFile.save(workspaceFilePath, { onlyIfChanged: true });
241+
242+
const content: string = FileSystem.readFile(workspaceFilePath);
243+
expect(content).toMatchSnapshot();
244+
});
245+
});
183246
});

libraries/rush-lib/src/logic/pnpm/test/__snapshots__/PnpmWorkspaceFile.test.ts.snap

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,39 @@ exports[`PnpmWorkspaceFile catalog functionality handles undefined catalog 1`] =
6565
- projects/app1
6666
"
6767
`;
68+
69+
exports[`PnpmWorkspaceFile allowBuilds functionality generates workspace file with allowBuilds 1`] = `
70+
"allowBuilds:
71+
core-js: false
72+
esbuild: true
73+
playwright: true
74+
packages:
75+
- projects/app1
76+
"
77+
`;
78+
79+
exports[`PnpmWorkspaceFile allowBuilds functionality generates workspace file with both catalogs and allowBuilds 1`] = `
80+
"allowBuilds:
81+
core-js: false
82+
esbuild: true
83+
catalogs:
84+
default:
85+
react: ^18.0.0
86+
packages:
87+
- projects/app1
88+
"
89+
`;
90+
91+
exports[`PnpmWorkspaceFile allowBuilds functionality handles empty allowBuilds object 1`] = `
92+
"packages:
93+
- projects/app1
94+
"
95+
`;
96+
97+
exports[`PnpmWorkspaceFile allowBuilds functionality handles undefined allowBuilds 1`] = `
98+
"packages:
99+
- projects/app1
100+
"
101+
`;
102+
103+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"globalNeverBuiltDependencies": ["fsevents"],
3+
"globalAllowBuilds": {
4+
"esbuild": true
5+
}
6+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"globalAllowBuilds": {
3+
"esbuild": true,
4+
"playwright": true,
5+
"core-js": false,
6+
"@swc/core": true
7+
}
8+
}

0 commit comments

Comments
 (0)