fix: avoid updating allowBuilds when workspace is ignored (#12488)

pnpm install --ignore-workspace auto-populated ignored builds into the
allowBuilds map of pnpm-workspace.yaml, overwriting committed true/false
values with the "set this to true or false" placeholder — even under
--frozen-lockfile, which must stay read-only.

The ignore-workspace CLI flag is now a first-class Config field
(ignoreWorkspace, mirroring ignoreWorkspaceCycles) instead of being read
from the untyped cliOptions bag. handleIgnoredBuilds reads it directly and
skips writing to allowBuilds when the workspace is ignored. The strict
ignored-build failure is unchanged.

Closes pnpm/pnpm#12469

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
Nightt
2026-06-20 18:39:15 +08:00
committed by GitHub
parent 6c35a432be
commit 6545793845
6 changed files with 66 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/installing.commands": patch
"pnpm": patch
---
Fixed `pnpm install --ignore-workspace` overwriting the `allowBuilds` map in `pnpm-workspace.yaml`. The ignored builds of a package with a build script were auto-populated into `allowBuilds` even though `--ignore-workspace` was passed, clobbering committed `true`/`false` values with the `set this to true or false` placeholder [#12469](https://github.com/pnpm/pnpm/issues/12469).

View File

@@ -232,6 +232,7 @@ export interface Config extends OptionsFromRootManifest {
dedupePeerDependents?: boolean
dedupePeers?: boolean
patchesDir?: string
ignoreWorkspace?: boolean
ignoreWorkspaceCycles?: boolean
disallowWorkspaceCycles?: boolean
packGzipLevel?: number

View File

@@ -8,6 +8,7 @@ import { lexCompare } from '@pnpm/util.lex-comparator'
export interface HandleIgnoredBuildsOpts {
allowBuilds?: Record<string, boolean | string>
ignoreWorkspace?: boolean
rootProjectManifestDir?: string
workspaceDir?: string
strictDepBuilds?: boolean
@@ -18,7 +19,9 @@ export async function handleIgnoredBuilds (
ignoredBuilds: IgnoredBuilds | undefined
): Promise<void> {
if (!ignoredBuilds?.size) return
await writeIgnoredBuildsToAllowBuilds(opts, ignoredBuilds)
if (!opts.ignoreWorkspace) {
await writeIgnoredBuildsToAllowBuilds(opts, ignoredBuilds)
}
if (opts.strictDepBuilds) {
throw new IgnoredBuildsError(ignoredBuilds)
}

View File

@@ -117,6 +117,7 @@ export type InstallDepsOptions = Pick<Config,
| 'workspaceDir'
| 'workspacePackagePatterns'
| 'extraEnv'
| 'ignoreWorkspace'
| 'ignoreWorkspaceCycles'
| 'disallowWorkspaceCycles'
| 'configDependencies'

View File

@@ -0,0 +1,31 @@
import fs from 'node:fs'
import path from 'node:path'
import { expect, test } from '@jest/globals'
import { prepareEmpty } from '@pnpm/prepare'
import type { DepPath } from '@pnpm/types'
import { readYamlFileSync } from 'read-yaml-file'
import { writeYamlFileSync } from 'write-yaml-file'
import { handleIgnoredBuilds } from '../lib/handleIgnoredBuilds.js'
test('handleIgnoredBuilds does not update pnpm-workspace.yaml when workspace is ignored', async () => {
prepareEmpty()
const workspaceManifestFile = path.resolve('pnpm-workspace.yaml')
const workspaceManifest = {
allowBuilds: {
esbuild: false,
},
}
writeYamlFileSync(workspaceManifestFile, workspaceManifest)
const workspaceManifestBefore = fs.readFileSync(workspaceManifestFile, 'utf8')
await handleIgnoredBuilds({
ignoreWorkspace: true,
rootProjectManifestDir: process.cwd(),
}, new Set(['esbuild@0.25.0' as DepPath]))
expect(fs.readFileSync(workspaceManifestFile, 'utf8')).toBe(workspaceManifestBefore)
expect(readYamlFileSync(workspaceManifestFile)).toStrictEqual(workspaceManifest)
})

View File

@@ -333,6 +333,29 @@ test('auto-populated placeholders are merged with existing allowBuilds', async (
expect(manifest?.allowBuilds?.['@pnpm.e2e/pre-and-postinstall-scripts-example']).toBe('set this to true or false')
})
test('install --ignore-workspace does not overwrite allowBuilds in pnpm-workspace.yaml', () => {
prepare({
dependencies: {
'@pnpm.e2e/pre-and-postinstall-scripts-example': '1.0.0',
},
})
writeYamlFileSync('pnpm-workspace.yaml', {
allowBuilds: {
'@pnpm.e2e/pre-and-postinstall-scripts-example': false,
},
})
const manifestBefore = fs.readFileSync('pnpm-workspace.yaml', 'utf8')
const { status, stdout, stderr } = execPnpmSync(['install', '--ignore-workspace'])
// The build is ignored (--ignore-workspace skips the allowBuilds entry), so the
// install ends in ERR_PNPM_IGNORED_BUILDS — the same code path that would have
// written the placeholder. The manifest must stay untouched regardless.
expect(status).toBe(1)
expect(`${stdout}${stderr}`).toContain('ERR_PNPM_IGNORED_BUILDS')
expect(fs.readFileSync('pnpm-workspace.yaml', 'utf8')).toBe(manifestBefore)
})
test('selective rebuild preserves ignoredBuilds for packages not being rebuilt', async () => {
const project = prepare({})
writeYamlFileSync('pnpm-workspace.yaml', {