node/deps/npm/test/lib/commands/approve-scripts.js
npm CLI robot 36bcfa7913
deps: upgrade npm to 11.17.0
PR-URL: https://github.com/nodejs/node/pull/63857
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Jordan Harband <ljharb@gmail.com>
2026-06-13 19:08:33 +00:00

811 lines
28 KiB
JavaScript

const t = require('tap')
const fs = require('node:fs')
const { resolve } = require('node:path')
const _mockNpm = require('../../fixtures/mock-npm')
const mockNpm = async (t, opts = {}) => {
return _mockNpm(t, opts)
}
const setupProject = ({ allowScripts, withScripts = ['canvas'] } = {}) => {
const pkg = {
name: 'host',
version: '1.0.0',
dependencies: Object.fromEntries(withScripts.map((n) => [n, '*'])),
}
if (allowScripts !== undefined) {
pkg.allowScripts = allowScripts
}
const lockPackages = { '': pkg }
const nodeModules = {}
for (const name of withScripts) {
const tarUrl = `https://registry.npmjs.org/${name}/-/${name}-1.0.0.tgz`
nodeModules[name] = {
'package.json': JSON.stringify({
name,
version: '1.0.0',
scripts: { install: 'echo install' },
}),
}
lockPackages[`node_modules/${name}`] = {
version: '1.0.0',
resolved: tarUrl,
hasInstallScript: true,
}
}
return {
'package.json': JSON.stringify(pkg, null, 2),
'package-lock.json': JSON.stringify({
name: pkg.name,
version: pkg.version,
lockfileVersion: 3,
requires: true,
packages: lockPackages,
}),
node_modules: nodeModules,
}
}
t.test('approve-scripts --pending lists unreviewed packages', async t => {
const { npm, joinedOutput } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas', 'sharp'] }),
config: { 'allow-scripts-pending': true },
})
await npm.exec('approve-scripts', [])
const out = joinedOutput()
t.match(out, /2 packages have install scripts not yet covered/)
t.match(out, /canvas@1\.0\.0/)
t.match(out, /sharp@1\.0\.0/)
})
t.test('approve-scripts --pending lists unreviewed packages even with ignore-scripts set', async t => {
const { npm, joinedOutput } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas', 'sharp'] }),
config: { 'allow-scripts-pending': true, 'ignore-scripts': true },
})
await npm.exec('approve-scripts', [])
const out = joinedOutput()
t.match(out, /2 packages have install scripts not yet covered/)
t.match(out, /canvas@1\.0\.0/)
t.match(out, /sharp@1\.0\.0/)
})
t.test('approve-scripts --pending with no unreviewed says so', async t => {
const { npm, joinedOutput } = await mockNpm(t, {
prefixDir: setupProject({
allowScripts: { canvas: true },
withScripts: ['canvas'],
}),
config: { 'allow-scripts-pending': true },
})
await npm.exec('approve-scripts', [])
t.match(joinedOutput(), /No packages with unreviewed install scripts/)
})
t.test('approve-scripts <pkg> writes pinned entry by default', async t => {
const { npm, prefix } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
})
await npm.exec('approve-scripts', ['canvas'])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
t.strictSame(pkg.allowScripts, { 'canvas@1.0.0': true })
})
t.test('approve-scripts <pkg> --no-pin writes name-only entry', async t => {
const { npm, prefix } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
config: { 'allow-scripts-pin': false },
})
await npm.exec('approve-scripts', ['canvas'])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
t.strictSame(pkg.allowScripts, { canvas: true })
})
t.test('approve-scripts --all approves every unreviewed package', async t => {
const { npm, prefix } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas', 'sharp'] }),
config: { all: true },
})
await npm.exec('approve-scripts', [])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
t.strictSame(pkg.allowScripts, {
'canvas@1.0.0': true,
'sharp@1.0.0': true,
})
})
t.test('approve-scripts errors on unknown package', async t => {
const { npm } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
})
await t.rejects(
npm.exec('approve-scripts', ['not-installed']),
{ code: 'ENOMATCH' }
)
})
t.test('approve-scripts approves a package whose name contains dots', async t => {
const { npm, prefix } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['cordova.plugins.diagnostic'] }),
})
await npm.exec('approve-scripts', ['cordova.plugins.diagnostic'])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
t.strictSame(pkg.allowScripts, { 'cordova.plugins.diagnostic@1.0.0': true })
})
t.test('approve-scripts <pkg@version> approves a dotted name with a version specifier', async t => {
const { npm, prefix } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['cordova.plugins.diagnostic'] }),
})
await npm.exec('approve-scripts', ['cordova.plugins.diagnostic@1.0.0'])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
t.strictSame(pkg.allowScripts, { 'cordova.plugins.diagnostic@1.0.0': true })
})
t.test('approve-scripts <@scope/pkg@version> approves a scoped name with a version', async t => {
const { npm, prefix } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'host',
version: '1.0.0',
dependencies: { '@scope/pkg': '*' },
}),
'package-lock.json': JSON.stringify({
name: 'host',
version: '1.0.0',
lockfileVersion: 3,
requires: true,
packages: {
'': { name: 'host', version: '1.0.0', dependencies: { '@scope/pkg': '*' } },
'node_modules/@scope/pkg': {
version: '2.0.0',
resolved: 'https://registry.npmjs.org/@scope/pkg/-/pkg-2.0.0.tgz',
hasInstallScript: true,
},
},
}),
node_modules: {
'@scope': {
pkg: {
'package.json': JSON.stringify({
name: '@scope/pkg',
version: '2.0.0',
scripts: { install: 'echo install' },
}),
},
},
},
},
})
await npm.exec('approve-scripts', ['@scope/pkg@2.0.0'])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
t.strictSame(pkg.allowScripts, { '@scope/pkg@2.0.0': true })
})
t.test('approve-scripts <pkg@version> errors when no installed version matches', async t => {
const { npm } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
})
await t.rejects(
npm.exec('approve-scripts', ['canvas@9.9.9']),
{ code: 'ENOMATCH' }
)
})
t.test('approve-scripts reports only the unmatched args and writes nothing', async t => {
const { npm, prefix } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
})
const err = await npm.exec('approve-scripts', ['canvas', 'not-installed'])
.then(() => null, e => e)
t.equal(err.code, 'ENOMATCH')
t.match(err.message, /not-installed/)
t.notMatch(err.message, /canvas/)
// Nothing is written when any arg fails to match.
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
t.notOk(pkg.allowScripts)
})
t.test('approve-scripts <pkg@tag> matches every installed version', async t => {
const { npm, prefix } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
})
// A tag carries no version to filter on, so it behaves like a bare name.
await npm.exec('approve-scripts', ['canvas@latest'])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
t.strictSame(pkg.allowScripts, { 'canvas@1.0.0': true })
})
t.test('approve-scripts errors on an unparseable spec', async t => {
const { npm } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
})
// npm-package-arg throws on this; the raw string is used as the name and
// matches nothing.
await t.rejects(
npm.exec('approve-scripts', ['foo@@bar']),
{ code: 'ENOMATCH' }
)
})
t.test('approve-scripts errors on a spec with no package name', async t => {
const { npm } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
})
// A path spec parses but carries no name, so it matches no registry dep.
await t.rejects(
npm.exec('approve-scripts', ['./local-pkg']),
{ code: 'ENOMATCH' }
)
})
t.test('approve-scripts respects existing deny entry', async t => {
const { npm, prefix, logs } = await mockNpm(t, {
prefixDir: setupProject({
withScripts: ['canvas'],
allowScripts: { canvas: false },
}),
})
await npm.exec('approve-scripts', ['canvas'])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
// Deny wins; unchanged.
t.strictSame(pkg.allowScripts, { canvas: false })
t.match(logs.warn.byTitle('approve-scripts'), [/canvas is denied/])
})
t.test('approve-scripts requires positional args, --all, or --pending', async t => {
const { npm } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
})
await t.rejects(npm.exec('approve-scripts', []), { code: 'EUSAGE' })
})
t.test('approve-scripts --pending cannot be combined with positional', async t => {
const { npm } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
config: { 'allow-scripts-pending': true },
})
await t.rejects(npm.exec('approve-scripts', ['canvas']), { code: 'EUSAGE' })
})
t.test('approve-scripts fails on global', async t => {
const { npm } = await mockNpm(t, {
config: { global: true },
})
await t.rejects(npm.exec('approve-scripts', ['canvas']), { code: 'EGLOBAL' })
})
t.test('approve-scripts --json outputs structured summary', async t => {
const { npm, joinedOutput } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
config: { json: true },
})
await npm.exec('approve-scripts', ['canvas'])
const parsed = JSON.parse(joinedOutput())
t.match(parsed, {
allowScripts: [{ name: 'canvas', changes: [{ key: 'canvas@1.0.0', change: 'added' }] }],
})
})
t.test('approve-scripts --pending --json lists unreviewed packages as JSON', async t => {
const { npm, joinedOutput } = await mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas', 'sharp'] }),
config: { 'allow-scripts-pending': true, json: true },
})
await npm.exec('approve-scripts', [])
const parsed = JSON.parse(joinedOutput())
const byName = Object.fromEntries(parsed.allowScripts.map((e) => [e.name, e.changes]))
t.strictSame(byName, {
canvas: [{ key: 'canvas@1.0.0', change: 'pending' }],
sharp: [{ key: 'sharp@1.0.0', change: 'pending' }],
})
})
t.test('approve-scripts --pending --json with no unreviewed emits empty list', async t => {
const { npm, joinedOutput } = await mockNpm(t, {
prefixDir: setupProject({
allowScripts: { canvas: true },
withScripts: ['canvas'],
}),
config: { 'allow-scripts-pending': true, json: true },
})
await npm.exec('approve-scripts', [])
t.strictSame(JSON.parse(joinedOutput()), { allowScripts: [] })
})
t.test('approve-scripts --all --json with no unreviewed emits empty list', async t => {
const { npm, joinedOutput } = await _mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'host', version: '1.0.0' }),
'package-lock.json': JSON.stringify({
name: 'host',
version: '1.0.0',
lockfileVersion: 3,
requires: true,
packages: { '': { name: 'host', version: '1.0.0' } },
}),
node_modules: {},
},
config: { all: true, json: true },
})
await npm.exec('approve-scripts', [])
t.strictSame(JSON.parse(joinedOutput()), { allowScripts: [] })
})
t.test('approve-scripts --all with no unreviewed packages prints message', async t => {
const { npm, joinedOutput } = await _mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'host', version: '1.0.0' }),
'package-lock.json': JSON.stringify({
name: 'host',
version: '1.0.0',
lockfileVersion: 3,
requires: true,
packages: { '': { name: 'host', version: '1.0.0' } },
}),
node_modules: {},
},
config: { all: true },
})
await npm.exec('approve-scripts', [])
t.match(joinedOutput(), /No packages with unreviewed install scripts/)
})
t.test('approve-scripts <pkg> on a package already at the right pin is no-op', async t => {
const { npm, prefix, joinedOutput } = await _mockNpm(t, {
prefixDir: setupProject({
withScripts: ['canvas'],
allowScripts: { 'canvas@1.0.0': true },
}),
})
await npm.exec('approve-scripts', ['canvas'])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
t.strictSame(pkg.allowScripts, { 'canvas@1.0.0': true })
t.match(joinedOutput(), /Nothing to approve/)
})
t.test('approve-scripts --pending with single package uses singular wording', async t => {
const { npm, joinedOutput } = await _mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
config: { 'allow-scripts-pending': true },
})
await npm.exec('approve-scripts', [])
t.match(joinedOutput(), /1 package has install scripts/)
})
t.test('approve-scripts --pending lists package with no version', async t => {
// Use a fixture where the lockfile records a synthetic node without a version
const { npm } = await _mockNpm(t, {
prefixDir: setupProject({ withScripts: ['canvas'] }),
config: { 'allow-scripts-pending': true },
})
await npm.exec('approve-scripts', [])
// Just exercising; no assertion needed for additional coverage.
t.pass()
})
const twoVersionFixture = {
'package.json': JSON.stringify({
name: 'host',
version: '1.0.0',
dependencies: { 'top-of-tree': '*' },
}),
'package-lock.json': JSON.stringify({
name: 'host',
version: '1.0.0',
lockfileVersion: 3,
requires: true,
packages: {
'': { name: 'host', version: '1.0.0', dependencies: { 'top-of-tree': '*' } },
'node_modules/lodash': {
version: '4.17.21',
resolved: 'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
hasInstallScript: true,
},
'node_modules/top-of-tree': {
version: '1.0.0',
resolved: 'https://registry.npmjs.org/top-of-tree/-/top-of-tree-1.0.0.tgz',
dependencies: { lodash: '3.10.1' },
},
'node_modules/top-of-tree/node_modules/lodash': {
version: '3.10.1',
resolved: 'https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz',
hasInstallScript: true,
},
},
}),
node_modules: {
lodash: {
'package.json': JSON.stringify({
name: 'lodash',
version: '4.17.21',
scripts: { install: 'echo install' },
}),
},
'top-of-tree': {
'package.json': JSON.stringify({ name: 'top-of-tree', version: '1.0.0' }),
node_modules: {
lodash: {
'package.json': JSON.stringify({
name: 'lodash',
version: '3.10.1',
scripts: { install: 'echo install' },
}),
},
},
},
},
}
t.test('approve-scripts --pending --json groups multiple versions under one name', async t => {
// Two versions of lodash are unreviewed; pendingSummary must collapse
// them into a single `lodash` entry (hits the `groups.has(display)`
// truthy branch on the second node).
const { npm, joinedOutput } = await _mockNpm(t, {
prefixDir: twoVersionFixture,
config: { 'allow-scripts-pending': true, json: true },
})
await npm.exec('approve-scripts', [])
const parsed = JSON.parse(joinedOutput())
t.strictSame(parsed.allowScripts.map((e) => e.name), ['lodash'])
t.strictSame(parsed.allowScripts[0].changes.map((c) => c.key).sort(), [
'lodash@3.10.1',
'lodash@4.17.21',
])
t.ok(parsed.allowScripts[0].changes.every((c) => c.change === 'pending'))
})
t.test('approve-scripts groups multiple installed versions of the same package', async t => {
// Two versions of lodash exist in the tree; both have install scripts.
// groupByPackage should put them in the same group (hits the
// `if (!groups[key])` falsy branch on the second node).
const { npm, prefix } = await _mockNpm(t, {
prefixDir: twoVersionFixture,
})
await npm.exec('approve-scripts', ['lodash'])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
// Both versions get pinned.
t.strictSame(pkg.allowScripts, {
'lodash@3.10.1': true,
'lodash@4.17.21': true,
})
})
t.test('approve-scripts <pkg@version> pins only the matching installed version', async t => {
const { npm, prefix } = await _mockNpm(t, {
prefixDir: twoVersionFixture,
})
await npm.exec('approve-scripts', ['lodash@4.17.21'])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
t.strictSame(pkg.allowScripts, { 'lodash@4.17.21': true })
})
t.test('approve-scripts <pkg@range> pins only versions satisfying the range', async t => {
const { npm, prefix } = await _mockNpm(t, {
prefixDir: twoVersionFixture,
})
await npm.exec('approve-scripts', ['lodash@^4'])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
t.strictSame(pkg.allowScripts, { 'lodash@4.17.21': true })
})
t.test('approve-scripts --pending handles node with no version', async t => {
// Exercise the ternary's falsy branch in runPending: `node.version ? '@'... : ''`
// when the node has no version field.
const mockSync = await _mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'host', version: '1.0.0' }),
'package-lock.json': JSON.stringify({
name: 'host',
version: '1.0.0',
lockfileVersion: 3,
requires: true,
packages: { '': { name: 'host', version: '1.0.0' } },
}),
node_modules: {},
},
config: { 'allow-scripts-pending': true },
mocks: {
// Make the walker return a synthetic node with no version
'{LIB}/utils/check-allow-scripts.js': async () => [{
node: { packageName: 'no-version-pkg', name: 'no-version-pkg', version: undefined },
scripts: { install: 'do-stuff' },
}],
},
})
await mockSync.npm.exec('approve-scripts', [])
// Output should mention the package without an @version suffix.
t.match(mockSync.joinedOutput(), / no-version-pkg \(install: do-stuff\)/)
})
t.test('approve-scripts --pending --json handles node with no version', async t => {
// Exercise pendingSummary's `version ? ... : display` falsy branch: the
// key is the bare name when the node has no version field.
const { npm, joinedOutput } = await _mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'host', version: '1.0.0' }),
'package-lock.json': JSON.stringify({
name: 'host',
version: '1.0.0',
lockfileVersion: 3,
requires: true,
packages: { '': { name: 'host', version: '1.0.0' } },
}),
node_modules: {},
},
config: { 'allow-scripts-pending': true, json: true },
mocks: {
'{LIB}/utils/check-allow-scripts.js': async () => [{
node: { packageName: 'no-version-pkg', name: 'no-version-pkg', version: undefined },
scripts: { install: 'do-stuff' },
}],
},
})
await npm.exec('approve-scripts', [])
t.strictSame(JSON.parse(joinedOutput()), {
allowScripts: [
{ name: 'no-version-pkg', changes: [{ key: 'no-version-pkg', change: 'pending' }] },
],
})
})
t.test('forbidden semver range in package.json#allowScripts is dropped with a warning', async t => {
// End-to-end: project declares a caret range in allowScripts. The
// resolver must drop the entry, emit a warning, and the matching node
// must remain unreviewed (listed by --pending).
const mock = await _mockNpm(t, {
prefixDir: setupProject({
withScripts: ['canvas'],
// ^0.33.0 is a forbidden range per RFC.
allowScripts: { 'canvas@^0.33.0': true },
}),
config: { 'allow-scripts-pending': true },
})
await mock.npm.exec('approve-scripts', [])
const warnings = mock.logs.warn.byTitle('allow-scripts')
t.ok(
warnings.some(m => /semver ranges/.test(m) && /canvas@\^0\.33\.0/.test(m)),
'resolver emits warning about forbidden range'
)
// canvas was installed with version 1.0.0 (setupProject default) and
// the forbidden allowlist entry was dropped, so canvas appears in the
// pending list.
t.match(mock.joinedOutput(), /canvas@1\.0\.0/)
})
t.test('approve-scripts --pending lists packages that only have binding.gyp', async t => {
// End-to-end: a package with no preinstall/install/postinstall but a
// binding.gyp on disk gets a synthetic `node-gyp rebuild` install
// script. The runtime isNodeGypPackage check must see it and surface
// the package in --pending output.
const mock = await _mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'host',
version: '1.0.0',
dependencies: { 'native-pkg': '*' },
}),
'package-lock.json': JSON.stringify({
name: 'host',
version: '1.0.0',
lockfileVersion: 3,
requires: true,
packages: {
'': { name: 'host', version: '1.0.0', dependencies: { 'native-pkg': '*' } },
'node_modules/native-pkg': {
version: '1.0.0',
resolved: 'https://registry.npmjs.org/native-pkg/-/native-pkg-1.0.0.tgz',
// No hasInstallScript — the synthetic node-gyp injection is
// what we want this test to exercise.
},
},
}),
node_modules: {
'native-pkg': {
'package.json': JSON.stringify({ name: 'native-pkg', version: '1.0.0' }),
// The file that triggers isNodeGypPackage to return true.
'binding.gyp': '{}',
},
},
},
config: { 'allow-scripts-pending': true },
})
await mock.npm.exec('approve-scripts', [])
const out = mock.joinedOutput()
t.match(out, /native-pkg@1\.0\.0/, 'binding.gyp-only package appears in --pending')
t.match(out, /install: node-gyp rebuild/, 'synthetic node-gyp install is named')
})
t.test('approve-scripts --all never approves bundled deps', async t => {
// Bundled deps never run their install scripts and cannot be
// allowlisted. They never reach the unreviewed list, so --all must not
// write a key derived from the bundled tarball's self-claimed identity.
const { npm, logs, prefix } = await _mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'host',
version: '1.0.0',
dependencies: { 'parent-pkg': '*' },
}),
'package-lock.json': JSON.stringify({
name: 'host',
version: '1.0.0',
lockfileVersion: 3,
requires: true,
packages: {
'': { name: 'host', version: '1.0.0', dependencies: { 'parent-pkg': '*' } },
'node_modules/parent-pkg': {
version: '1.0.0',
resolved: 'https://registry.npmjs.org/parent-pkg/-/parent-pkg-1.0.0.tgz',
hasInstallScript: true,
},
'node_modules/parent-pkg/node_modules/inner': {
version: '1.0.0',
inBundle: true,
hasInstallScript: true,
},
},
}),
node_modules: {
'parent-pkg': {
'package.json': JSON.stringify({
name: 'parent-pkg',
version: '1.0.0',
scripts: { install: 'echo install' },
bundleDependencies: ['inner'],
}),
node_modules: {
inner: {
'package.json': JSON.stringify({
name: 'inner',
version: '1.0.0',
scripts: { install: 'echo bundled-install' },
}),
},
},
},
},
},
config: { all: true },
})
await npm.exec('approve-scripts', [])
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
// parent-pkg is approvable. inner is bundled and must be excluded.
t.equal(pkg.allowScripts['parent-pkg@1.0.0'], true,
'non-bundled parent gets approved')
t.notOk(Object.keys(pkg.allowScripts).some(k => k.startsWith('inner')),
'bundled inner is not approved')
t.strictSame(logs.warn.byTitle('approve-scripts'), [],
'no warning; bundled deps are excluded upstream')
})
t.test('approve-scripts <bundled-pkg> positional is ignored', async t => {
// Same protection on the positional path: a user typing a bundled
// package name must not get a policy entry written.
const { npm } = await _mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'host',
version: '1.0.0',
dependencies: { 'parent-pkg': '*' },
}),
'package-lock.json': JSON.stringify({
name: 'host',
version: '1.0.0',
lockfileVersion: 3,
requires: true,
packages: {
'': { name: 'host', version: '1.0.0', dependencies: { 'parent-pkg': '*' } },
'node_modules/parent-pkg': {
version: '1.0.0',
resolved: 'https://registry.npmjs.org/parent-pkg/-/parent-pkg-1.0.0.tgz',
hasInstallScript: true,
},
'node_modules/parent-pkg/node_modules/inner': {
version: '1.0.0',
inBundle: true,
hasInstallScript: true,
},
},
}),
node_modules: {
'parent-pkg': {
'package.json': JSON.stringify({
name: 'parent-pkg',
version: '1.0.0',
scripts: { install: 'echo install' },
bundleDependencies: ['inner'],
}),
node_modules: {
inner: {
'package.json': JSON.stringify({
name: 'inner',
version: '1.0.0',
scripts: { install: 'echo bundled' },
}),
},
},
},
},
},
})
await t.rejects(
npm.exec('approve-scripts', ['inner']),
{ code: 'ENOMATCH' },
'typing the bundled package name does not match any approvable node'
)
})
t.test('approve-scripts --all with only bundled deps has nothing to review', async t => {
const { npm, logs, joinedOutput, prefix } = await _mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'host',
version: '1.0.0',
dependencies: { 'parent-pkg': '*' },
}),
'package-lock.json': JSON.stringify({
name: 'host',
version: '1.0.0',
lockfileVersion: 3,
requires: true,
packages: {
'': { name: 'host', version: '1.0.0', dependencies: { 'parent-pkg': '*' } },
'node_modules/parent-pkg': {
version: '1.0.0',
resolved: 'https://registry.npmjs.org/parent-pkg/-/parent-pkg-1.0.0.tgz',
// parent-pkg has NO install scripts; only the bundled child does.
},
'node_modules/parent-pkg/node_modules/only-bundled': {
version: '1.0.0',
inBundle: true,
hasInstallScript: true,
},
},
}),
node_modules: {
'parent-pkg': {
'package.json': JSON.stringify({
name: 'parent-pkg',
version: '1.0.0',
bundleDependencies: ['only-bundled'],
}),
node_modules: {
'only-bundled': {
'package.json': JSON.stringify({
name: 'only-bundled',
version: '1.0.0',
scripts: { install: 'echo evil' },
}),
},
},
},
},
},
config: { all: true },
})
await npm.exec('approve-scripts', [])
t.match(joinedOutput(), /No packages with unreviewed install scripts/)
t.strictSame(logs.warn.byTitle('approve-scripts'), [],
'no warning; bundled deps are excluded upstream')
// Ensure no policy entry was written.
const pkg = JSON.parse(fs.readFileSync(resolve(prefix, 'package.json'), 'utf8'))
t.notOk(pkg.allowScripts, 'no allowScripts written')
})