node/deps/npm/test/lib/utils/resolve-allow-scripts.js
npm CLI robot 30a7e289b6
deps: upgrade npm to 11.16.0
PR-URL: https://github.com/nodejs/node/pull/63602
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
2026-05-29 21:22:30 +00:00

347 lines
12 KiB
JavaScript

const t = require('tap')
const mockNpm = require('../../fixtures/mock-npm')
const tmock = require('../../fixtures/tmock')
const loadResolver = (t) => tmock(t, '{LIB}/utils/resolve-allow-scripts.js')
// Helper that simulates config layering. `cliConfig` sets the value at
// the 'cli' source; `npmrcConfig` sets it at the 'user' source. mockNpm
// puts all `config` keys into the 'cli' source by default, so for npmrc
// tests we use an .npmrc file instead.
t.test('returns null when no policy is set anywhere', async t => {
const { npm } = await mockNpm(t, {
prefixDir: { 'package.json': JSON.stringify({ name: 'p' }) },
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm)
t.strictSame(result, { policy: null, source: null })
})
t.test('global install: skips package.json but still consults CLI', async t => {
const { npm } = await mockNpm(t, {
config: { global: true, 'allow-scripts': 'canvas' },
prefixDir: { 'package.json': JSON.stringify({ name: 'p', allowScripts: { sharp: true } }) },
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm)
t.equal(result.source, 'cli')
t.strictSame(result.policy, { canvas: true })
})
t.test('global install: skips package.json but still consults .npmrc', async t => {
const { npm } = await mockNpm(t, {
config: { global: true },
homeDir: { '.npmrc': 'allow-scripts = canvas' },
prefixDir: {
'package.json': JSON.stringify({ name: 'p', allowScripts: { sharp: true } }),
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm)
t.equal(result.source, '.npmrc')
t.strictSame(result.policy, { canvas: true })
})
t.test('global install with no CLI or .npmrc returns null', async t => {
const { npm } = await mockNpm(t, {
config: { global: true },
prefixDir: { 'package.json': JSON.stringify({ name: 'p', allowScripts: { x: true } }) },
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm)
t.strictSame(result, { policy: null, source: null })
})
t.test('reads from package.json when only package.json is set', async t => {
const { npm } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'p',
allowScripts: { canvas: true, 'core-js': false, 'sharp@0.33.2': true },
}),
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm)
t.equal(result.source, 'package.json')
t.strictSame(result.policy, { canvas: true, 'core-js': false, 'sharp@0.33.2': true })
})
t.test('--allow-scripts CLI flag is rejected in project-scoped installs', async t => {
const mock = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'p',
allowScripts: { sharp: true },
}),
},
// mock-npm puts all config keys at the 'cli' source.
config: { 'allow-scripts': 'canvas' },
})
const resolveAllowScripts = loadResolver(t)
await t.rejects(
resolveAllowScripts(mock.npm),
{ code: 'EALLOWSCRIPTS', message: /--allow-scripts is not allowed/ }
)
})
t.test('--allow-scripts CLI flag is accepted in global installs (RFC layer 1 wins)', async t => {
const mock = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'p',
allowScripts: { sharp: true },
}),
},
config: { 'allow-scripts': 'canvas', global: true },
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(mock.npm)
t.equal(result.source, 'cli')
t.strictSame(result.policy, { canvas: true })
})
t.test('package.json wins over .npmrc setting (RFC layer 2 > layer 3)', async t => {
// Put the allow-scripts setting in an .npmrc file so it loads at the
// 'user' source, not 'cli'.
const mock = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'p',
allowScripts: { sharp: true },
}),
'.npmrc': 'allow-scripts = canvas',
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(mock.npm)
t.equal(result.source, 'package.json')
t.strictSame(result.policy, { sharp: true })
t.match(
mock.logs.warn.byTitle('allow-scripts'),
[/\.npmrc allow-scripts setting is being ignored because package.json/]
)
})
t.test('.npmrc setting is used when nothing higher is set', async t => {
const { npm } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'p' }),
'.npmrc': 'allow-scripts = canvas, sharp',
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm)
t.equal(result.source, '.npmrc')
t.strictSame(result.policy, { canvas: true, sharp: true })
})
t.test('--allow-scripts CLI flag is accepted via skipProjectConfig (npm exec)', async t => {
const mock = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'p' }),
'.npmrc': 'allow-scripts = canvas',
},
config: { 'allow-scripts': 'sharp' },
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(mock.npm, { skipProjectConfig: true })
t.equal(result.source, 'cli')
t.strictSame(result.policy, { sharp: true })
t.match(
mock.logs.warn.byTitle('allow-scripts'),
[/\.npmrc allow-scripts setting is being ignored because --allow-scripts/]
)
})
t.test('empty allowScripts object in package.json falls through to .npmrc', async t => {
const { npm } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'p', allowScripts: {} }),
'.npmrc': 'allow-scripts = canvas',
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm)
t.equal(result.source, '.npmrc')
t.strictSame(result.policy, { canvas: true })
})
t.test('missing package.json with .npmrc setting uses .npmrc', async t => {
const { npm } = await mockNpm(t, {
prefixDir: {
'.npmrc': 'allow-scripts = canvas',
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm)
t.equal(result.source, '.npmrc')
t.strictSame(result.policy, { canvas: true })
})
t.test('reads from npm.prefix, not cwd, so workspace sub-installs find root policy', async t => {
const { npm } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'root',
workspaces: ['packages/*'],
allowScripts: { sharp: true },
}),
packages: {
sub: { 'package.json': JSON.stringify({ name: 'sub' }) },
},
},
chdir: ({ prefix }) => require('node:path').join(prefix, 'packages', 'sub'),
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm)
t.equal(result.source, 'package.json')
t.strictSame(result.policy, { sharp: true })
})
t.test('drops package.json entries with forbidden semver ranges and warns', async t => {
const mock = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'p',
allowScripts: {
'sharp@^0.33.0': true, // forbidden: caret range
'canvas@~2.11.0': true, // forbidden: tilde range
'core-js@>=3.0.0': true, // forbidden: gte range
'good@1.2.3': true, // OK: exact pin
'also-good': true, // OK: bare name
'disjunction@1.0.0 || 2.0.0': true, // OK: exact disjunction
},
}),
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(mock.npm)
t.equal(result.source, 'package.json')
t.strictSame(result.policy, {
'good@1.2.3': true,
'also-good': true,
'disjunction@1.0.0 || 2.0.0': true,
})
const warnings = mock.logs.warn.byTitle('allow-scripts')
t.equal(warnings.filter(m => /semver ranges/.test(m)).length, 3)
})
t.test('drops package.json entries with dist-tag specs and warns', async t => {
const mock = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'p',
allowScripts: {
'sharp@latest': true, // forbidden: dist-tag
'canvas@next': true, // forbidden: dist-tag
'good@1.2.3': true, // OK: exact pin
},
}),
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(mock.npm)
t.equal(result.source, 'package.json')
t.strictSame(result.policy, { 'good@1.2.3': true })
const warnings = mock.logs.warn.byTitle('allow-scripts')
t.equal(warnings.filter(m => /dist-tag specs/.test(m)).length, 2)
})
t.test('drops .npmrc forbidden ranges (and warns) but keeps valid entries', async t => {
const mock = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'p' }),
'.npmrc': 'allow-scripts = canvas, sharp@^0.33.0, lodash@4.17.21',
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(mock.npm)
t.equal(result.source, '.npmrc')
t.strictSame(result.policy, { canvas: true, 'lodash@4.17.21': true })
const warnings = mock.logs.warn.byTitle('allow-scripts')
t.ok(warnings.some(m => /sharp@\^0\.33\.0/.test(m) && /semver ranges/.test(m)))
})
t.test('drops package.json entries that fail npa parse', async t => {
const mock = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'p',
allowScripts: {
'@@@invalid@@@': true,
good: true,
},
}),
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(mock.npm)
t.equal(result.source, 'package.json')
t.strictSame(result.policy, { good: true })
t.ok(mock.logs.warn.byTitle('allow-scripts').some(m => /unparseable/.test(m)))
})
t.test('returns null when all package.json entries are dropped as invalid', async t => {
const { npm } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'p',
allowScripts: { 'sharp@^0.33.0': true },
}),
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm)
t.strictSame(result, { policy: null, source: null })
})
t.test('skipProjectConfig: ignores package.json even when present', async t => {
// Per RFC line 299, exec/npx consults only user/global .npmrc.
const { npm } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'p',
allowScripts: { sharp: true },
}),
'.npmrc': 'allow-scripts = canvas',
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm, { skipProjectConfig: true })
// package.json is skipped, falls through to .npmrc.
t.equal(result.source, '.npmrc')
t.strictSame(result.policy, { canvas: true })
})
t.test('skipProjectConfig: CLI still wins over .npmrc', async t => {
const { npm } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'p',
allowScripts: { sharp: true },
}),
'.npmrc': 'allow-scripts = canvas',
},
config: { 'allow-scripts': 'lodash' },
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm, { skipProjectConfig: true })
t.equal(result.source, 'cli')
t.strictSame(result.policy, { lodash: true })
})
t.test('skipProjectConfig: returns null when only package.json is set', async t => {
const { npm } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'p',
allowScripts: { sharp: true },
}),
},
})
const resolveAllowScripts = loadResolver(t)
const result = await resolveAllowScripts(npm, { skipProjectConfig: true })
t.strictSame(result, { policy: null, source: null })
})