npm CLI robot 5338533373
deps: upgrade npm to 10.8.3
PR-URL: https://github.com/nodejs/node/pull/54619
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
2024-09-08 06:09:40 +00:00

462 lines
11 KiB
JavaScript

const t = require('tap')
const fs = require('node:fs/promises')
const nodePath = require('node:path')
const { resolve, basename } = require('node:path')
const _mockNpm = require('../../fixtures/mock-npm')
const { cleanTime } = require('../../fixtures/clean-snapshot')
t.cleanSnapshot = cleanTime
const mockNpm = async (t, { noLog, libnpmexec, initPackageJson, ...opts } = {}) => {
const res = await _mockNpm(t, {
...opts,
mocks: {
...(libnpmexec ? { libnpmexec } : {}),
...(initPackageJson ? { 'init-package-json': initPackageJson } : {}),
},
globals: {
// init-package-json prints directly to console.log
// this avoids poluting test output with those logs
...(noLog ? { 'console.log': () => {} } : {}),
},
})
return res
}
t.test('displays output', async t => {
const { npm, joinedOutput } = await mockNpm(t, {
initPackageJson: async () => {},
})
await npm.exec('init', [])
t.matchSnapshot(joinedOutput(), 'displays helper info')
})
t.test('classic npm init -y', async t => {
const { npm, prefix } = await mockNpm(t, {
config: { yes: true },
noLog: true,
})
await npm.exec('init', [])
const pkg = require(resolve(prefix, 'package.json'))
t.equal(pkg.version, '1.0.0')
t.equal(pkg.license, 'ISC')
})
t.test('classic interactive npm init', async t => {
t.plan(1)
const { npm } = await mockNpm(t, {
initPackageJson: async (path) => {
t.equal(
path,
resolve(npm.localPrefix),
'should start init package.json in expected path'
)
},
})
await npm.exec('init', [])
})
t.test('npm init <arg>', async t => {
t.plan(1)
const { npm } = await mockNpm(t, {
libnpmexec: ({ args }) => {
t.same(
args,
['create-react-app@*'],
'should npx with listed packages'
)
},
})
await npm.exec('init', ['react-app'])
})
t.test('npm init <arg> -- other-args', async t => {
t.plan(1)
const { npm } = await mockNpm(t, {
libnpmexec: ({ args }) => {
t.same(
args,
['create-react-app@*', 'my-path', '--some-option', 'some-value'],
'should npm exec with expected args'
)
},
})
await npm.exec('init', ['react-app', 'my-path', '--some-option', 'some-value'])
})
t.test('npm init @scope/name', async t => {
t.plan(1)
const { npm } = await mockNpm(t, {
libnpmexec: ({ args }) => {
t.same(
args,
['@npmcli/create-something@*'],
'should npx with scoped packages'
)
},
})
await npm.exec('init', ['@npmcli/something'])
})
t.test('npm init @scope@spec', async t => {
t.plan(1)
const { npm } = await mockNpm(t, {
libnpmexec: ({ args }) => {
t.same(
args,
['@npmcli/create@foo'],
'should npx with scoped packages'
)
},
})
await npm.exec('init', ['@npmcli@foo'])
})
t.test('npm init @scope/name@spec', async t => {
t.plan(1)
const { npm } = await mockNpm(t, {
libnpmexec: ({ args }) => {
t.same(
args,
['@npmcli/create-something@foo'],
'should npx with scoped packages'
)
},
})
await npm.exec('init', ['@npmcli/something@foo'])
})
t.test('npm init git spec', async t => {
t.plan(1)
const { npm } = await mockNpm(t, {
libnpmexec: ({ args }) => {
t.same(
args,
['npm/create-something'],
'should npx with git-spec packages'
)
},
})
await npm.exec('init', ['npm/something'])
})
t.test('npm init @scope', async t => {
t.plan(1)
const { npm } = await mockNpm(t, {
libnpmexec: ({ args }) => {
t.same(
args,
['@npmcli/create'],
'should npx with @scope/create pkgs'
)
},
})
await npm.exec('init', ['@npmcli'])
})
t.test('npm init tgz', async t => {
const { npm } = await mockNpm(t)
await t.rejects(
npm.exec('init', ['something.tgz']),
/Unrecognized initializer: something.tgz/,
'should throw error when using an unsupported spec'
)
})
t.test('npm init <arg>@next', async t => {
t.plan(1)
const { npm } = await mockNpm(t, {
libnpmexec: ({ args }) => {
t.same(
args,
['create-something@next'],
'should npx with something@next'
)
},
})
await npm.exec('init', ['something@next'])
})
t.test('npm init exec error', async t => {
const { npm } = await mockNpm(t, {
libnpmexec: async () => {
throw new Error('ERROR')
},
})
await t.rejects(
npm.exec('init', ['something@next']),
/ERROR/,
'should exit with exec error'
)
})
t.test('should not rewrite flatOptions', async t => {
t.plan(1)
const { npm } = await mockNpm(t, {
libnpmexec: async ({ args }) => {
t.same(
args,
['create-react-app@*', 'my-app'],
'should npx with extra args'
)
},
})
await npm.exec('init', ['react-app', 'my-app'])
})
t.test('npm init cancel', async t => {
const { npm, logs } = await mockNpm(t, {
initPackageJson: async () => {
throw new Error('canceled')
},
})
await npm.exec('init', [])
t.equal(logs.warn[0], 'init canceled', 'should have init title and canceled')
})
t.test('npm init error', async t => {
const { npm } = await mockNpm(t, {
initPackageJson: async () => {
throw new Error('Unknown Error')
},
})
await t.rejects(
npm.exec('init', []),
/Unknown Error/,
'should throw error'
)
})
t.test('workspaces', async t => {
await t.test('no args -- yes', async t => {
const { npm, prefix, joinedOutput } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'top-level',
}),
},
config: { workspace: 'a', yes: true },
noLog: true,
})
await npm.exec('init', [])
const pkg = require(resolve(prefix, 'a/package.json'))
t.equal(pkg.name, 'a')
t.equal(pkg.version, '1.0.0')
t.equal(pkg.license, 'ISC')
t.matchSnapshot(joinedOutput(), 'should print helper info')
const lock = require(resolve(prefix, 'package-lock.json'))
t.ok(lock.packages.a)
})
await t.test('no args, existing folder', async t => {
const { npm, prefix } = await mockNpm(t, {
prefixDir: {
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '2.0.0',
}),
},
},
'package.json': JSON.stringify({
name: 'top-level',
workspaces: ['packages/a'],
}),
},
config: { workspace: 'packages/a', yes: true },
noLog: true,
})
await npm.exec('init', [])
const pkg = require(resolve(prefix, 'packages/a/package.json'))
t.equal(pkg.name, 'a')
t.equal(pkg.version, '2.0.0')
t.equal(pkg.license, 'ISC')
})
await t.test('fail parsing top-level package.json to set workspace', async t => {
const { npm } = await mockNpm(t, {
prefixDir: {
'package.json': 'not json[',
},
config: { workspace: 'a', yes: true },
noLog: true,
})
await t.rejects(
npm.exec('init', []),
{ code: 'EJSONPARSE' }
)
})
await t.test('missing top-level package.json when settting workspace', async t => {
const { npm, logs } = await mockNpm(t, {
config: { workspace: 'a' },
})
await t.rejects(
npm.exec('init', []),
{ code: 'ENOENT' },
'should exit with missing package.json file error'
)
t.equal(logs.warn[0], 'init Missing package.json. Try with `--include-workspace-root`.')
})
await t.test('bad package.json when settting workspace', async t => {
const { npm, logs } = await mockNpm(t, {
prefixDir: {
'package.json': '{{{{',
},
config: { workspace: 'a' },
})
await t.rejects(
npm.exec('init', []),
{ code: 'EJSONPARSE' },
'should exit with parse file error'
)
t.strictSame(logs.warn, [])
})
await t.test('using args - no package.json', async t => {
const { npm, prefix } = await mockNpm(t, {
prefixDir: {
b: {
'package.json': JSON.stringify({
name: 'b',
}),
},
'package.json': JSON.stringify({
name: 'top-level',
workspaces: ['b'],
}),
},
// Important: exec did not write a package.json here
libnpmexec: async () => {},
config: { workspace: 'a', yes: true },
})
await npm.exec('init', ['react-app'])
const pkg = require(resolve(prefix, 'package.json'))
t.strictSame(pkg.workspaces, ['b'], 'pkg workspaces did not get updated')
})
await t.test('init template - bad package.json', async t => {
const { npm, prefix } = await mockNpm(t, {
prefixDir: {
b: {
'package.json': JSON.stringify({
name: 'b',
}),
},
'package.json': JSON.stringify({
name: 'top-level',
workspaces: ['b'],
}),
},
initPackageJson: async (...args) => {
const [dir] = args
if (dir.endsWith('c')) {
await fs.writeFile(resolve(dir, 'package.json'), JSON.stringify({
name: basename(dir),
}), 'utf-8')
}
},
config: { yes: true, workspace: ['a', 'c'] },
})
await npm.exec('init', [])
const pkg = require(resolve(prefix, 'package.json'))
t.strictSame(pkg.workspaces, ['b', 'c'])
const lock = require(resolve(prefix, 'package-lock.json'))
t.notOk(lock.packages.a)
})
t.test('workspace root', async t => {
const { npm } = await mockNpm(t, {
config: { workspace: 'packages/a', 'include-workspace-root': true, yes: true },
noLog: true,
})
await npm.exec('init', [])
const pkg = require(resolve(npm.localPrefix, 'package.json'))
t.equal(pkg.version, '1.0.0')
t.equal(pkg.license, 'ISC')
t.strictSame(pkg.workspaces, ['packages/a'])
const ws = require(resolve(npm.localPrefix, 'packages/a/package.json'))
t.equal(ws.version, '1.0.0')
t.equal(ws.license, 'ISC')
})
t.test('init pkg - installed workspace package', async t => {
const { npm } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'init-ws-test',
dependencies: {
'@npmcli/create': '1.0.0',
},
workspaces: ['test/workspace-init-a'],
}),
'test/workspace-init-a': {
'package.json': JSON.stringify({
version: '1.0.0',
name: '@npmcli/create',
bin: { 'init-create': 'index.js' },
}),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-init-test-success', '')
console.log('init-create ran')`,
},
},
})
await npm.exec('install', []) // reify
npm.config.set('workspace', ['test/workspace-init-b'])
await npm.exec('init', ['@npmcli'])
const exists = await fs.stat(nodePath.join(
npm.prefix, 'test/workspace-init-b', 'npm-init-test-success'))
t.ok(exists.isFile(), 'bin ran, creating file inside workspace')
})
})