feat!: deprecate old settings that were replaced by allowBuilds (#10382)

This commit is contained in:
Zoltan Kochan
2026-01-02 12:22:42 +01:00
committed by GitHub
parent b5751aeac8
commit cb367b9515
47 changed files with 304 additions and 537 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/plugin-commands-deploy": minor
---
Preserve `allowBuilds` settings when deploying a project. The `allowBuilds` configuration is now written to `pnpm-workspace.yaml` in the deploy directory.

View File

@@ -0,0 +1,6 @@
---
"@pnpm/config": minor
"pnpm": minor
---
Support reading `allowBuilds` from `pnpm-workspace.yaml` in the global package directory for global installs.

View File

@@ -0,0 +1,30 @@
---
"pnpm": major
---
Remove deprecated build dependency settings: `onlyBuiltDependencies`, `onlyBuiltDependenciesFile`, `neverBuiltDependencies`, and `ignoredBuiltDependencies`.
Use the `allowBuilds` setting instead. It is a map where keys are package name patterns and values are booleans:
- `true` means the package is allowed to run build scripts
- `false` means the package is explicitly denied from running build scripts
Same as before, by default, none of the packages in the dependencies are allowed to run scripts. If a package has postinstall scripts and it isn't declared in `allowBuilds`, an error is printed.
Before:
```yaml
onlyBuiltDependencies:
- electron
onlyBuiltDependenciesFile: 'allowed-builds.json'
neverBuiltDependencies:
- core-js
ignoredBuiltDependencies:
- esbuild
```
After:
```yaml
allowBuilds:
electron: true
core-js: false
esbuild: false
```

View File

@@ -0,0 +1,13 @@
---
"@pnpm/types": major
"@pnpm/config": major
"@pnpm/core": major
"@pnpm/headless": major
"@pnpm/builder.policy": major
"@pnpm/exec.build-commands": major
"@pnpm/plugin-commands-installation": major
"@pnpm/plugin-commands-rebuild": major
"@pnpm/workspace.manifest-writer": major
---
Remove deprecated build dependency settings: `onlyBuiltDependencies`, `onlyBuiltDependenciesFile`, `neverBuiltDependencies`, and `ignoredBuiltDependencies`.

View File

@@ -1,27 +1,16 @@
import { type AllowBuild } from '@pnpm/types'
import { expandPackageVersionSpecs } from '@pnpm/config.version-policy'
import fs from 'fs'
export function createAllowBuildFunction (
opts: {
dangerouslyAllowAllBuilds?: boolean
neverBuiltDependencies?: string[]
onlyBuiltDependencies?: string[]
onlyBuiltDependenciesFile?: string
}
): undefined | AllowBuild {
if (opts.dangerouslyAllowAllBuilds) return () => true
if (opts.onlyBuiltDependenciesFile != null || opts.onlyBuiltDependencies != null) {
const onlyBuiltDeps = opts.onlyBuiltDependencies ?? []
if (opts.onlyBuiltDependenciesFile) {
onlyBuiltDeps.push(...JSON.parse(fs.readFileSync(opts.onlyBuiltDependenciesFile, 'utf8')))
}
const onlyBuiltDependencies = expandPackageVersionSpecs(onlyBuiltDeps)
if (opts.onlyBuiltDependencies != null) {
const onlyBuiltDependencies = expandPackageVersionSpecs(opts.onlyBuiltDependencies)
return (pkgName, version) => onlyBuiltDependencies.has(pkgName) || onlyBuiltDependencies.has(`${pkgName}@${version}`)
}
if (opts.neverBuiltDependencies != null && opts.neverBuiltDependencies.length > 0) {
const neverBuiltDependencies = new Set(opts.neverBuiltDependencies)
return (pkgName) => !neverBuiltDependencies.has(pkgName)
}
return undefined
}

View File

@@ -1,15 +1,5 @@
import path from 'path'
import { createAllowBuildFunction } from '@pnpm/builder.policy'
it('should neverBuiltDependencies', () => {
const allowBuild = createAllowBuildFunction({
neverBuiltDependencies: ['foo'],
})
expect(typeof allowBuild).toBe('function')
expect(allowBuild!('foo', '1.0.0')).toBeFalsy()
expect(allowBuild!('bar', '1.0.0')).toBeTruthy()
})
it('should onlyBuiltDependencies', () => {
const allowBuild = createAllowBuildFunction({
onlyBuiltDependencies: ['foo', 'qar@1.0.0 || 2.0.0'],
@@ -30,28 +20,6 @@ it('should not allow patterns in onlyBuiltDependencies', () => {
expect(allowBuild!('is-odd', '1.0.0')).toBeFalsy()
})
it('should onlyBuiltDependencies set via a file', () => {
const allowBuild = createAllowBuildFunction({
onlyBuiltDependenciesFile: path.join(import.meta.dirname, 'onlyBuild.json'),
})
expect(typeof allowBuild).toBe('function')
expect(allowBuild!('zoo', '1.0.0')).toBeTruthy()
expect(allowBuild!('qar', '1.0.0')).toBeTruthy()
expect(allowBuild!('bar', '1.0.0')).toBeFalsy()
})
it('should onlyBuiltDependencies set via a file and config', () => {
const allowBuild = createAllowBuildFunction({
onlyBuiltDependencies: ['bar'],
onlyBuiltDependenciesFile: path.join(import.meta.dirname, 'onlyBuild.json'),
})
expect(typeof allowBuild).toBe('function')
expect(allowBuild!('zoo', '1.0.0')).toBeTruthy()
expect(allowBuild!('qar', '1.0.0')).toBeTruthy()
expect(allowBuild!('bar', '1.0.0')).toBeTruthy()
expect(allowBuild!('esbuild', '1.0.0')).toBeFalsy()
})
it('should return undefined if no policy is set', () => {
expect(createAllowBuildFunction({})).toBeUndefined()
})

View File

@@ -3,8 +3,6 @@ import { type Config } from './Config.js'
export const DEPS_BUILD_CONFIG_KEYS = [
'dangerouslyAllowAllBuilds',
'onlyBuiltDependencies',
'onlyBuiltDependenciesFile',
'neverBuiltDependencies',
'allowBuilds',
] as const satisfies Array<keyof Config>

View File

@@ -17,9 +17,7 @@ export type OptionsFromRootManifest = {
allowUnusedPatches?: boolean
ignorePatchFailures?: boolean
overrides?: Record<string, string>
neverBuiltDependencies?: string[]
onlyBuiltDependencies?: string[]
onlyBuiltDependenciesFile?: string
ignoredBuiltDependencies?: string[]
packageExtensions?: Record<string, PackageExtension>
ignoredOptionalDependencies?: string[]
@@ -37,15 +35,9 @@ export function getOptionsFromRootManifest (manifestDir: string, manifest: Proje
'allowUnusedPatches',
'allowedDeprecatedVersions',
'auditConfig',
'auditConfig',
'auditConfig',
'configDependencies',
'ignorePatchFailures',
'ignoredBuiltDependencies',
'ignoredOptionalDependencies',
'neverBuiltDependencies',
'onlyBuiltDependencies',
'onlyBuiltDependenciesFile',
'overrides',
'packageExtensions',
'patchedDependencies',
@@ -74,9 +66,6 @@ export function getOptionsFromPnpmSettings (manifestDir: string | undefined, pnp
settings.overrides = mapValues(createVersionReferencesReplacer(manifest), settings.overrides)
}
}
if (pnpmSettings.onlyBuiltDependenciesFile && manifestDir != null) {
settings.onlyBuiltDependenciesFile = path.join(manifestDir, pnpmSettings.onlyBuiltDependenciesFile)
}
if (pnpmSettings.patchedDependencies) {
settings.patchedDependencies = { ...pnpmSettings.patchedDependencies }
for (const [dep, patchFile] of Object.entries(pnpmSettings.patchedDependencies)) {
@@ -93,6 +82,7 @@ export function getOptionsFromPnpmSettings (manifestDir: string | undefined, pnp
}
if (pnpmSettings.allowBuilds) {
settings.allowBuilds = pnpmSettings.allowBuilds
settings.onlyBuiltDependencies ??= []
settings.ignoredBuiltDependencies ??= []
for (const [packagePattern, build] of Object.entries(pnpmSettings.allowBuilds)) {

View File

@@ -412,6 +412,17 @@ export async function getConfig (opts: {
workspaceManifest,
})
}
} else if (cliOptions['global']) {
// For global installs, read settings from pnpm-workspace.yaml in the global package directory
const workspaceManifest = await readWorkspaceManifest(pnpmConfig.globalPkgDir)
if (workspaceManifest) {
addSettingsFromWorkspaceManifestToConfig(pnpmConfig, {
configFromCliOpts,
projectManifest: pnpmConfig.rootProjectManifest,
workspaceDir: pnpmConfig.globalPkgDir,
workspaceManifest,
})
}
}
}
@@ -459,13 +470,8 @@ export async function getConfig (opts: {
overrideSupportedArchitecturesWithCLI(pnpmConfig, cliOptions)
if (opts.cliOptions['global']) {
extractAndRemoveDependencyBuildOptions(pnpmConfig)
if (!hasDependencyBuildOptions(pnpmConfig)) {
Object.assign(pnpmConfig, globalDepsBuildConfig)
} else {
if (!hasDependencyBuildOptions(pnpmConfig)) {
Object.assign(pnpmConfig, globalDepsBuildConfig)
}
}
if (opts.cliOptions['save-peer']) {
if (opts.cliOptions['save-prod']) {
@@ -621,12 +627,6 @@ export async function getConfig (opts: {
pnpmConfig.dev = true
}
if (pnpmConfig.dangerouslyAllowAllBuilds) {
if (pnpmConfig.neverBuiltDependencies && pnpmConfig.neverBuiltDependencies.length > 0) {
warnings.push('You have set dangerouslyAllowAllBuilds to true. The dependencies listed in neverBuiltDependencies will run their scripts.')
}
pnpmConfig.neverBuiltDependencies = []
}
if (pnpmConfig.ci) {
// Using a global virtual store in CI makes little sense,
// as there is never a warm cache in that environment.

View File

@@ -93,7 +93,7 @@ test('getOptionsFromRootManifest() should return onlyBuiltDependencies as undefi
test('getOptionsFromRootManifest() should return the list from onlyBuiltDependencies', () => {
const options = getOptionsFromRootManifest(process.cwd(), {
pnpm: {
onlyBuiltDependencies: ['electron'],
allowBuilds: { electron: true },
},
})
expect(options.onlyBuiltDependencies).toStrictEqual(['electron'])
@@ -186,6 +186,11 @@ test('getOptionsFromRootManifest() converts allowBuilds', () => {
},
})
expect(options).toStrictEqual({
allowBuilds: {
foo: true,
bar: false,
qar: 'warn',
},
onlyBuiltDependencies: ['foo'],
ignoredBuiltDependencies: ['bar'],
})

View File

@@ -1281,38 +1281,6 @@ test('settings gitBranchLockfile in pnpm-workspace.yaml should take effect', asy
expect(config.rawConfig['git-branch-lockfile']).toBe(true)
})
test('when dangerouslyAllowAllBuilds is set to true neverBuiltDependencies is set to an empty array', async () => {
const { config } = await getConfig({
cliOptions: {
'dangerously-allow-all-builds': true,
},
packageManager: {
name: 'pnpm',
version: '1.0.0',
},
})
expect(config.neverBuiltDependencies).toStrictEqual([])
})
test('when dangerouslyAllowAllBuilds is set to true and neverBuiltDependencies not empty, a warning is returned', async () => {
const workspaceDir = f.find('never-built-dependencies')
process.chdir(workspaceDir)
const { config, warnings } = await getConfig({
cliOptions: {
'dangerously-allow-all-builds': true,
},
packageManager: {
name: 'pnpm',
version: '1.0.0',
},
workspaceDir,
})
expect(config.neverBuiltDependencies).toStrictEqual([])
expect(warnings).toStrictEqual(['You have set dangerouslyAllowAllBuilds to true. The dependencies listed in neverBuiltDependencies will run their scripts.'])
})
test('loads setting from environment variable pnpm_config_*', async () => {
prepareEmpty()
const { config } = await getConfig({

View File

@@ -51,6 +51,7 @@
"diskusage",
"dislink",
"dpkg",
"drivelist",
"duplexify",
"eagain",
"ebadplatform",

View File

@@ -2,7 +2,6 @@ import { type Config } from '@pnpm/config'
import { globalInfo } from '@pnpm/logger'
import { type StrictModules, writeModulesManifest } from '@pnpm/modules-yaml'
import { lexCompare } from '@pnpm/util.lex-comparator'
import { type PnpmSettings } from '@pnpm/types'
import renderHelp from 'render-help'
import enquirer from 'enquirer'
import chalk from 'chalk'
@@ -92,25 +91,19 @@ export async function handler (opts: ApproveBuildsCommandOpts & RebuildCommandOp
} as any) as any // eslint-disable-line @typescript-eslint/no-explicit-any
const buildPackages = result.map(({ value }: { value: string }) => value)
const ignoredPackages = automaticallyIgnoredBuilds.filter((automaticallyIgnoredBuild) => !buildPackages.includes(automaticallyIgnoredBuild))
const updatedSettings: PnpmSettings = {}
const allowBuilds: Record<string, boolean> = {}
if (ignoredPackages.length) {
if (opts.ignoredBuiltDependencies == null) {
updatedSettings.ignoredBuiltDependencies = sortUniqueStrings(ignoredPackages)
} else {
updatedSettings.ignoredBuiltDependencies = sortUniqueStrings([
...opts.ignoredBuiltDependencies,
...ignoredPackages,
])
for (const pkg of [...ignoredPackages, ...opts.ignoredBuiltDependencies ?? []]) {
allowBuilds[pkg] = false
}
}
if (buildPackages.length) {
if (opts.onlyBuiltDependencies == null) {
updatedSettings.onlyBuiltDependencies = sortUniqueStrings(buildPackages)
} else {
updatedSettings.onlyBuiltDependencies = sortUniqueStrings([
...opts.onlyBuiltDependencies,
...buildPackages,
])
const onlyBuiltDependencies = [
...opts.onlyBuiltDependencies ?? [],
...buildPackages,
]
if (onlyBuiltDependencies.length) {
for (const pkg of onlyBuiltDependencies) {
allowBuilds[pkg] = true
}
}
if (buildPackages.length) {
@@ -130,12 +123,12 @@ Do you approve?`,
await writeSettings({
...opts,
workspaceDir: opts.workspaceDir ?? opts.rootProjectManifestDir,
updatedSettings,
updatedSettings: { allowBuilds },
})
if (buildPackages.length) {
return rebuild.handler({
...opts,
onlyBuiltDependencies: updatedSettings.onlyBuiltDependencies,
onlyBuiltDependencies,
}, buildPackages)
} else if (modulesManifest) {
delete modulesManifest.ignoredBuilds

View File

@@ -2,7 +2,7 @@ import { type Config } from '@pnpm/config'
import renderHelp from 'render-help'
import { getAutomaticallyIgnoredBuilds } from './getAutomaticallyIgnoredBuilds.js'
export type IgnoredBuildsCommandOpts = Pick<Config, 'modulesDir' | 'dir' | 'rootProjectManifest' | 'lockfileDir'>
export type IgnoredBuildsCommandOpts = Pick<Config, 'modulesDir' | 'dir' | 'ignoredBuiltDependencies' | 'lockfileDir'>
export const commandNames = ['ignored-builds']
@@ -22,7 +22,7 @@ export function rcOptionsTypes (): Record<string, unknown> {
}
export async function handler (opts: IgnoredBuildsCommandOpts): Promise<string> {
const ignoredBuiltDependencies = opts.rootProjectManifest?.pnpm?.ignoredBuiltDependencies ?? []
const ignoredBuiltDependencies = opts.ignoredBuiltDependencies ?? []
let { automaticallyIgnoredBuilds } = await getAutomaticallyIgnoredBuilds(opts)
if (automaticallyIgnoredBuilds) {
automaticallyIgnoredBuilds = automaticallyIgnoredBuilds
@@ -35,12 +35,15 @@ export async function handler (opts: IgnoredBuildsCommandOpts): Promise<string>
output += ' None'
} else {
output += ` ${automaticallyIgnoredBuilds.join('\n ')}
hint: To allow the execution of build scripts for a package, add its name to "pnpm.onlyBuiltDependencies" in your "package.json", then run "pnpm rebuild".
hint: If you don't want to build a package, add it to the "pnpm.ignoredBuiltDependencies" list.`
hint: To allow the execution of build scripts for a package, add its name to "allowBuilds" and set to "true", then run "pnpm rebuild".
hint: For example:
hint: allowBuilds:
hint: esbuild: true
hint: If you don't want to build a package, set it to "false" instead.`
}
output += '\n'
if (ignoredBuiltDependencies.length) {
output += `\nExplicitly ignored package builds (via pnpm.ignoredBuiltDependencies):\n ${ignoredBuiltDependencies.join('\n ')}\n`
output += `\nExplicitly ignored package builds (via allowBuilds):\n ${ignoredBuiltDependencies.join('\n ')}\n`
}
return output
}

View File

@@ -1,10 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`ignoredBuilds lists automatically ignored dependencies 1`] = `
"Automatically ignored builds during installation:
foo
hint: To allow the execution of build scripts for a package, add its name to "pnpm.onlyBuiltDependencies" in your "package.json", then run "pnpm rebuild".
hint: If you don't want to build a package, add it to the "pnpm.ignoredBuiltDependencies" list.
hint: To allow the execution of build scripts for a package, add its name to "allowBuilds" and set to "true", then run "pnpm rebuild".
hint: For example:
hint: allowBuilds:
hint: esbuild: true
hint: If you don't want to build a package, set it to "false" instead.
"
`;
@@ -12,10 +15,13 @@ exports[`ignoredBuilds lists both automatically and explicitly ignored dependenc
"Automatically ignored builds during installation:
foo
bar
hint: To allow the execution of build scripts for a package, add its name to "pnpm.onlyBuiltDependencies" in your "package.json", then run "pnpm rebuild".
hint: If you don't want to build a package, add it to the "pnpm.ignoredBuiltDependencies" list.
hint: To allow the execution of build scripts for a package, add its name to "allowBuilds" and set to "true", then run "pnpm rebuild".
hint: For example:
hint: allowBuilds:
hint: esbuild: true
hint: If you don't want to build a package, set it to "false" instead.
Explicitly ignored package builds (via pnpm.ignoredBuiltDependencies):
Explicitly ignored package builds (via allowBuilds):
qar
zoo
"
@@ -25,7 +31,7 @@ exports[`ignoredBuilds lists explicitly ignored dependencies 1`] = `
"Automatically ignored builds during installation:
None
Explicitly ignored package builds (via pnpm.ignoredBuiltDependencies):
Explicitly ignored package builds (via allowBuilds):
bar
"
`;
@@ -34,7 +40,7 @@ exports[`ignoredBuilds prints an info message when there is no node_modules 1`]
"Automatically ignored builds during installation:
Cannot identify as no node_modules found
Explicitly ignored package builds (via pnpm.ignoredBuiltDependencies):
Explicitly ignored package builds (via allowBuilds):
qar
zoo
"

View File

@@ -4,12 +4,10 @@ import { install } from '@pnpm/plugin-commands-installation'
import { type ApproveBuildsCommandOpts } from '@pnpm/exec.build-commands'
import { type RebuildCommandOpts } from '@pnpm/plugin-commands-rebuild'
import { prepare } from '@pnpm/prepare'
import { type ProjectManifest } from '@pnpm/types'
import { getConfig } from '@pnpm/config'
import { readModulesManifest } from '@pnpm/modules-yaml'
import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
import { jest } from '@jest/globals'
import { loadJsonFileSync } from 'load-json-file'
import { omit } from 'ramda'
import { tempDir } from '@pnpm/prepare-temp-dir'
import { writePackageSync } from 'write-package'
@@ -88,16 +86,15 @@ test('approve selected build', async () => {
'@pnpm.e2e/pre-and-postinstall-scripts-example': '1.0.0',
'@pnpm.e2e/install-script-example': '*',
},
pnpm: {
overrides: {},
},
})
await approveSomeBuilds()
const manifest = loadJsonFileSync<ProjectManifest>(path.resolve('package.json'))
expect(manifest.pnpm?.onlyBuiltDependencies).toStrictEqual(['@pnpm.e2e/pre-and-postinstall-scripts-example'])
expect(manifest.pnpm?.ignoredBuiltDependencies).toStrictEqual(['@pnpm.e2e/install-script-example'])
const workspaceManifest = readYamlFile<any>(path.resolve('pnpm-workspace.yaml')) // eslint-disable-line
expect(workspaceManifest.allowBuilds).toStrictEqual({
'@pnpm.e2e/install-script-example': false,
'@pnpm.e2e/pre-and-postinstall-scripts-example': true,
})
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeTruthy()
@@ -155,62 +152,6 @@ test("works when root project manifest doesn't exist in a workspace", async () =
})
})
test('should update onlyBuiltDependencies when package.json exists with ignoredBuiltDependencies defined', async () => {
const temp = tempDir()
const rootProjectManifest = {
dependencies: {
'@pnpm.e2e/pre-and-postinstall-scripts-example': '1.0.0',
'@pnpm.e2e/install-script-example': '*',
},
pnpm: {
ignoredBuiltDependencies: ['@pnpm.e2e/install-script-example'],
},
}
prepare(rootProjectManifest, {
tempDir: temp,
})
const workspaceManifestFile = path.join(temp, 'pnpm-workspace.yaml')
writeYamlFile(workspaceManifestFile, { packages: ['packages/*'] })
await approveSomeBuilds({ workspaceDir: temp, rootProjectManifestDir: temp, rootProjectManifest })
expect(readYamlFile(workspaceManifestFile)).toStrictEqual({
packages: ['packages/*'],
})
expect(loadJsonFileSync<ProjectManifest>(path.join(temp, 'package.json'))!.pnpm).toStrictEqual({
ignoredBuiltDependencies: ['@pnpm.e2e/install-script-example'],
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
})
})
test('should approve builds when package.json exists with onlyBuiltDependencies defined', async () => {
const temp = tempDir()
prepare({
dependencies: {
'@pnpm.e2e/pre-and-postinstall-scripts-example': '1.0.0',
'@pnpm.e2e/install-script-example': '*',
},
pnpm: {
onlyBuiltDependencies: ['@pnpm.e2e/install-script-example'],
},
}, {
tempDir: temp,
})
const workspaceManifestFile = path.join(temp, 'pnpm-workspace.yaml')
writeYamlFile(workspaceManifestFile, { packages: ['packages/*'] })
await approveSomeBuilds({ workspaceDir: temp, rootProjectManifestDir: temp })
expect(readYamlFile(workspaceManifestFile)).toStrictEqual({
packages: ['packages/*'],
})
expect(loadJsonFileSync<ProjectManifest>(path.join(temp, 'package.json'))!.pnpm).toStrictEqual({
onlyBuiltDependencies: ['@pnpm.e2e/install-script-example', '@pnpm.e2e/pre-and-postinstall-scripts-example'],
})
})
test('should approve builds with package.json that has no onlyBuiltDependencies and ignoredBuiltDependencies fields defined', async () => {
const temp = tempDir()

View File

@@ -36,7 +36,7 @@ test('ignoredBuilds lists automatically ignored dependencies', async () => {
const output = await ignoredBuilds.handler({
dir,
modulesDir,
rootProjectManifest: {},
ignoredBuiltDependencies: [],
})
expect(output).toMatchSnapshot()
})
@@ -52,11 +52,7 @@ test('ignoredBuilds lists explicitly ignored dependencies', async () => {
const output = await ignoredBuilds.handler({
dir,
modulesDir,
rootProjectManifest: {
pnpm: {
ignoredBuiltDependencies: ['bar'],
},
},
ignoredBuiltDependencies: ['bar'],
})
expect(output).toMatchSnapshot()
})
@@ -72,11 +68,7 @@ test('ignoredBuilds lists both automatically and explicitly ignored dependencies
const output = await ignoredBuilds.handler({
dir,
modulesDir,
rootProjectManifest: {
pnpm: {
ignoredBuiltDependencies: ['qar', 'zoo'],
},
},
ignoredBuiltDependencies: ['qar', 'zoo'],
})
expect(output).toMatchSnapshot()
})
@@ -87,11 +79,7 @@ test('ignoredBuilds prints an info message when there is no node_modules', async
const output = await ignoredBuilds.handler({
dir,
modulesDir,
rootProjectManifest: {
pnpm: {
ignoredBuiltDependencies: ['qar', 'zoo'],
},
},
ignoredBuiltDependencies: ['qar', 'zoo'],
})
expect(output).toMatchSnapshot()
})

View File

@@ -51,7 +51,7 @@ export type StrictRebuildOptions = {
peersSuffixMaxLength: number
strictStorePkgContentCheck: boolean
fetchFullMetadata?: boolean
} & Pick<Config, 'sslConfigs' | 'onlyBuiltDependencies' | 'onlyBuiltDependenciesFile' | 'neverBuiltDependencies' | 'ignoredBuiltDependencies'>
} & Pick<Config, 'sslConfigs' | 'onlyBuiltDependencies' | 'ignoredBuiltDependencies'>
export type RebuildOptions = Partial<StrictRebuildOptions> &
Pick<StrictRebuildOptions, 'storeDir' | 'storeController'> & Pick<Config, 'rootProjectManifest' | 'rootProjectManifestDir'>
@@ -106,7 +106,7 @@ export async function extendRebuildOptions (
...(opts.rootProjectManifest ? getOptionsFromRootManifest(opts.rootProjectManifestDir, opts.rootProjectManifest) : {}),
}
extendedOpts.registries = normalizeRegistries(extendedOpts.registries)
if (extendedOpts.neverBuiltDependencies == null && extendedOpts.onlyBuiltDependencies == null && extendedOpts.onlyBuiltDependenciesFile == null) {
if (extendedOpts.onlyBuiltDependencies == null) {
extendedOpts.onlyBuiltDependencies = []
}
return extendedOpts

View File

@@ -50,6 +50,7 @@ test('rebuilds dependencies', async () => {
pending: false,
registries: modulesManifest!.registries!,
storeDir,
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example', 'test-git-fetch'],
}, [])
modules = project.readModulesManifest()
@@ -139,6 +140,7 @@ test('skipIfHasSideEffectsCache', async () => {
registries: modulesManifest!.registries!,
skipIfHasSideEffectsCache: true,
storeDir,
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
}, [])
modules = project.readModulesManifest()
@@ -176,6 +178,7 @@ test('rebuild does not fail when a linked package is present', async () => {
pending: false,
registries: modulesManifest!.registries!,
storeDir,
onlyBuiltDependencies: ['local-pkg', 'is-positive'],
}, [])
// see related issue https://github.com/pnpm/pnpm/issues/1155
@@ -206,6 +209,7 @@ test('rebuilds specific dependencies', async () => {
pending: false,
registries: modulesManifest!.registries!,
storeDir,
onlyBuiltDependencies: ['install-scripts-example-for-pnpm'],
}, ['install-scripts-example-for-pnpm'])
project.hasNot('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall')
@@ -265,6 +269,7 @@ test('rebuild with pending option', async () => {
pending: true,
registries: modules!.registries!,
storeDir,
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example', 'install-scripts-example-for-pnpm'],
}, [])
modules = project.readModulesManifest()
@@ -318,6 +323,7 @@ test('rebuild dependencies in correct order', async () => {
pending: false,
registries: modules!.registries!,
storeDir,
onlyBuiltDependencies: ['@pnpm.e2e/with-postinstall-a', '@pnpm.e2e/with-postinstall-b'],
}, [])
modules = project.readModulesManifest()
@@ -359,6 +365,7 @@ test('rebuild links bins', async () => {
pending: true,
registries: modules!.registries!,
storeDir,
onlyBuiltDependencies: ['@pnpm.e2e/has-generated-bins-as-dep', '@pnpm.e2e/generated-bins'],
}, [])
project.isExecutable('.bin/cmd1')
@@ -400,51 +407,6 @@ test(`rebuild should not fail on incomplete ${WANTED_LOCKFILE}`, async () => {
registries: modules!.registries!,
reporter,
storeDir,
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example', '@pnpm.e2e/not-compatible-with-any-os'],
}, [])
})
test('never build neverBuiltDependencies', async () => {
const project = prepare({
pnpm: {
neverBuiltDependencies: [],
},
})
const cacheDir = path.resolve('cache')
const storeDir = path.resolve('store')
await execa('node', [
pnpmBin,
'add',
'@pnpm.e2e/install-script-example@1.0.0',
'@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0',
`--registry=${REGISTRY}`,
`--store-dir=${storeDir}`,
`--cache-dir=${cacheDir}`,
'--config.enableGlobalVirtualStore=false',
])
const modulesManifest = project.readModulesManifest()
await rebuild.handler(
{
...DEFAULT_OPTS,
neverBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
cacheDir,
dir: process.cwd(),
pending: false,
registries: modulesManifest!.registries!,
storeDir,
},
[]
)
expect(
fs.existsSync(
'node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-prepare.js'
)
).toBeFalsy()
expect(
fs.existsSync(
'node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js'
)
).toBeTruthy()
})
})

View File

@@ -58,6 +58,7 @@ test('pnpm recursive rebuild', async () => {
registries: modulesManifest!.registries!,
selectedProjectsGraph,
workspaceDir: process.cwd(),
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
}, [])
projects['project-1'].has('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')
@@ -136,6 +137,7 @@ test('pnpm recursive rebuild with hoisted node linker', async () => {
selectedProjectsGraph,
lockfileDir: process.cwd(),
workspaceDir: process.cwd(),
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
}, [])
rootProject.has('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')
@@ -215,13 +217,14 @@ test('rebuild multiple packages in correct order', async () => {
recursive: true,
selectedProjectsGraph,
workspaceDir: process.cwd(),
onlyBuiltDependencies: ['project-1', 'project-2', 'project-3'],
}, [])
expect(server1.getLines()).toStrictEqual(['project-1', 'project-2'])
expect(server2.getLines()).toStrictEqual(['project-1', 'project-3'])
})
test('never build neverBuiltDependencies', async () => {
test('only build onlyBuiltDependencies (not others)', async () => {
const projects = preparePackages([
{
name: 'project-1',
@@ -285,7 +288,7 @@ test('never build neverBuiltDependencies', async () => {
await rebuild.handler(
{
...DEFAULT_OPTS,
neverBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
onlyBuiltDependencies: ['@pnpm.e2e/install-script-example'],
allProjects,
dir: process.cwd(),
recursive: true,

View File

@@ -28,7 +28,6 @@ export const DEFAULT_OPTS = {
localAddress: undefined,
lock: false,
lockStaleDuration: 90,
neverBuiltDependencies: [],
networkConcurrency: 16,
offline: false,
pending: false,

View File

@@ -151,11 +151,7 @@ export interface AuditConfig {
export interface PnpmSettings {
configDependencies?: ConfigDependencies
neverBuiltDependencies?: string[] // deprecated
onlyBuiltDependencies?: string[] // deprecated
onlyBuiltDependenciesFile?: string // deprecated
allowBuilds?: Record<string, boolean | string>
ignoredBuiltDependencies?: string[]
overrides?: Record<string, string>
packageExtensions?: Record<string, PackageExtension>
ignoredOptionalDependencies?: string[]

View File

@@ -72,9 +72,7 @@ export interface StrictInstallOptions {
verifyStoreIntegrity: boolean
engineStrict: boolean
ignoredBuiltDependencies?: string[]
neverBuiltDependencies?: string[]
onlyBuiltDependencies?: string[]
onlyBuiltDependenciesFile?: string
nodeExecPath?: string
nodeLinker: 'isolated' | 'hoisted' | 'pnp'
nodeVersion?: string
@@ -291,12 +289,9 @@ export function extendOptions (
}
}
}
if (opts.neverBuiltDependencies == null && opts.onlyBuiltDependencies == null && opts.onlyBuiltDependenciesFile == null) {
if (opts.onlyBuiltDependencies == null) {
opts.onlyBuiltDependencies = []
}
if (opts.onlyBuiltDependencies && opts.neverBuiltDependencies) {
throw new PnpmError('CONFIG_CONFLICT_BUILT_DEPENDENCIES', 'Cannot have both neverBuiltDependencies and onlyBuiltDependencies')
}
const defaultOpts = defaults(opts)
const extendedOpts: ProcessedInstallOptions = {
...defaultOpts,

View File

@@ -370,11 +370,9 @@ export async function mutateModules (
if (!opts.ignoreScripts && ignoredBuilds?.size) {
ignoredBuilds = await runUnignoredDependencyBuilds(opts, ignoredBuilds)
}
if (!opts.neverBuiltDependencies) {
ignoredScriptsLogger.debug({
packageNames: ignoredBuilds ? dedupePackageNamesFromIgnoredBuilds(ignoredBuilds) : [],
})
}
ignoredScriptsLogger.debug({
packageNames: ignoredBuilds ? dedupePackageNamesFromIgnoredBuilds(ignoredBuilds) : [],
})
if ((reporter != null) && typeof reporter === 'function') {
streamParser.removeListener('data', reporter)
@@ -1085,7 +1083,6 @@ type InstallFunction = (
patchedDependencies?: PatchGroupRecord
makePartialCurrentLockfile: boolean
needsFullResolution: boolean
neverBuiltDependencies?: string[]
onlyBuiltDependencies?: string[]
overrides?: Record<string, string>
updateLockfileMinorVersion: boolean

View File

@@ -184,7 +184,12 @@ test('run pre/postinstall scripts. bin files should be linked in a hoisted node_
const project = prepareEmpty()
await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example'],
testDefaults({ fastUnpack: false, nodeLinker: 'hoisted', targetDependenciesField: 'devDependencies' })
testDefaults({
fastUnpack: false,
nodeLinker: 'hoisted',
targetDependenciesField: 'devDependencies',
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
})
)
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-prepare.js')).toBeFalsy()
@@ -212,7 +217,7 @@ test('running install scripts in a workspace that has no root project', async ()
},
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
}, testDefaults({ fastUnpack: false, nodeLinker: 'hoisted' }))
}, testDefaults({ fastUnpack: false, nodeLinker: 'hoisted', onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'] }))
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
})

View File

@@ -27,7 +27,7 @@ test('run pre/postinstall scripts', async () => {
const project = prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'],
testDefaults({ fastUnpack: false, targetDependenciesField: 'devDependencies' })
testDefaults({ fastUnpack: false, targetDependenciesField: 'devDependencies', onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'] })
)
{
@@ -46,7 +46,7 @@ test('run pre/postinstall scripts', async () => {
// testing that the packages are not installed even though they are in lockfile
// and that their scripts are not tried to be executed
await install(manifest, testDefaults({ fastUnpack: false, production: true }))
await install(manifest, testDefaults({ fastUnpack: false, production: true, onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'] }))
{
const generatedByPreinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall')
@@ -95,6 +95,7 @@ test('run pre/postinstall scripts, when PnP is used and no symlinks', async () =
enablePnp: true,
symlink: false,
targetDependenciesField: 'devDependencies',
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
})
)
@@ -108,7 +109,7 @@ test('testing that the bins are linked when the package with the bins was alread
const project = prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/hello-world-js-bin'], testDefaults({ fastUnpack: false }))
await addDependenciesToPackage(manifest, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'], testDefaults({ fastUnpack: false, targetDependenciesField: 'devDependencies' }))
await addDependenciesToPackage(manifest, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'], testDefaults({ fastUnpack: false, targetDependenciesField: 'devDependencies', onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'] }))
const generatedByPreinstall = project.requireModule('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall')
expect(typeof generatedByPreinstall).toBe('function')
@@ -119,7 +120,7 @@ test('testing that the bins are linked when the package with the bins was alread
test('run install scripts', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({}, ['@pnpm.e2e/install-script-example'], testDefaults({ fastUnpack: false }))
await addDependenciesToPackage({}, ['@pnpm.e2e/install-script-example'], testDefaults({ fastUnpack: false, onlyBuiltDependencies: ['@pnpm.e2e/install-script-example'] }))
const generatedByInstall = project.requireModule('@pnpm.e2e/install-script-example/generated-by-install')
expect(typeof generatedByInstall).toBe('function')
@@ -202,6 +203,7 @@ test('INIT_CWD is always set to lockfile directory', async () => {
}, testDefaults({
fastUnpack: false,
lockfileDir: rootDir,
onlyBuiltDependencies: ['@pnpm.e2e/write-lifecycle-env'],
}))
const childEnv = loadJsonFileSync<{ INIT_CWD: string }>(path.join(rootDir, 'node_modules/@pnpm.e2e/write-lifecycle-env/env.json'))
@@ -217,7 +219,7 @@ test("reports child's output", async () => {
const reporter = sinon.spy()
await addDependenciesToPackage({}, ['@pnpm.e2e/count-to-10'], testDefaults({ fastUnpack: false, reporter }))
await addDependenciesToPackage({}, ['@pnpm.e2e/count-to-10'], testDefaults({ fastUnpack: false, reporter, onlyBuiltDependencies: ['@pnpm.e2e/count-to-10'] }))
expect(reporter.calledWithMatch({
depPath: '@pnpm.e2e/count-to-10@1.0.0',
@@ -265,7 +267,7 @@ test("reports child's close event", async () => {
const reporter = sinon.spy()
await expect(
addDependenciesToPackage({}, ['@pnpm.e2e/failing-postinstall'], testDefaults({ reporter }))
addDependenciesToPackage({}, ['@pnpm.e2e/failing-postinstall'], testDefaults({ reporter, onlyBuiltDependencies: ['@pnpm.e2e/failing-postinstall'] }))
).rejects.toThrow()
expect(reporter.calledWithMatch({
@@ -293,7 +295,7 @@ testOnNonWindows('lifecycle scripts have access to node-gyp', async () => {
!p.includes(`${path.sep}.npm${path.sep}`))
.join(path.delimiter)
await addDependenciesToPackage({}, ['drivelist@5.1.8'], testDefaults({ fastUnpack: false }))
await addDependenciesToPackage({}, ['drivelist@5.1.8'], testDefaults({ fastUnpack: false, onlyBuiltDependencies: ['drivelist'] }))
process.env[PATH] = initialPath
})
@@ -301,7 +303,7 @@ testOnNonWindows('lifecycle scripts have access to node-gyp', async () => {
test('run lifecycle scripts of dependent packages after running scripts of their deps', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({}, ['@pnpm.e2e/with-postinstall-a'], testDefaults({ fastUnpack: false }))
await addDependenciesToPackage({}, ['@pnpm.e2e/with-postinstall-a'], testDefaults({ fastUnpack: false, onlyBuiltDependencies: ['@pnpm.e2e/with-postinstall-a', '@pnpm.e2e/with-postinstall-b'] }))
expect(+project.requireModule('.pnpm/@pnpm.e2e+with-postinstall-b@1.0.0/node_modules/@pnpm.e2e/with-postinstall-b/output.json')[0] < +project.requireModule('@pnpm.e2e/with-postinstall-a/output.json')[0]).toBeTruthy()
})
@@ -330,7 +332,7 @@ test('run prepare script for git-hosted dependencies', async () => {
test('lifecycle scripts run before linking bins', async () => {
const project = prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/generated-bins'], testDefaults({ fastUnpack: false }))
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/generated-bins'], testDefaults({ fastUnpack: false, onlyBuiltDependencies: ['@pnpm.e2e/generated-bins'] }))
project.isExecutable('.bin/cmd1')
project.isExecutable('.bin/cmd2')
@@ -341,7 +343,7 @@ test('lifecycle scripts run before linking bins', async () => {
manifest,
mutation: 'install',
rootDir: process.cwd() as ProjectRootDir,
}, testDefaults({ frozenLockfile: true }))
}, testDefaults({ frozenLockfile: true, onlyBuiltDependencies: ['@pnpm.e2e/generated-bins'] }))
project.isExecutable('.bin/cmd1')
project.isExecutable('.bin/cmd2')
@@ -350,7 +352,7 @@ test('lifecycle scripts run before linking bins', async () => {
test('hoisting does not fail on commands that will be created by lifecycle scripts on a later stage', async () => {
prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/has-generated-bins-as-dep'], testDefaults({ fastUnpack: false, hoistPattern: '*' }))
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/has-generated-bins-as-dep'], testDefaults({ fastUnpack: false, hoistPattern: '*', onlyBuiltDependencies: ['@pnpm.e2e/has-generated-bins-as-dep', '@pnpm.e2e/generated-bins'] }))
// project.isExecutable('.pnpm/node_modules/.bin/cmd1')
// project.isExecutable('.pnpm/node_modules/.bin/cmd2')
@@ -362,7 +364,7 @@ test('hoisting does not fail on commands that will be created by lifecycle scrip
manifest,
mutation: 'install',
rootDir: process.cwd() as ProjectRootDir,
}, testDefaults({ frozenLockfile: true, hoistPattern: '*' }))
}, testDefaults({ frozenLockfile: true, hoistPattern: '*', onlyBuiltDependencies: ['@pnpm.e2e/has-generated-bins-as-dep', '@pnpm.e2e/generated-bins'] }))
// project.isExecutable('.pnpm/node_modules/.bin/cmd1')
// project.isExecutable('.pnpm/node_modules/.bin/cmd2')
@@ -423,7 +425,7 @@ test('dependency should not be added to current lockfile if it was not built suc
manifest,
mutation: 'install',
rootDir: process.cwd() as ProjectRootDir,
}, testDefaults({ frozenLockfile: true }))
}, testDefaults({ frozenLockfile: true, onlyBuiltDependencies: ['package-that-cannot-be-installed'] }))
).rejects.toThrow()
expect(project.readCurrentLockfile()).toBeFalsy()
@@ -435,53 +437,19 @@ test('scripts have access to unlisted bins when hoisting is used', async () => {
await addDependenciesToPackage(
{},
['@pnpm.e2e/pkg-that-calls-unlisted-dep-in-hooks'],
testDefaults({ fastUnpack: false, hoistPattern: '*' })
testDefaults({ fastUnpack: false, hoistPattern: '*', onlyBuiltDependencies: ['@pnpm.e2e/pkg-that-calls-unlisted-dep-in-hooks'] })
)
expect(project.requireModule('@pnpm.e2e/pkg-that-calls-unlisted-dep-in-hooks/output.json')).toStrictEqual(['Hello world!'])
})
test('selectively ignore scripts in some dependencies by neverBuiltDependencies', async () => {
prepareEmpty()
const neverBuiltDependencies = ['@pnpm.e2e/pre-and-postinstall-scripts-example']
const { updatedManifest: manifest } = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
testDefaults({ fastUnpack: false, neverBuiltDependencies })
)
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
rimraf('node_modules')
await install(manifest, testDefaults({ fastUnpack: false, frozenLockfile: true, neverBuiltDependencies }))
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
})
test('throw an exception when both neverBuiltDependencies and onlyBuiltDependencies are used', async () => {
prepareEmpty()
await expect(
addDependenciesToPackage(
{},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'],
testDefaults({ onlyBuiltDependencies: ['@pnpm.e2e/foo'], neverBuiltDependencies: ['@pnpm.e2e/bar'] })
)
).rejects.toThrow(/Cannot have both/)
})
test('selectively allow scripts in some dependencies by onlyBuiltDependencies', async () => {
prepareEmpty()
const reporter = sinon.spy()
const onlyBuiltDependencies = ['@pnpm.e2e/install-script-example']
const neverBuiltDependencies: string[] | undefined = undefined
const { updatedManifest: manifest } = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
testDefaults({ fastUnpack: false, onlyBuiltDependencies, neverBuiltDependencies, reporter })
testDefaults({ fastUnpack: false, onlyBuiltDependencies, reporter })
)
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
@@ -500,7 +468,6 @@ test('selectively allow scripts in some dependencies by onlyBuiltDependencies',
fastUnpack: false,
frozenLockfile: true,
ignoredBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
neverBuiltDependencies,
onlyBuiltDependencies,
reporter,
}))
@@ -519,10 +486,9 @@ test('selectively allow scripts in some dependencies by onlyBuiltDependencies us
prepareEmpty()
const reporter = sinon.spy()
const onlyBuiltDependencies = ['@pnpm.e2e/install-script-example@1.0.0']
const neverBuiltDependencies: string[] | undefined = undefined
const { updatedManifest: manifest } = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
testDefaults({ fastUnpack: false, onlyBuiltDependencies, neverBuiltDependencies, reporter })
testDefaults({ fastUnpack: false, onlyBuiltDependencies, reporter })
)
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
@@ -541,7 +507,6 @@ test('selectively allow scripts in some dependencies by onlyBuiltDependencies us
fastUnpack: false,
frozenLockfile: true,
ignoredBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
neverBuiltDependencies,
onlyBuiltDependencies,
reporter,
}))
@@ -560,7 +525,7 @@ test('lifecycle scripts have access to package\'s own binary by binary name', as
const project = prepareEmpty()
await addDependenciesToPackage({},
['@pnpm.e2e/runs-own-bin'],
testDefaults({ fastUnpack: false })
testDefaults({ fastUnpack: false, onlyBuiltDependencies: ['@pnpm.e2e/runs-own-bin'] })
)
project.isExecutable('.pnpm/@pnpm.e2e+runs-own-bin@1.0.0/node_modules/@pnpm.e2e/runs-own-bin/node_modules/.bin/runs-own-bin')
@@ -580,7 +545,7 @@ test('lifecycle scripts run after linking root dependencies', async () => {
manifest,
mutation: 'install',
rootDir: process.cwd() as ProjectRootDir,
}, testDefaults({ fastUnpack: false }))
}, testDefaults({ fastUnpack: false, onlyBuiltDependencies: ['@pnpm.e2e/postinstall-requires-is-positive'] }))
rimraf('node_modules')
@@ -588,7 +553,7 @@ test('lifecycle scripts run after linking root dependencies', async () => {
manifest,
mutation: 'install',
rootDir: process.cwd() as ProjectRootDir,
}, testDefaults({ fastUnpack: false, frozenLockfile: true }))
}, testDefaults({ fastUnpack: false, frozenLockfile: true, onlyBuiltDependencies: ['@pnpm.e2e/postinstall-requires-is-positive'] }))
// if there was no exception, the test passed
})
@@ -718,6 +683,7 @@ test('run pre/postinstall scripts in a workspace that uses node-linker=hoisted',
allProjects,
fastUnpack: false,
nodeLinker: 'hoisted',
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
}))
const rootProject = assertProject(process.cwd())
rootProject.has('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')
@@ -736,7 +702,7 @@ test('run pre/postinstall scripts in a project that uses node-linker=hoisted. Sh
const project = prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'],
testDefaults({ fastUnpack: false, targetDependenciesField: 'devDependencies', nodeLinker: 'hoisted', sideEffectsCacheRead: true, sideEffectsCacheWrite: true })
testDefaults({ fastUnpack: false, targetDependenciesField: 'devDependencies', nodeLinker: 'hoisted', sideEffectsCacheRead: true, sideEffectsCacheWrite: true, onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'] })
)
{
@@ -760,6 +726,7 @@ test('run pre/postinstall scripts in a project that uses node-linker=hoisted. Sh
reporter,
sideEffectsCacheRead: true,
sideEffectsCacheWrite: true,
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
})
)
@@ -771,13 +738,11 @@ test('run pre/postinstall scripts in a project that uses node-linker=hoisted. Sh
test('build dependencies that were not previously built after onlyBuiltDependencies changes', async () => {
prepareEmpty()
const neverBuiltDependencies: string[] | undefined = undefined
const { updatedManifest: manifest } = await addDependenciesToPackage({},
['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'],
testDefaults({
fastUnpack: false,
onlyBuiltDependencies: ['@pnpm.e2e/install-script-example'],
neverBuiltDependencies,
})
)
@@ -789,7 +754,6 @@ test('build dependencies that were not previously built after onlyBuiltDependenc
fastUnpack: false,
frozenLockfile: true,
ignoredBuiltDependencies: [],
neverBuiltDependencies,
onlyBuiltDependencies: ['@pnpm.e2e/install-script-example', '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'],
}))

View File

@@ -549,7 +549,7 @@ test('bin specified in the directories property symlinked to .bin folder when pr
testOnNonWindows('building native addons', async () => {
prepareEmpty()
await addDependenciesToPackage({}, ['diskusage@1.1.3'], testDefaults({ fastUnpack: false }))
await addDependenciesToPackage({}, ['diskusage@1.1.3'], testDefaults({ fastUnpack: false, onlyBuiltDependencies: ['diskusage'] }))
expect(fs.existsSync('node_modules/diskusage/build')).toBeTruthy()
})

View File

@@ -578,7 +578,9 @@ test('fail on a package with failing postinstall if the package is both an optio
'@pnpm.e2e/has-failing-postinstall-dep': '1.0.0',
},
},
testDefaults({})
testDefaults({
onlyBuiltDependencies: ['@pnpm.e2e/has-failing-postinstall-dep', '@pnpm.e2e/failing-postinstall'],
})
)
).rejects.toThrow()
})

View File

@@ -822,7 +822,7 @@ test('peer bins are linked', async () => {
test('run pre/postinstall scripts of each variations of packages with peer dependencies', async () => {
await addDistTag({ package: '@pnpm.e2e/peer-c', version: '1.0.0', distTag: 'latest' })
prepareEmpty()
await addDependenciesToPackage({}, ['@pnpm.e2e/parent-of-pkg-with-events-and-peers', '@pnpm.e2e/pkg-with-events-and-peers', '@pnpm.e2e/peer-c@2.0.0'], testDefaults({ fastUnpack: false }))
await addDependenciesToPackage({}, ['@pnpm.e2e/parent-of-pkg-with-events-and-peers', '@pnpm.e2e/pkg-with-events-and-peers', '@pnpm.e2e/peer-c@2.0.0'], testDefaults({ fastUnpack: false, onlyBuiltDependencies: ['@pnpm.e2e/pkg-with-events-and-peers'] }))
const pkgVariation1 = path.resolve('node_modules/.pnpm/@pnpm.e2e+pkg-with-events-and-peers@1.0.0_@pnpm.e2e+peer-c@1.0.0/node_modules')
await okFile(path.join(pkgVariation1, '@pnpm.e2e/pkg-with-events-and-peers', 'generated-by-preinstall.js'))

View File

@@ -79,6 +79,7 @@ test('using side effects cache', async () => {
sideEffectsCacheRead: true,
sideEffectsCacheWrite: true,
verifyStoreIntegrity: false,
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
}, {}, {}, { packageImportMethod: 'copy' })
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'], opts)
@@ -111,6 +112,7 @@ test('using side effects cache', async () => {
sideEffectsCacheWrite: true,
storeDir: opts.storeDir,
verifyStoreIntegrity: false,
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
}, {}, {}, { packageImportMethod: 'copy' })
await addDependenciesToPackage(manifest, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'], opts2)
@@ -159,6 +161,7 @@ test('uploading errors do not interrupt installation', async () => {
fastUnpack: false,
sideEffectsCacheRead: true,
sideEffectsCacheWrite: true,
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
})
opts.storeController.upload = async () => {
throw new Error('an unexpected error')
@@ -179,6 +182,7 @@ test('a postinstall script does not modify the original sources added to the sto
fastUnpack: false,
sideEffectsCacheRead: true,
sideEffectsCacheWrite: true,
onlyBuiltDependencies: ['@pnpm/postinstall-modifies-source'],
}, {}, {}, { packageImportMethod: 'hardlink' })
await addDependenciesToPackage({}, ['@pnpm/postinstall-modifies-source@1.0.0'], opts)
@@ -211,6 +215,7 @@ test('a corrupted side-effects cache is ignored', async () => {
fastUnpack: false,
sideEffectsCacheRead: true,
sideEffectsCacheWrite: true,
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
})
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0'], opts)
@@ -242,6 +247,7 @@ test('a corrupted side-effects cache is ignored', async () => {
sideEffectsCacheRead: true,
sideEffectsCacheWrite: true,
storeDir: opts.storeDir,
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
})
await install(manifest, opts2)

View File

@@ -35,7 +35,6 @@ export function testDefaults<T> (
})
const result = {
cacheDir,
neverBuiltDependencies: [] as string[],
registries: {
default: registry,
},

View File

@@ -104,10 +104,8 @@ export interface Project {
export interface HeadlessOptions {
ignorePatchFailures?: boolean
neverBuiltDependencies?: string[]
ignoredBuiltDependencies?: string[]
onlyBuiltDependencies?: string[]
onlyBuiltDependenciesFile?: string
autoInstallPeers?: boolean
childConcurrency?: number
currentLockfile?: LockfileObject

View File

@@ -263,28 +263,41 @@ export async function handler (
if (opts.argv.original.includes('--allow-build')) {
throw new PnpmError('ALLOW_BUILD_MISSING_PACKAGE', 'The --allow-build flag is missing a package name. Please specify the package name(s) that are allowed to run installation scripts.')
}
if (opts.rootProjectManifest?.pnpm?.ignoredBuiltDependencies?.length) {
const overlapDependencies = opts.rootProjectManifest.pnpm.ignoredBuiltDependencies.filter((dep) => opts.allowBuild?.includes(dep))
if (opts.rootProjectManifest?.pnpm?.allowBuilds) {
const ignoredBuiltDependencies = Object.keys(opts.rootProjectManifest.pnpm.allowBuilds)
.filter(pkg => opts.rootProjectManifest!.pnpm!.allowBuilds![pkg] === false)
const overlapDependencies = ignoredBuiltDependencies.filter((dep) => opts.allowBuild?.includes(dep))
if (overlapDependencies.length) {
throw new PnpmError('OVERRIDING_IGNORED_BUILT_DEPENDENCIES', `The following dependencies are ignored by the root project, but are allowed to be built by the current command: ${overlapDependencies.join(', ')}`, {
hint: 'If you are sure you want to allow those dependencies to run installation scripts, remove them from the pnpm.ignoredBuiltDependencies list.',
hint: 'If you are sure you want to allow those dependencies to run installation scripts, remove them from the pnpm.allowBuilds list (or change their value to true).',
})
}
}
opts.onlyBuiltDependencies = Array.from(new Set([
...(opts.onlyBuiltDependencies ?? []),
...opts.allowBuild,
])).sort((a, b) => a.localeCompare(b))
const allowBuilds: Record<string, boolean> = {}
for (const pkg of opts.allowBuild) {
allowBuilds[pkg] = true
}
if (opts.rootProjectManifestDir) {
opts.rootProjectManifest = opts.rootProjectManifest ?? {}
await writeSettings({
...opts,
workspaceDir: opts.workspaceDir ?? opts.rootProjectManifestDir,
updatedSettings: {
onlyBuiltDependencies: opts.onlyBuiltDependencies,
allowBuilds,
},
})
}
// Pass the allowed packages to onlyBuiltDependencies so they can build during this install
return installDeps({
...opts,
onlyBuiltDependencies: [
...opts.onlyBuiltDependencies ?? [],
...opts.allowBuild,
],
fetchFullMetadata: getFetchFullMetadata(opts),
include,
includeDirect: include,
}, params)
}
return installDeps({
...opts,

View File

@@ -95,6 +95,7 @@ export type InstallDepsOptions = Pick<Config,
| 'sharedWorkspaceLockfile'
| 'shellEmulator'
| 'tag'
| 'onlyBuiltDependencies'
| 'optional'
| 'workspaceConcurrency'
| 'workspaceDir'
@@ -212,11 +213,18 @@ when running add/update with the --workspace option')
linkWorkspacePackages: Boolean(opts.linkWorkspacePackages),
}).graph
const recursiveRootManifestOpts = getOptionsFromRootManifest(opts.rootProjectManifestDir, opts.rootProjectManifest ?? {})
await recursiveInstallThenUpdateWorkspaceState(allProjects,
params,
{
...opts,
...getOptionsFromRootManifest(opts.rootProjectManifestDir, opts.rootProjectManifest ?? {}),
...recursiveRootManifestOpts,
// Preserve onlyBuiltDependencies from opts if explicitly passed (e.g., from --allow-build flag)
// and merge with any from the manifest
onlyBuiltDependencies: [
...recursiveRootManifestOpts.onlyBuiltDependencies ?? [],
...opts.onlyBuiltDependencies ?? [],
],
forceHoistPattern,
forcePublicHoistPattern,
allProjectsGraph,
@@ -247,9 +255,16 @@ when running add/update with the --workspace option')
manifest = {}
}
const rootManifestOpts = getOptionsFromRootManifest(opts.dir, (opts.dir === opts.rootProjectManifestDir ? opts.rootProjectManifest ?? manifest : manifest))
const installOpts: Omit<MutateModulesOptions, 'allProjects'> = {
...opts,
...getOptionsFromRootManifest(opts.dir, (opts.dir === opts.rootProjectManifestDir ? opts.rootProjectManifest ?? manifest : manifest)),
...rootManifestOpts,
// Preserve onlyBuiltDependencies from opts if explicitly passed (e.g., from --allow-build flag)
// and merge with any from the manifest
onlyBuiltDependencies: [
...rootManifestOpts.onlyBuiltDependencies ?? [],
...opts.onlyBuiltDependencies ?? [],
],
forceHoistPattern,
forcePublicHoistPattern,
// In case installation is done in a multi-package repository

View File

@@ -67,6 +67,7 @@ export type RecursiveOptions = CreateStoreControllerOptions & Pick<Config,
| 'lockfileDir'
| 'lockfileOnly'
| 'modulesDir'
| 'onlyBuiltDependencies'
| 'rawLocalConfig'
| 'registries'
| 'rootProjectManifest'

20
pnpm-lock.yaml generated
View File

@@ -7020,6 +7020,9 @@ importers:
render-help:
specifier: 'catalog:'
version: 1.0.3
write-yaml-file:
specifier: 'catalog:'
version: 5.0.0
devDependencies:
'@jest/globals':
specifier: 'catalog:'
@@ -7048,9 +7051,6 @@ importers:
'@types/ramda':
specifier: 'catalog:'
version: 0.29.12
write-yaml-file:
specifier: 'catalog:'
version: 5.0.0
releasing/plugin-commands-publishing:
dependencies:
@@ -18412,7 +18412,7 @@ snapshots:
'@pnpm/fs.packlist': 2.0.0
'@pnpm/logger': 1001.0.0
'@pnpm/prepare-package': 1000.0.16(@pnpm/logger@1001.0.0)(typanion@3.14.0)
'@pnpm/worker': 1000.1.7(@pnpm/logger@packages+logger)(@types/node@22.15.30)
'@pnpm/worker': 1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.30)
'@zkochan/rimraf': 3.0.2
execa: safe-execa@0.1.2
transitivePeerDependencies:
@@ -18547,7 +18547,7 @@ snapshots:
'@pnpm/find-workspace-dir': 1000.1.0
'@pnpm/logger': 1001.0.0
'@pnpm/types': 1000.6.0
'@pnpm/worker': 1000.1.7(@pnpm/logger@packages+logger)(@types/node@22.15.30)
'@pnpm/worker': 1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.30)
'@pnpm/workspace.find-packages': 1000.0.25(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.30))(typanion@3.14.0)
'@pnpm/workspace.read-manifest': 1000.1.5
load-json-file: 7.0.1
@@ -18753,7 +18753,7 @@ snapshots:
'@pnpm/store-controller-types': 1003.0.2
'@pnpm/store.cafs': 1000.0.13
'@pnpm/types': 1000.6.0
'@pnpm/worker': 1000.1.7(@pnpm/logger@packages+logger)(@types/node@22.15.30)
'@pnpm/worker': 1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.30)
p-defer: 3.0.0
p-limit: 3.1.0
p-queue: 6.6.2
@@ -18772,7 +18772,7 @@ snapshots:
'@pnpm/store-controller-types': 1003.0.2
'@pnpm/store.cafs': 1000.0.13
'@pnpm/types': 1000.6.0
'@pnpm/worker': 1000.1.7(@pnpm/logger@packages+logger)(@types/node@22.15.30)
'@pnpm/worker': 1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.30)
'@zkochan/rimraf': 3.0.2
load-json-file: 6.2.0
ramda: '@pnpm/ramda@0.28.1'
@@ -19051,7 +19051,7 @@ snapshots:
'@pnpm/graceful-fs': 1000.0.0
'@pnpm/logger': 1001.0.0
'@pnpm/prepare-package': 1000.0.16(@pnpm/logger@1001.0.0)(typanion@3.14.0)
'@pnpm/worker': 1000.1.7(@pnpm/logger@packages+logger)(@types/node@22.15.30)
'@pnpm/worker': 1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.30)
'@zkochan/retry': 0.2.0
lodash.throttle: 4.1.1
p-map-values: 1.0.0
@@ -19090,7 +19090,7 @@ snapshots:
dependencies:
isexe: 2.0.0
'@pnpm/worker@1000.1.7(@pnpm/logger@packages+logger)(@types/node@22.15.30)':
'@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.30)':
dependencies:
'@pnpm/cafs-types': 1000.0.0
'@pnpm/create-cafs-store': 1000.0.14(@pnpm/logger@1001.0.0)
@@ -19099,7 +19099,7 @@ snapshots:
'@pnpm/exec.pkg-requires-build': 1000.0.8
'@pnpm/fs.hard-link-dir': 1000.0.1(@pnpm/logger@1001.0.0)
'@pnpm/graceful-fs': 1000.0.0
'@pnpm/logger': link:packages/logger
'@pnpm/logger': 1001.0.0
'@pnpm/store.cafs': 1000.0.13
'@pnpm/symlink-dependency': 1000.0.9(@pnpm/logger@1001.0.0)
'@rushstack/worker-pool': 0.4.9(@types/node@22.15.30)

View File

@@ -1,7 +1,6 @@
import fs from 'fs'
import { prepare } from '@pnpm/prepare'
import { getIntegrity } from '@pnpm/registry-mock'
import { sync as rimraf } from '@zkochan/rimraf'
import { sync as readYamlFile } from 'read-yaml-file'
import { sync as writeYamlFile } from 'write-yaml-file'
import { execPnpm } from './utils/index.js'
@@ -39,56 +38,6 @@ test('patch from configuration dependency is applied via updateConfig hook', asy
expect(lockfile.patchedDependencies['@pnpm.e2e/foo'].path).toBe('node_modules/.pnpm-config/@pnpm.e2e/has-patch-for-foo/@pnpm.e2e__foo@100.0.0.patch')
})
test('selectively allow scripts in some dependencies by onlyBuiltDependenciesFile', async () => {
prepare({})
writeYamlFile('pnpm-workspace.yaml', {
configDependencies: {
'@pnpm.e2e/build-allow-list': `1.0.0+${getIntegrity('@pnpm.e2e/build-allow-list', '1.0.0')}`,
},
onlyBuiltDependenciesFile: 'node_modules/.pnpm-config/@pnpm.e2e/build-allow-list/list.json',
strictDepBuilds: false,
})
await execPnpm(['add', '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'])
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
rimraf('node_modules')
await execPnpm(['install'])
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
})
test('selectively allow scripts in some dependencies by onlyBuiltDependenciesFile and onlyBuiltDependencies', async () => {
prepare()
writeYamlFile('pnpm-workspace.yaml', {
configDependencies: {
'@pnpm.e2e/build-allow-list': `1.0.0+${getIntegrity('@pnpm.e2e/build-allow-list', '1.0.0')}`,
},
onlyBuiltDependenciesFile: 'node_modules/.pnpm-config/@pnpm.e2e/build-allow-list/list.json',
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
})
await execPnpm(['add', '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'])
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeTruthy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
rimraf('node_modules')
await execPnpm(['install'])
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeTruthy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
})
test('catalog applied by configurational dependency hook', async () => {
const project = prepare({
dependencies: {

View File

@@ -5,6 +5,7 @@ import { LAYOUT_VERSION } from '@pnpm/constants'
import { prepare } from '@pnpm/prepare'
import { type ProjectManifest } from '@pnpm/types'
import isWindows from 'is-windows'
import writeYamlFile from 'write-yaml-file'
import {
addDistTag,
execPnpm,
@@ -100,7 +101,12 @@ test('run lifecycle events of global packages in correct working directory', asy
const pnpmHome = path.join(global, 'pnpm')
const globalPkgDir = path.join(pnpmHome, 'global', String(LAYOUT_VERSION))
fs.mkdirSync(globalPkgDir, { recursive: true })
fs.writeFileSync(path.join(globalPkgDir, 'package.json'), JSON.stringify({ pnpm: { neverBuiltDependencies: [] } }))
fs.writeFileSync(path.join(globalPkgDir, 'package.json'), JSON.stringify({}))
writeYamlFile.sync(path.join(globalPkgDir, 'pnpm-workspace.yaml'), {
allowBuilds: {
'@pnpm.e2e/postinstall-calls-pnpm': true,
},
})
const env = {
[PATH_NAME]: `${pnpmHome}${path.delimiter}${process.env[PATH_NAME]!}`,
@@ -124,7 +130,7 @@ test.skip('dangerously-allow-all-builds=true in global config', async () => {
version: '0.0.0',
private: true,
pnpm: {
onlyBuiltDependencies: [], // don't allow any dependencies to be built
allowBuilds: {}, // don't allow any dependencies to be built
},
}
@@ -160,7 +166,7 @@ test.skip('dangerously-allow-all-builds=true in global config', async () => {
expect(fs.readdirSync(path.resolve('node_modules/@pnpm.e2e/postinstall-calls-pnpm'))).not.toContain('created-by-postinstall')
// global config should be used if local config did not specify
delete manifest.pnpm!.onlyBuiltDependencies
delete manifest.pnpm!.allowBuilds
project.writePackageJson(manifest)
fs.rmSync('node_modules', { recursive: true })
fs.rmSync('pnpm-lock.yaml')
@@ -179,7 +185,7 @@ test.skip('dangerously-allow-all-builds=false in global config', async () => {
version: '0.0.0',
private: true,
pnpm: {
onlyBuiltDependencies: ['@pnpm.e2e/postinstall-calls-pnpm'],
allowBuilds: { '@pnpm.e2e/postinstall-calls-pnpm': true },
},
}
@@ -215,7 +221,7 @@ test.skip('dangerously-allow-all-builds=false in global config', async () => {
expect(fs.readdirSync(path.resolve('node_modules/@pnpm.e2e/postinstall-calls-pnpm'))).toContain('created-by-postinstall')
// global config should be used if local config did not specify
delete manifest.pnpm!.onlyBuiltDependencies
delete manifest.pnpm!.allowBuilds
project.writePackageJson(manifest)
fs.rmSync('node_modules', { recursive: true })
fs.rmSync('pnpm-lock.yaml')

View File

@@ -2,12 +2,10 @@ import fs from 'fs'
import path from 'path'
import { prepare } from '@pnpm/prepare'
import { type PackageManifest, type ProjectManifest } from '@pnpm/types'
import { sync as rimraf } from '@zkochan/rimraf'
import PATH from 'path-name'
import { loadJsonFileSync } from 'load-json-file'
import writeYamlFile from 'write-yaml-file'
import { execPnpmSync, pnpmBinLocation } from '../utils/index.js'
import { getIntegrity } from '@pnpm/registry-mock'
import { readWorkspaceManifest } from '@pnpm/workspace.read-manifest'
const pkgRoot = path.join(import.meta.dirname, '..', '..')
@@ -108,9 +106,9 @@ test('dependency should not be added to package.json and lockfile if it was not
const initialPkg = {
name: 'foo',
version: '1.0.0',
pnpm: { neverBuiltDependencies: [] },
}
const project = prepare(initialPkg)
await writeYamlFile('pnpm-workspace.yaml', { allowBuilds: { 'package-that-cannot-be-installed': true } })
const result = execPnpmSync(['install', 'package-that-cannot-be-installed@0.0.0'])
@@ -145,36 +143,6 @@ test('node-gyp is in the PATH', async () => {
expect(result.status).toBe(0)
})
test('selectively allow scripts in some dependencies by onlyBuiltDependenciesFile', async () => {
prepare({
pnpm: {
configDependencies: {
'@pnpm.e2e/build-allow-list': `1.0.0+${getIntegrity('@pnpm.e2e/build-allow-list', '1.0.0')}`,
},
onlyBuiltDependenciesFile: 'node_modules/.pnpm-config/@pnpm.e2e/build-allow-list/list.json',
},
})
execPnpmSync(['add', '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'])
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
rimraf('node_modules')
execPnpmSync(['install', '--frozen-lockfile'])
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
execPnpmSync(['rebuild'])
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
})
test('selectively allow scripts in some dependencies by --allow-build flag', async () => {
const project = prepare({})
execPnpmSync(['add', '--allow-build=@pnpm.e2e/install-script-example', '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'])
@@ -183,10 +151,7 @@ test('selectively allow scripts in some dependencies by --allow-build flag', asy
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
const manifest = loadJsonFileSync<ProjectManifest>('package.json')
expect(manifest.pnpm?.onlyBuiltDependencies).toBeUndefined()
const modulesManifest = await readWorkspaceManifest(project.dir())
expect(modulesManifest?.onlyBuiltDependencies).toBeUndefined()
expect(modulesManifest?.allowBuilds).toStrictEqual({ '@pnpm.e2e/install-script-example': true })
})
@@ -201,22 +166,8 @@ test('--allow-build flag should specify the package', async () => {
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeFalsy()
const manifest = loadJsonFileSync<ProjectManifest>('package.json')
expect(manifest.pnpm?.onlyBuiltDependencies).toBeUndefined()
const modulesManifest = await readWorkspaceManifest(project.dir())
expect(modulesManifest?.onlyBuiltDependencies).toBeUndefined()
})
test('selectively allow scripts in some dependencies by --allow-build flag overlap ignoredBuiltDependencies', async () => {
prepare({
pnpm: {
ignoredBuiltDependencies: ['@pnpm.e2e/install-script-example'],
},
})
const result = execPnpmSync(['add', '--allow-build=@pnpm.e2e/install-script-example', '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'])
expect(result.status).toBe(1)
expect(result.stdout.toString()).toContain('The following dependencies are ignored by the root project, but are allowed to be built by the current command: @pnpm.e2e/install-script-example')
expect(modulesManifest?.allowBuilds).toBeUndefined()
})
test('preinstall script does not trigger verify-deps-before-run (#8954)', async () => {
@@ -310,3 +261,15 @@ test('git dependencies with preparation scripts should be installed when dangero
expect(result.status).toBe(0)
expect(fs.existsSync('node_modules/test-git-fetch/dist/index.js')).toBeTruthy()
})
test('--allow-build flag should error when conflicting with allowBuilds: false', async () => {
prepare({
pnpm: {
allowBuilds: { '@pnpm.e2e/install-script-example': false },
},
})
const result = execPnpmSync(['add', '--allow-build=@pnpm.e2e/install-script-example', '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'])
expect(result.status).toBe(1)
expect(result.stdout.toString()).toContain('The following dependencies are ignored by the root project, but are allowed to be built by the current command: @pnpm.e2e/install-script-example')
})

View File

@@ -1251,11 +1251,7 @@ test('dependencies of workspace projects are built during headless installation'
const projects = preparePackages([
{
location: '.',
package: {
pnpm: {
neverBuiltDependencies: [],
},
},
package: {},
},
{
name: 'project-1',
@@ -1270,6 +1266,9 @@ test('dependencies of workspace projects are built during headless installation'
writeYamlFile('pnpm-workspace.yaml', {
packages: ['**', '!store/**'],
sharedWorkspaceLockfile: false,
allowBuilds: {
'@pnpm.e2e/pre-and-postinstall-scripts-example': true,
},
})
await execPnpm(['install', '--lockfile-only'])
@@ -1890,9 +1889,6 @@ test('deploy should keep files created by lifecycle scripts', async () => {
name: 'root',
version: '0.0.0',
private: true,
pnpm: {
neverBuiltDependencies: [],
},
},
'project-0': {
name: 'project-0',
@@ -1914,6 +1910,9 @@ test('deploy should keep files created by lifecycle scripts', async () => {
writeYamlFile('pnpm-workspace.yaml', {
packages: ['**', '!store/**'],
injectWorkspacePackages: true,
allowBuilds: {
'@pnpm.e2e/install-script-example': true,
},
})
const monorepoRoot = process.cwd()
@@ -1941,9 +1940,6 @@ test('rebuild in a directory created with "pnpm deploy" and with "pnpm.neverBuil
name: 'root',
version: '0.0.0',
private: true,
pnpm: {
neverBuiltDependencies: [],
},
},
'project-0': {
name: 'project-0',
@@ -1965,6 +1961,9 @@ test('rebuild in a directory created with "pnpm deploy" and with "pnpm.neverBuil
writeYamlFile('pnpm-workspace.yaml', {
packages: ['**', '!store/**'],
injectWorkspacePackages: true,
allowBuilds: {
'@pnpm.e2e/install-script-example': true,
},
})
const monorepoRoot = process.cwd()

View File

@@ -5,11 +5,7 @@ test('`pnpm recursive rebuild` specific dependencies', async () => {
const projects = preparePackages([
{
location: '.',
package: {
pnpm: {
neverBuiltDependencies: [],
},
},
package: {},
},
{
name: 'project-1',
@@ -42,7 +38,7 @@ test('`pnpm recursive rebuild` specific dependencies', async () => {
projects['project-2'].hasNot('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')
projects['project-2'].hasNot('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')
await execPnpm(['recursive', 'rebuild', 'install-scripts-example-for-pnpm'])
await execPnpm(['recursive', 'rebuild', 'install-scripts-example-for-pnpm', '--config.dangerouslyAllowAllBuilds=true'])
projects['project-1'].hasNot('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')
projects['project-1'].hasNot('@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')

View File

@@ -49,7 +49,8 @@
"@zkochan/rimraf": "catalog:",
"normalize-path": "catalog:",
"ramda": "catalog:",
"render-help": "catalog:"
"render-help": "catalog:",
"write-yaml-file": "catalog:"
},
"peerDependencies": {
"@pnpm/logger": "catalog:"
@@ -63,8 +64,7 @@
"@pnpm/registry-mock": "catalog:",
"@pnpm/test-fixtures": "workspace:*",
"@pnpm/workspace.filter-packages-from-dir": "workspace:*",
"@types/ramda": "catalog:",
"write-yaml-file": "catalog:"
"@types/ramda": "catalog:"
},
"engines": {
"node": ">=20.19"

View File

@@ -30,11 +30,17 @@ export interface CreateDeployFilesOptions {
selectedProjectManifest: ProjectManifest
projectId: ProjectId
rootProjectManifestDir: string
allowBuilds?: Record<string, boolean | string>
}
export interface DeployWorkspaceManifest {
allowBuilds?: Record<string, boolean | string>
}
export interface DeployFiles {
lockfile: LockfileObject
manifest: ProjectManifest
workspaceManifest?: DeployWorkspaceManifest
}
export function createDeployFiles ({
@@ -46,6 +52,7 @@ export function createDeployFiles ({
selectedProjectManifest,
projectId,
rootProjectManifestDir,
allowBuilds,
}: CreateDeployFilesOptions): DeployFiles {
const deployedProjectRealPath = path.resolve(lockfileDir, projectId)
const inputSnapshot = lockfile.importers[projectId]
@@ -160,6 +167,12 @@ export function createDeployFiles ({
}
}
if (allowBuilds) {
result.workspaceManifest = {
allowBuilds,
}
}
return result
}

View File

@@ -3,6 +3,7 @@ import path from 'path'
import { pick } from 'ramda'
import { docsUrl } from '@pnpm/cli-utils'
import { type Config, types as configTypes } from '@pnpm/config'
import { WORKSPACE_MANIFEST_FILENAME } from '@pnpm/constants'
import { fetchFromDir } from '@pnpm/directory-fetcher'
import { createIndexedPkgImporter } from '@pnpm/fs.indexed-pkg-importer'
import { isEmptyDirOrNothing } from '@pnpm/fs.is-empty-dir-or-nothing'
@@ -12,6 +13,7 @@ import { PnpmError } from '@pnpm/error'
import { getLockfileImporterId, readWantedLockfile, writeWantedLockfile } from '@pnpm/lockfile.fs'
import rimraf from '@zkochan/rimraf'
import renderHelp from 'render-help'
import writeYamlFile from 'write-yaml-file'
import { deployHook } from './deployHook.js'
import { logger, globalWarn } from '@pnpm/logger'
import { type Project } from '@pnpm/types'
@@ -78,7 +80,7 @@ export function help (): string {
export type DeployOptions =
& Omit<install.InstallCommandOptions, 'useLockfile'>
& Pick<Config, 'forceLegacyDeploy'>
& Pick<Config, 'allowBuilds' | 'forceLegacyDeploy'>
export async function handler (opts: DeployOptions, params: string[]): Promise<void> {
if (!opts.workspaceDir) {
@@ -240,15 +242,22 @@ async function deployFromSharedLockfile (
selectedProjectManifest: selectedProject.manifest,
projectId,
rootProjectManifestDir,
allowBuilds: opts.allowBuilds,
})
await Promise.all([
const filesToWrite: Array<Promise<void>> = [
fs.promises.writeFile(
path.join(deployDir, 'package.json'),
JSON.stringify(deployFiles.manifest, undefined, 2) + '\n'
),
writeWantedLockfile(deployDir, deployFiles.lockfile),
])
]
if (deployFiles.workspaceManifest) {
filesToWrite.push(
writeYamlFile(path.join(deployDir, WORKSPACE_MANIFEST_FILENAME), deployFiles.workspaceManifest)
)
}
await Promise.all(filesToWrite)
try {
await install.handler({

View File

@@ -278,7 +278,7 @@ test('the deploy manifest should inherit some fields from the pnpm object from t
version: '0.0.0',
private: true,
pnpm: {
onlyBuiltDependencies: ['from-root'],
allowBuilds: { 'from-root': true },
overrides: {
'is-positive': '2.0.0',
},
@@ -292,7 +292,7 @@ test('the deploy manifest should inherit some fields from the pnpm object from t
'is-positive': '3.1.0',
},
pnpm: {
onlyBuiltDependencies: ['from-project-0'],
allowBuilds: { 'from-project-0': true },
overrides: {
'is-positive': '=1.0.0',
},
@@ -344,7 +344,7 @@ test('the deploy manifest should inherit some fields from the pnpm object from t
const manifest = readPackageJson('deploy') as ProjectManifest
expect(manifest.pnpm).toStrictEqual({
onlyBuiltDependencies: preparedManifests.root.pnpm!.onlyBuiltDependencies,
allowBuilds: preparedManifests.root.pnpm!.allowBuilds,
} as ProjectManifest['pnpm'])
expect(readPackageJson('deploy/node_modules/is-positive/')).toHaveProperty(['version'], preparedManifests.root.pnpm!.overrides!['is-positive'])
@@ -1079,9 +1079,6 @@ test('deploy with a shared lockfile should keep files created by lifecycle scrip
name: 'root',
version: '0.0.0',
private: true,
pnpm: {
neverBuiltDependencies: [],
},
},
'project-0': {
name: 'project-0',
@@ -1117,6 +1114,7 @@ test('deploy with a shared lockfile should keep files created by lifecycle scrip
rootProjectManifestDir: process.cwd(),
recursive: true,
lockfileDir: process.cwd(),
onlyBuiltDependencies: ['@pnpm.e2e/install-script-example'],
workspaceDir: process.cwd(),
})
expect(fs.existsSync('pnpm-lock.yaml')).toBeTruthy()
@@ -1132,6 +1130,7 @@ test('deploy with a shared lockfile should keep files created by lifecycle scrip
selectedProjectsGraph,
sharedWorkspaceLockfile: true,
lockfileDir: process.cwd(),
onlyBuiltDependencies: ['@pnpm.e2e/install-script-example'],
workspaceDir: process.cwd(),
}, ['deploy'])

View File

@@ -45,32 +45,7 @@ export async function updateWorkspaceManifest (dir: string, opts: {
shouldBeUpdated = removePackagesFromWorkspaceCatalog(manifest, opts.allProjects ?? []) || shouldBeUpdated
}
// If the current manifest has allowBuilds, convert old fields to allowBuilds format
const updatedFields = { ...opts.updatedFields }
if (manifest.allowBuilds != null || (manifest.onlyBuiltDependencies == null && manifest.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
if (manifest.allowBuilds != null || Object.keys(allowBuilds).length > 0) {
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)) {

View File

@@ -8,13 +8,13 @@ import { sync as writeYamlFile } from 'write-yaml-file'
test('updateWorkspaceManifest adds a new setting', async () => {
const dir = tempDir(false)
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
writeYamlFile(filePath, { packages: ['*'], onlyBuiltDependencies: [] })
writeYamlFile(filePath, { packages: ['*'], allowBuilds: {} })
await updateWorkspaceManifest(dir, {
updatedFields: { onlyBuiltDependencies: [] },
updatedFields: { allowBuilds: {} },
})
expect(readYamlFile(filePath)).toStrictEqual({
packages: ['*'],
onlyBuiltDependencies: [],
allowBuilds: {},
})
})
@@ -48,14 +48,13 @@ test('updateWorkspaceManifest updates allowBuilds', async () => {
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
writeYamlFile(filePath, { packages: ['*'], allowBuilds: { qar: 'warn' } })
await updateWorkspaceManifest(dir, {
updatedFields: { onlyBuiltDependencies: ['foo'], ignoredBuiltDependencies: ['bar'] },
updatedFields: { allowBuilds: { foo: true, bar: false } },
})
expect(readYamlFile(filePath)).toStrictEqual({
packages: ['*'],
allowBuilds: {
bar: false,
foo: true,
qar: 'warn',
},
})
})