Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .changeset/cold-weeks-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
"@pnpm/workspace.manifest-writer": minor
"@pnpm/types": minor
"@pnpm/config": minor
"pnpm": minor
---

Added support for `allowBuilds`, which is a new field that can be used instead of `onlyBuiltDependencies` and `ignoredBuiltDependencies`. The new `allowBuilds` field in your `pnpm-workspace.yaml` uses a map of package matchers to explicitly allow (`true`) or disallow (`false`) script execution. This allows for a single, easy-to-manage source of truth for your build permissions.

**Example Usage.** To explicitly allow all versions of `esbuild` to run scripts and prevent `core-js` from running them:

```yaml
allowBuilds:
esbuild: true
core-js: false
```

The example above achieves the same result as the previous configuration:

```yaml
onlyBuiltDependencies:
- esbuild
ignoredBuiltDependencies:
- core-js
```

Related PR: [#10311](https://github.com/pnpm/pnpm/pull/10311)
1 change: 1 addition & 0 deletions config/config/src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export interface Config extends OptionsFromRootManifest {
gitShallowHosts?: string[]
legacyDirFiltering?: boolean
onlyBuiltDependencies?: string[]
allowBuilds?: Record<string, boolean | string>
dedupePeerDependents?: boolean
patchesDir?: string
ignoreWorkspaceCycles?: boolean
Expand Down
1 change: 1 addition & 0 deletions config/config/src/dependencyBuildOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const DEPS_BUILD_CONFIG_KEYS = [
'onlyBuiltDependencies',
'onlyBuiltDependenciesFile',
'neverBuiltDependencies',
'allowBuilds',
] as const satisfies Array<keyof Config>

export type DepsBuildConfigKey = typeof DEPS_BUILD_CONFIG_KEYS[number]
Expand Down
20 changes: 19 additions & 1 deletion config/config/src/getOptionsFromRootManifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ export type OptionsFromRootManifest = {
patchedDependencies?: Record<string, string>
peerDependencyRules?: PeerDependencyRules
supportedArchitectures?: SupportedArchitectures
allowBuilds?: Record<string, boolean | string>
} & Pick<PnpmSettings, 'configDependencies' | 'auditConfig' | 'executionEnv' | 'updateConfig'>

export function getOptionsFromRootManifest (manifestDir: string, manifest: ProjectManifest): OptionsFromRootManifest {
const settings: OptionsFromRootManifest = getOptionsFromPnpmSettings(manifestDir, {
...pick([
'allowNonAppliedPatches',
'allowBuilds',
'allowUnusedPatches',
'allowedDeprecatedVersions',
'auditConfig',
Expand Down Expand Up @@ -65,7 +67,7 @@ export function getOptionsFromRootManifest (manifestDir: string, manifest: Proje
}

export function getOptionsFromPnpmSettings (manifestDir: string | undefined, pnpmSettings: PnpmSettings, manifest?: ProjectManifest): OptionsFromRootManifest {
const renamedKeys = ['allowNonAppliedPatches'] as const satisfies Array<keyof PnpmSettings>
const renamedKeys = ['allowNonAppliedPatches', 'allowBuilds'] as const satisfies Array<keyof PnpmSettings>
const settings: OptionsFromRootManifest = omit(renamedKeys, replaceEnvInSettings(pnpmSettings))
if (settings.overrides) {
if (Object.keys(settings.overrides).length === 0) {
Expand All @@ -91,6 +93,22 @@ export function getOptionsFromPnpmSettings (manifestDir: string | undefined, pnp
if (pnpmSettings.ignorePatchFailures != null) {
settings.ignorePatchFailures = pnpmSettings.ignorePatchFailures
}

if (pnpmSettings.allowBuilds) {
settings.onlyBuiltDependencies ??= []
settings.ignoredBuiltDependencies ??= []
for (const [packagePattern, build] of Object.entries(pnpmSettings.allowBuilds)) {
switch (build) {
case true:
settings.onlyBuiltDependencies.push(packagePattern)
break
case false:
settings.ignoredBuiltDependencies.push(packagePattern)
break
}
}
}

return settings
}

Expand Down
16 changes: 16 additions & 0 deletions config/config/test/getOptionsFromRootManifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,19 @@ test('getOptionsFromPnpmSettings() replaces env variables in settings', () => {
} as any) as any // eslint-disable-line
expect(options.foo).toBe('bar')
})

test('getOptionsFromRootManifest() converts allowBuilds', () => {
const options = getOptionsFromRootManifest(process.cwd(), {
pnpm: {
allowBuilds: {
foo: true,
bar: false,
qar: 'warn',
},
},
})
expect(options).toStrictEqual({
onlyBuiltDependencies: ['foo'],
ignoredBuiltDependencies: ['bar'],
})
})
7 changes: 4 additions & 3 deletions packages/types/src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,10 @@ export interface AuditConfig {

export interface PnpmSettings {
configDependencies?: ConfigDependencies
neverBuiltDependencies?: string[]
onlyBuiltDependencies?: string[]
onlyBuiltDependenciesFile?: string
neverBuiltDependencies?: string[] // deprecated
onlyBuiltDependencies?: string[] // deprecated
onlyBuiltDependenciesFile?: string // deprecated
allowBuilds?: Record<string, boolean | string>
ignoredBuiltDependencies?: string[]
overrides?: Record<string, string>
packageExtensions?: Record<string, PackageExtension>
Expand Down
27 changes: 26 additions & 1 deletion workspace/manifest-writer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,32 @@ export async function updateWorkspaceManifest (dir: string, opts: {
shouldBeUpdated = removePackagesFromWorkspaceCatalog(manifest, opts.allProjects ?? []) || shouldBeUpdated
}

for (const [key, value] of Object.entries(opts.updatedFields ?? {})) {
// If the current manifest has allowBuilds, convert old fields to allowBuilds format
const updatedFields = { ...opts.updatedFields }
if (manifest.allowBuilds != null && (updatedFields.onlyBuiltDependencies != null || updatedFields.ignoredBuiltDependencies != null)) {
const allowBuilds: Record<string, boolean | string> = { ...manifest.allowBuilds }

// Convert onlyBuiltDependencies to allowBuilds with true values
if (updatedFields.onlyBuiltDependencies != null) {
for (const pattern of updatedFields.onlyBuiltDependencies) {
allowBuilds[pattern] = true
}
}

// Convert ignoredBuiltDependencies to allowBuilds with false values
if (updatedFields.ignoredBuiltDependencies != null) {
for (const pattern of updatedFields.ignoredBuiltDependencies) {
allowBuilds[pattern] = false
}
}

// Update allowBuilds instead of the old fields
updatedFields.allowBuilds = allowBuilds
delete updatedFields.onlyBuiltDependencies
delete updatedFields.ignoredBuiltDependencies
}

for (const [key, value] of Object.entries(updatedFields)) {
if (!equals(manifest[key as keyof WorkspaceManifest], value)) {
shouldBeUpdated = true
if (value == null) {
Expand Down
17 changes: 17 additions & 0 deletions workspace/manifest-writer/test/updateWorkspaceManifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,20 @@ test('updateWorkspaceManifest updates an existing setting', async () => {
overrides: { bar: '3' },
})
})

test('updateWorkspaceManifest updates allowBuilds', async () => {
const dir = tempDir(false)
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
writeYamlFile(filePath, { packages: ['*'], allowBuilds: { qar: 'warn' } })
await updateWorkspaceManifest(dir, {
updatedFields: { onlyBuiltDependencies: ['foo'], ignoredBuiltDependencies: ['bar'] },
})
expect(readYamlFile(filePath)).toStrictEqual({
packages: ['*'],
allowBuilds: {
bar: false,
foo: true,
qar: 'warn',
},
})
})
Loading