Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

219
deps/npm/test/lib/arborist-cmd.js vendored Normal file
View File

@ -0,0 +1,219 @@
const { resolve } = require('node:path')
const t = require('tap')
const { load: loadMockNpm } = require('../fixtures/mock-npm')
const tmock = require('../fixtures/tmock')
const mockArboristCmd = async (t, exec, workspace, { mocks = {}, ...opts } = {}) => {
const ArboristCmd = tmock(t, '{LIB}/arborist-cmd.js', mocks)
const config = (typeof workspace === 'function')
? (dirs) => ({ workspace: workspace(dirs) })
: { workspace }
const mock = await loadMockNpm(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'simple-workspaces-list',
version: '1.1.1',
workspaces: [
'a',
'b',
'group/*',
],
}),
node_modules: {
abbrev: {
'package.json': JSON.stringify({ name: 'abbrev', version: '1.1.1' }),
},
a: t.fixture('symlink', '../a'),
b: t.fixture('symlink', '../b'),
},
a: {
'package.json': JSON.stringify({ name: 'a', version: '1.0.0' }),
},
b: {
'package.json': JSON.stringify({ name: 'b', version: '1.0.0' }),
},
group: {
c: {
'package.json': JSON.stringify({
name: 'c',
version: '1.0.0',
dependencies: {
abbrev: '^1.1.1',
},
}),
},
d: {
'package.json': JSON.stringify({ name: 'd', version: '1.0.0' }),
},
},
},
...opts,
})
let execArg
class TestCmd extends ArboristCmd {
async exec (arg) {
execArg = arg
}
}
const cmd = new TestCmd(mock.npm)
if (exec) {
await cmd.execWorkspaces(exec)
}
return { ...mock, cmd, getArg: () => execArg }
}
t.test('arborist-cmd', async t => {
await t.test('single name', async t => {
const { cmd, getArg } = await mockArboristCmd(t, ['foo'], 'a')
t.same(cmd.workspaceNames, ['a'], 'should set array with single ws name')
t.same(getArg(), ['foo'], 'should get received args')
})
await t.test('single path', async t => {
const { cmd } = await mockArboristCmd(t, [], './a')
t.same(cmd.workspaceNames, ['a'], 'should set array with single ws name')
})
await t.test('single full path', async t => {
const { cmd } = await mockArboristCmd(t, [], ({ prefix }) => resolve(prefix, 'a'))
t.same(cmd.workspaceNames, ['a'], 'should set array with single ws name')
})
await t.test('multiple names', async t => {
const { cmd } = await mockArboristCmd(t, [], ['a', 'c'])
t.same(cmd.workspaceNames, ['a', 'c'], 'should set array with single ws name')
})
await t.test('multiple paths', async t => {
const { cmd } = await mockArboristCmd(t, [], ['./a', 'group/c'])
t.same(cmd.workspaceNames, ['a', 'c'], 'should set array with single ws name')
})
await t.test('parent path', async t => {
const { cmd } = await mockArboristCmd(t, [], './group')
t.same(cmd.workspaceNames, ['c', 'd'], 'should set array with single ws name')
})
await t.test('parent path', async t => {
const { cmd } = await mockArboristCmd(t, [], './group')
t.same(cmd.workspaceNames, ['c', 'd'], 'should set array with single ws name')
})
await t.test('prefix inside cwd', async t => {
const { npm, cmd, prefix } = await mockArboristCmd(t, null, ['a', 'c'], {
chdir: (dirs) => dirs.testdir,
})
// TODO there has to be a better way to do this
npm.config.localPrefix = prefix
await cmd.execWorkspaces([])
t.same(cmd.workspaceNames, ['a', 'c'], 'should set array with single ws name')
})
})
t.test('handle getWorkspaces raising an error', async t => {
const { cmd } = await mockArboristCmd(t, null, 'a', {
mocks: {
'{LIB}/utils/get-workspaces.js': async () => {
throw new Error('oopsie')
},
},
})
await t.rejects(
cmd.execWorkspaces(['foo']),
{ message: 'oopsie' }
)
})
t.test('location detection and audit', async (t) => {
await t.test('audit false without package.json', async t => {
const { npm } = await loadMockNpm(t, {
command: 'install',
prefixDir: {
// no package.json
'readme.txt': 'just a file',
'other-dir': { a: 'a' },
},
})
t.equal(npm.config.get('location'), 'user')
t.equal(npm.config.get('audit'), false)
})
await t.test('audit true with package.json', async t => {
const { npm } = await loadMockNpm(t, {
command: 'install',
prefixDir: {
'package.json': '{ "name": "testpkg", "version": "1.0.0" }',
'readme.txt': 'just a file',
},
})
t.equal(npm.config.get('location'), 'user')
t.equal(npm.config.get('audit'), true)
})
await t.test('audit true without package.json when set', async t => {
const { npm } = await loadMockNpm(t, {
command: 'install',
prefixDir: {
// no package.json
'readme.txt': 'just a file',
'other-dir': { a: 'a' },
},
config: {
audit: true,
},
})
t.equal(npm.config.get('location'), 'user')
t.equal(npm.config.get('audit'), true)
})
await t.test('audit true in root config without package.json', async t => {
const { npm } = await loadMockNpm(t, {
command: 'install',
prefixDir: {
// no package.json
'readme.txt': 'just a file',
'other-dir': { a: 'a' },
},
// change npmRoot to get it to use a builtin rc file
otherDirs: { npmrc: 'audit=true' },
npm: ({ other }) => ({ npmRoot: other }),
})
t.equal(npm.config.get('location'), 'user')
t.equal(npm.config.get('audit'), true)
})
await t.test('test for warning when --global & --audit', async t => {
const { npm, logs } = await loadMockNpm(t, {
command: 'install',
prefixDir: {
// no package.json
'readme.txt': 'just a file',
'other-dir': { a: 'a' },
},
config: {
audit: true,
global: true,
},
})
t.equal(npm.config.get('location'), 'user')
t.equal(npm.config.get('audit'), true)
t.equal(logs.warn[0],
'config includes both --global and --audit, which is currently unsupported.')
})
})

10
deps/npm/test/lib/cli.js vendored Normal file
View File

@ -0,0 +1,10 @@
const t = require('tap')
const tmock = require('../fixtures/tmock')
t.test('returns cli-entry function', async t => {
const cli = tmock(t, '{LIB}/cli.js', {
'{LIB}/cli/entry.js': () => 'ENTRY',
})
t.equal(cli(process), 'ENTRY')
})

175
deps/npm/test/lib/cli/entry.js vendored Normal file
View File

@ -0,0 +1,175 @@
const t = require('tap')
const { readdirSync } = require('node:fs')
const { dirname } = require('node:path')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const tmock = require('../../fixtures/tmock.js')
const validateEngines = require('../../../lib/cli/validate-engines.js')
const cliMock = async (t, opts) => {
let exitHandlerArgs = null
let npm = null
const { Npm, ...mock } = await loadMockNpm(t, { ...opts, init: false })
const cli = tmock(t, '{LIB}/cli/entry.js', {
'{LIB}/npm.js': Npm,
'{LIB}/cli/exit-handler.js': class MockExitHandler {
exit (...args) {
exitHandlerArgs = args
npm.unload()
}
registerUncaughtHandlers () {}
setNpm (_npm) {
npm = _npm
}
},
})
return {
...mock,
Npm,
cli: (p) => validateEngines(p, () => cli),
exitHandlerCalled: () => exitHandlerArgs,
exitHandlerNpm: () => npm,
}
}
t.test('print the version ', async t => {
const { logs, cli, Npm, outputs, exitHandlerCalled } = await cliMock(t, {
globals: { 'process.argv': ['node', 'npm', 'root'] },
})
await cli(process)
t.strictSame(process.argv, ['node', 'npm', 'root'], 'system process.argv was rewritten')
t.strictSame(logs.verbose.byTitle('cli'), ['cli node npm'])
t.strictSame(logs.verbose.byTitle('title'), ['title npm root'])
t.match(logs.verbose.byTitle('argv'), ['argv "root"'])
t.strictSame(logs.info, [`using npm@${Npm.version}`, `using node@${process.version}`])
t.equal(outputs.length, 1)
t.match(outputs[0], dirname(process.cwd()))
t.strictSame(exitHandlerCalled(), [])
})
t.test('calling with --versions calls npm version with no args', async t => {
const { logs, cli, outputs, exitHandlerCalled } = await cliMock(t, {
globals: {
'process.argv': ['node', 'npm', 'install', 'or', 'whatever', '--versions', '--json'],
},
})
await cli(process)
t.equal(process.title, 'npm install or whatever')
t.strictSame(logs.verbose.byTitle('cli'), ['cli node npm'])
t.strictSame(logs.verbose.byTitle('title'), ['title npm install or whatever'])
t.match(logs.verbose.byTitle('argv'), ['argv "install" "or" "whatever" "--versions"'])
t.equal(outputs.length, 1)
t.match(JSON.parse(outputs[0]), { npm: String, node: String, v8: String })
t.strictSame(exitHandlerCalled(), [])
})
t.test('logged argv is sanitized', async t => {
const { logs, cli } = await cliMock(t, {
globals: {
'process.argv': [
'node',
'npm',
'version',
'--registry',
'https://u:password@npmjs.org/password',
],
},
})
await cli(process)
t.equal(process.title, 'npm version')
t.strictSame(logs.verbose.byTitle('cli'), ['cli node npm'])
t.strictSame(logs.verbose.byTitle('title'), ['title npm version'])
t.match(logs.verbose.byTitle('argv'),
['argv "version" "--registry" "https://u:***@npmjs.org/password"'])
})
t.test('logged argv is sanitized with equals', async t => {
const { logs, cli } = await cliMock(t, {
globals: {
'process.argv': [
'node',
'npm',
'version',
'--registry=https://u:password@npmjs.org',
],
},
})
await cli(process)
t.match(logs.verbose.byTitle('argv'), ['argv "version" "--registry" "https://u:***@npmjs.org"'])
})
t.test('print usage if no params provided', async t => {
const { cli, outputs, exitHandlerCalled, exitHandlerNpm } = await cliMock(t, {
globals: {
'process.argv': ['node', 'npm'],
},
})
await cli(process)
t.match(outputs[0], 'Usage:', 'outputs npm usage')
t.match(exitHandlerCalled(), [], 'should call exitHandler with no args')
t.ok(exitHandlerNpm(), 'exitHandler npm is set')
t.match(process.exitCode, 1)
})
t.test('load error calls error handler', async t => {
const err = new Error('test load error')
const { cli, exitHandlerCalled } = await cliMock(t, {
mocks: {
'@npmcli/config': class BadConfig {
async load () {
throw err
}
},
},
globals: {
'process.argv': ['node', 'npm', 'asdf'],
},
})
await cli(process)
t.strictSame(exitHandlerCalled(), [err])
})
t.test('unsupported node version', async t => {
const { cli, logs } = await cliMock(t, {
globals: {
'process.version': '12.6.0',
},
})
await cli(process)
t.match(
logs.warn[0],
/npm v.* does not support Node\.js 12\.6\.0\./
)
})
t.test('non-ascii dash', async t => {
const { cli, logs } = await cliMock(t, {
globals: {
'process.argv': ['node', 'npm', 'scope', '\u2010not-a-dash'],
},
})
await cli(process)
t.equal(
logs.error[0],
'arg Argument starts with non-ascii dash, this is probably invalid: \u2010not-a-dash'
)
})
t.test('exit early for --version', async t => {
const { cli, outputs, Npm, cache } = await cliMock(t, {
globals: {
'process.argv': ['node', 'npm', '-v'],
},
})
await cli(process)
t.strictSame(readdirSync(cache), [], 'nothing created in cache')
t.equal(outputs[0], Npm.version)
})

709
deps/npm/test/lib/cli/exit-handler.js vendored Normal file
View File

@ -0,0 +1,709 @@
const fs = require('node:fs')
const { join, resolve } = require('node:path')
const EventEmitter = require('node:events')
const os = require('node:os')
const t = require('tap')
const fsMiniPass = require('fs-minipass')
const { output, time } = require('proc-log')
const errorMessage = require('../../../lib/utils/error-message.js')
const ExecCommand = require('../../../lib/commands/exec.js')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const mockGlobals = require('@npmcli/mock-globals')
const { cleanCwd, cleanDate } = require('../../fixtures/clean-snapshot')
const tmock = require('../../fixtures/tmock')
const { version: NPM_VERSION } = require('../../../package.json')
const pick = (obj, ...keys) => keys.reduce((acc, key) => {
acc[key] = obj[key]
return acc
}, {})
t.formatSnapshot = (obj) => {
if (Array.isArray(obj)) {
return obj
.map((i) => Array.isArray(i) ? i.join(' ') : i)
.join('\n')
}
return obj
}
t.cleanSnapshot = (path) => cleanDate(cleanCwd(path))
// Config loading is dependent on env so strip those from snapshots
.replace(/.*timing config:load:.*\n/gm, '')
// logfile cleaning is not awaited so it races with the process.exit
// in this test and can cause snapshot failures so we always remove them
.replace(/.*silly logfile.*cleaning.*\n/gm, '')
.replace(/(Completed in )\d+(ms)/g, '$1{TIME}$2')
.replace(/(removing )\d+( files)/g, '$1${NUM}2')
.replaceAll(`node ${process.version}`, '{NODE-VERSION}')
.replaceAll(`${os.type()} ${os.release()}`, '{OS}')
.replaceAll(`v${NPM_VERSION}`, '{NPM-VERSION}')
// cut off process from script so that it won't quit the test runner
// while trying to run through the myriad of cases. need to make it
// have all the functions signal-exit relies on so that it doesn't
// nerf itself, thinking global.process is broken or gone.
mockGlobals(t, {
process: Object.assign(new EventEmitter(), {
// these are process properties that are needed in the running code and tests
...pick(process, 'version', 'execPath', 'stdout', 'stderr', 'stdin', 'cwd', 'chdir', 'env', 'umask'),
pid: 123456,
argv: ['/node', ...process.argv.slice(1)],
kill: () => {},
reallyExit: (code) => process.exit(code),
exit (code) {
this.emit('exit', code)
},
}),
}, { replace: true })
const mockExitHandler = async (t, {
config,
mocks = {},
files,
error,
command,
...opts
} = {}) => {
const errors = []
mocks['{LIB}/utils/error-message.js'] = {
...errorMessage,
errorMessage: (err) => ({
...errorMessage.errorMessage(err),
...(files ? { files } : {}),
}),
getError: (...args) => ({
...errorMessage.getError(...args),
...(files ? { files } : {}),
}),
}
if (error) {
mocks[`{LIB}/commands/root.js`] = class {
async exec () {
throw error
}
}
mocks[`{LIB}/commands/exec.js`] = class extends ExecCommand {
async exec (...args) {
await super.exec(...args)
throw error
}
}
}
const { npm, ...rest } = await loadMockNpm(t, {
...opts,
mocks,
config: (dirs) => ({
loglevel: 'notice',
...(typeof config === 'function' ? config(dirs) : config),
}),
globals: {
'console.error': (err) => errors.push(err),
},
})
const ExitHandler = tmock(t, '{LIB}/cli/exit-handler.js', mocks)
const exitHandler = new ExitHandler({ process })
exitHandler.registerUncaughtHandlers()
if (npm) {
exitHandler.setNpm(npm)
}
t.teardown(() => {
process.removeAllListeners('exit')
})
return {
...rest,
errors: () => [
...rest.outputErrors,
...errors,
],
npm,
// Make it async to make testing ergonomics a little easier so we dont need
// to t.plan() every test to make sure we get process.exit called.
exitHandler: (argErr) => new Promise(res => {
process.once('exit', res)
if (npm) {
npm.exec(command || 'root')
.then(() => exitHandler.exit())
.catch((err) => exitHandler.exit(err))
} else {
exitHandler.exit(argErr || error)
}
}),
}
}
// Create errors with properties to be used in tests
const err = (message = '', options = {}, noStack = false) => {
const e = Object.assign(
new Error(message),
typeof options !== 'object' ? { code: options } : options
)
e.stack = options.stack || `Error: ${message}`
if (noStack) {
delete e.stack
}
return e
}
t.test('handles unknown error with logs and debug file', async (t) => {
const { exitHandler, debugFile, logs } = await mockExitHandler(t, {
config: { loglevel: 'silly', timing: true },
error: err('Unknown error', 'ECODE'),
})
await exitHandler()
const fileLogs = await debugFile()
const fileLines = fileLogs.split('\n')
const lineNumber = /^(\d+)\s/
const lastLog = fileLines[fileLines.length - 1].match(lineNumber)[1]
t.equal(process.exitCode, 1)
logs.forEach((logItem, i) => {
const logLines = logItem.split('\n').map(l => `${i} ${l}`)
for (const line of logLines) {
t.match(fileLogs.trim(), line, 'log appears in debug file')
}
})
t.equal(logs.length, parseInt(lastLog) + 1)
t.match(logs.error, [
'code ECODE',
'Unknown error',
'A complete log of this run can be found in:',
])
t.match(fileLogs, /\d+ error code ECODE/)
t.match(fileLogs, /\d+ error Unknown error/)
t.matchSnapshot(logs, 'logs')
t.matchSnapshot(fileLines.map(l => l.replace(lineNumber, 'XX ')), 'debug file contents')
})
t.test('exit handler never called', async t => {
t.test('loglevel silent', async (t) => {
const { logs, errors } = await mockExitHandler(t, {
config: { loglevel: 'silent' },
})
process.emit('exit', 0)
t.strictSame(logs, [])
t.strictSame(errors(), [''], 'one empty string')
t.equal(process.exitCode, 1)
})
t.test('loglevel notice', async (t) => {
const { logs, errors } = await mockExitHandler(t)
process.emit('exit', 2)
t.equal(process.exitCode, 2)
t.match(logs.error, [
'Exit handler never called!',
/error with npm itself/,
])
t.strictSame(errors(), [])
})
t.test('no npm', async (t) => {
const { logs, errors } = await mockExitHandler(t, { init: false })
process.emit('exit', 1)
t.equal(process.exitCode, 1)
t.strictSame(logs.error, [])
t.match(errors(), [`Error: Process exited unexpectedly with code: 1`])
})
})
t.test('exit handler called and no npm', async t => {
t.test('no npm', async (t) => {
const { exitHandler, errors } = await mockExitHandler(t, { init: false })
await exitHandler()
t.equal(process.exitCode, 1)
t.equal(errors().length, 1)
t.match(errors(), [/Exit prior to setting npm in exit handler/])
})
t.test('with error', async (t) => {
const { exitHandler, errors } = await mockExitHandler(t, { init: false })
await exitHandler(err('something happened'))
t.equal(process.exitCode, 1)
t.equal(errors().length, 1)
t.match(errors(), [/Exit prior to setting npm in exit handler/])
t.match(errors(), [/something happened/])
})
t.test('with error without stack', async (t) => {
const { exitHandler, errors } = await mockExitHandler(t, { init: false })
await exitHandler(err('something happened', {}, true))
t.equal(process.exitCode, 1)
t.equal(errors().length, 1)
t.match(errors(), [/Exit prior to setting npm in exit handler/])
t.match(errors(), [/something happened/])
})
})
t.test('standard output using --json', async (t) => {
const { exitHandler, outputs } = await mockExitHandler(t, {
config: { json: true },
error: err('Error: EBADTHING Something happened'),
})
await exitHandler()
t.equal(process.exitCode, 1)
t.same(
JSON.parse(outputs[0]),
{
error: {
code: 'EBADTHING', // should default error code to E[A-Z]+
summary: 'Error: EBADTHING Something happened',
detail: '',
},
},
'should output expected json output'
)
})
t.test('merges output buffers errors with --json', async (t) => {
const { exitHandler, outputs } = await mockExitHandler(t, {
config: { json: true },
error: err('Error: EBADTHING Something happened'),
})
output.buffer({ output_data: 1 })
output.buffer({ more_data: 2 })
await exitHandler()
t.equal(process.exitCode, 1)
t.same(
JSON.parse(outputs[0]),
{
output_data: 1,
more_data: 2,
error: {
code: 'EBADTHING', // should default error code to E[A-Z]+
summary: 'Error: EBADTHING Something happened',
detail: '',
},
},
'should output expected json output'
)
})
t.test('output buffer without json', async (t) => {
const { exitHandler, outputs, logs } = await mockExitHandler(t, {
error: err('Error: EBADTHING Something happened'),
})
output.buffer('output_data')
output.buffer('more_data')
await exitHandler()
t.equal(process.exitCode, 1)
t.same(
outputs,
['output_data', 'more_data'],
'should output expected output'
)
t.match(logs.error, ['code EBADTHING'])
})
t.test('throw a non-error obj', async (t) => {
const { exitHandler, logs } = await mockExitHandler(t, {
error: {
code: 'ESOMETHING',
message: 'foo bar',
},
})
await exitHandler()
t.equal(process.exitCode, 1)
t.match(logs.error, [
"weird error { code: 'ESOMETHING', message: 'foo bar' }",
])
})
t.test('throw a string error', async (t) => {
const { exitHandler, logs } = await mockExitHandler(t, {
error: 'foo bar',
})
await exitHandler()
t.equal(process.exitCode, 1)
t.match(logs.error, [
'foo bar',
])
})
t.test('update notification - shows even with loglevel error', async (t) => {
const { exitHandler, logs, npm } = await mockExitHandler(t, {
config: { loglevel: 'error' },
})
npm.updateNotification = 'you should update npm!'
await exitHandler()
t.match(logs.notice, ['you should update npm!'])
})
t.test('update notification - hidden with silent', async (t) => {
const { exitHandler, logs, npm } = await mockExitHandler(t, {
config: { loglevel: 'silent' },
})
npm.updateNotification = 'you should update npm!'
await exitHandler()
t.strictSame(logs.notice, [])
})
t.test('npm.config not ready', async (t) => {
const { exitHandler, logs, errors } = await mockExitHandler(t, {
load: false,
})
await exitHandler()
t.equal(process.exitCode, 1)
t.equal(errors().length, 1)
t.match(errors(), [
/Exit prior to config file resolving/,
], 'should exit with config error msg')
t.match(errors(), [
/call config.load\(\) before reading values/,
], 'should exit with cause error msg')
t.strictSame(logs, [], 'no logs if it doesnt load')
})
t.test('no logs dir', async (t) => {
const { exitHandler, logs } = await mockExitHandler(t, {
config: { 'logs-max': 0 },
error: new Error('an error'),
})
await exitHandler()
t.match(logs.error, [
'an error',
'Log files were not written due to the config logs-max=0',
])
t.match(logs.filter((l) => l.includes('npm.load.mkdirplogs')), [])
})
t.test('timers fail to write', async (t) => {
const { exitHandler, logs } = await mockExitHandler(t, {
error: new Error('an error'),
config: (dirs) => ({
'logs-dir': resolve(dirs.prefix, 'LOGS_DIR'),
timing: true,
}),
mocks: {
// we want the fs.writeFileSync in the Timers class to fail
'{LIB}/utils/timers.js': tmock(t, '{LIB}/utils/timers.js', {
'node:fs': {
...fs,
writeFileSync: (file, ...rest) => {
if (file.includes('LOGS_DIR')) {
throw new Error('err')
}
return fs.writeFileSync(file, ...rest)
},
},
}),
},
})
await exitHandler()
t.match(logs.warn[0], `timing could not write timing file: Error: err`)
})
t.test('log files fail to write', async (t) => {
const { exitHandler, logs } = await mockExitHandler(t, {
error: new Error('an error'),
config: (dirs) => ({
'logs-dir': resolve(dirs.prefix, 'LOGS_DIR'),
}),
mocks: {
// we want the fsMiniPass.WriteStreamSync in the LogFile class to fail
'{LIB}/utils/log-file.js': tmock(t, '{LIB}/utils/log-file.js', {
'fs-minipass': {
...fsMiniPass,
WriteStreamSync: (file) => {
if (file.includes('LOGS_DIR')) {
throw new Error('err')
}
},
},
}),
},
})
await exitHandler()
t.match(logs.error, ['an error', `error writing to the directory`])
})
t.test('files from error message', async (t) => {
const { exitHandler, logs, cache } = await mockExitHandler(t, {
error: err('Error message'),
files: [
['error-file.txt', '# error file content'],
],
})
await exitHandler()
const logFiles = fs.readdirSync(join(cache, '_logs'))
const errorFileName = logFiles.find(f => f.endsWith('error-file.txt'))
const errorFile = fs.readFileSync(join(cache, '_logs', errorFileName)).toString()
t.match(logs, ['Error message', /For a full report see:\n.*-error-file\.txt/])
t.match(errorFile, '# error file content')
t.match(errorFile, 'Log files:')
})
t.test('files from error message with error', async (t) => {
const { exitHandler, logs } = await mockExitHandler(t, {
error: err('Error message'),
config: (dirs) => ({
'logs-dir': resolve(dirs.prefix, 'LOGS_DIR'),
}),
files: [
['error-file.txt', '# error file content'],
],
mocks: {
'node:fs': {
...fs,
writeFileSync: (dir) => {
if (dir.includes('LOGS_DIR') && dir.endsWith('error-file.txt')) {
throw new Error('err')
}
},
},
},
})
await exitHandler()
t.match(logs.warn[0], /Could not write error message to.*error-file\.txt.*err/)
})
t.test('timing with no error', async (t) => {
const { exitHandler, timingFile, npm, logs } = await mockExitHandler(t, {
config: { timing: true, loglevel: 'silly' },
})
await exitHandler()
const timingFileData = await timingFile()
t.equal(process.exitCode, 0)
const msg = logs.info.byTitle('timing')[0]
t.match(msg, /Timing info written to:/)
t.match(timingFileData, {
metadata: {
command: [],
version: npm.version,
logfiles: [String],
},
timers: {
'npm:load': Number,
npm: Number,
},
})
})
t.test('timing message hidden by loglevel', async (t) => {
const { exitHandler, logs } = await mockExitHandler(t, {
config: { timing: true, loglevel: 'notice' },
})
await exitHandler()
t.equal(process.exitCode, 0)
t.strictSame(logs.info, [], 'no log message')
})
t.test('unfinished timers', async (t) => {
const { exitHandler, timingFile, npm } = await mockExitHandler(t, {
config: { timing: true },
})
time.start('foo')
time.start('bar')
await exitHandler()
const timingFileData = await timingFile()
t.equal(process.exitCode, 0)
t.match(timingFileData, {
metadata: {
command: [],
version: npm.version,
logfiles: [String],
},
timers: {
npm: Number,
},
unfinishedTimers: {
foo: [Number, Number],
bar: [Number, Number],
},
})
})
t.test('uses code from errno', async (t) => {
const { exitHandler, logs } = await mockExitHandler(t, {
error: err('Error with errno', { errno: 127 }),
})
await exitHandler()
t.equal(process.exitCode, 127)
t.match(logs.error, ['errno 127'])
})
t.test('uses code from number', async (t) => {
const { exitHandler, logs } = await mockExitHandler(t, {
error: err('Error with code type number', 404),
})
await exitHandler()
t.equal(process.exitCode, 404)
t.match(logs.error, ['code 404'])
})
t.test('uses all err special properties', async t => {
const keys = ['code', 'syscall', 'file', 'path', 'dest', 'errno']
const properties = keys.reduce((acc, k) => {
acc[k] = `${k}-hey`
return acc
}, {})
const { exitHandler, logs } = await mockExitHandler(t, {
error: err('Error with code type number', properties),
})
await exitHandler()
t.equal(process.exitCode, 1)
t.match(logs.error, keys.map((k) => `${k} ${k}-hey`), 'all special keys get logged')
})
t.test('verbose logs replace info on err props', async t => {
const keys = ['type', 'stack', 'pkgid']
const properties = keys.reduce((acc, k) => {
acc[k] = `${k}-https://user:pass@registry.npmjs.org/`
return acc
}, {})
const { exitHandler, logs } = await mockExitHandler(t, {
error: err('Error with code type number', properties),
config: { loglevel: 'verbose' },
})
await exitHandler()
t.equal(process.exitCode, 1)
t.match(
logs.verbose.filter(l => !/^(logfile|title|argv)/.test(l)),
keys.map((k) => `${k} ${k}-https://user:***@registry.npmjs.org/`),
'all special keys get replaced'
)
})
t.test('call exitHandler with no error', async (t) => {
const { exitHandler, logs } = await mockExitHandler(t)
await exitHandler()
t.equal(process.exitCode, 0)
t.match(logs.error, [])
})
t.test('defaults to log error msg if stack is missing when no npm', async (t) => {
const { exitHandler, logs, errors } = await mockExitHandler(t, {
init: false,
})
await exitHandler(err('Error with no stack', { code: 'ENOSTACK', errno: 127 }, true))
t.equal(process.exitCode, 127)
t.match(errors(), ['Error with no stack'], 'should use error msg')
t.strictSame(logs.error, [])
})
t.test('exits uncleanly when only emitting exit event', async (t) => {
const { logs } = await mockExitHandler(t)
process.emit('exit')
t.match(logs.error, ['Exit handler never called!'])
t.equal(process.exitCode, 1, 'exitCode coerced to 1')
})
t.test('do no fancy handling for shellouts', async t => {
const mockShelloutExit = (t, opts) => mockExitHandler(t, {
command: 'exec',
argv: ['-c', 'exit'],
config: {
timing: false,
progress: false,
...opts.config,
},
...opts,
})
t.test('shellout with a numeric error code', async t => {
const { exitHandler, logs, errors } = await mockShelloutExit(t, {
error: err('', 5),
})
await exitHandler()
t.equal(process.exitCode, 5, 'got expected exit code')
t.strictSame(logs.error, [], 'no noisy warnings')
t.strictSame(logs.warn, [], 'no noisy warnings')
t.strictSame(errors(), [])
})
t.test('shellout without a numeric error code (something in npm)', async t => {
const { exitHandler, logs, errors } = await mockShelloutExit(t, {
error: err('', 'banana stand'),
})
await exitHandler()
t.equal(process.exitCode, 1, 'got expected exit code')
// should log some warnings and errors, because something weird happened
t.strictNotSame(logs.error, [], 'bring the noise')
t.strictSame(errors(), [])
})
t.test('shellout with code=0 (extra weird?)', async t => {
const { exitHandler, logs, errors } = await mockShelloutExit(t, {
error: Object.assign(new Error(), { code: 0 }),
})
await exitHandler()
t.equal(process.exitCode, 1, 'got expected exit code')
t.strictNotSame(logs.error, [], 'bring the noise')
t.strictSame(errors(), [])
})
t.test('shellout with json', async t => {
const { exitHandler, logs, outputs } = await mockShelloutExit(t, {
error: err('', 5),
config: { json: true },
})
await exitHandler()
t.equal(process.exitCode, 5, 'got expected exit code')
t.strictSame(logs.error, [], 'no noisy warnings')
t.strictSame(logs.warn, [], 'no noisy warnings')
t.match(JSON.parse(outputs), {
error: { code: 5, summary: '', detail: '' },
})
})
})

279
deps/npm/test/lib/cli/update-notifier.js vendored Normal file
View File

@ -0,0 +1,279 @@
const t = require('tap')
const { basename } = require('node:path')
const tmock = require('../../fixtures/tmock')
const mockNpm = require('../../fixtures/mock-npm')
const MockRegistry = require('@npmcli/mock-registry')
const mockGlobals = require('@npmcli/mock-globals')
const CURRENT_VERSION = '123.420.69'
const CURRENT_MAJOR = '122.420.69'
const CURRENT_MINOR = '123.419.69'
const CURRENT_PATCH = '123.420.68'
const NEXT_VERSION = '123.421.70'
const NEXT_VERSION_ENGINE_COMPATIBLE = '123.421.60'
const NEXT_VERSION_ENGINE_COMPATIBLE_MINOR = `123.420.70`
const NEXT_VERSION_ENGINE_COMPATIBLE_PATCH = `123.421.58`
const NEXT_MINOR = '123.420.70'
const NEXT_PATCH = '123.421.69'
const CURRENT_BETA = '124.0.0-beta.99999'
const HAVE_BETA = '124.0.0-beta.0'
const packumentResponse = {
_id: 'npm',
name: 'npm',
'dist-tags': {
latest: CURRENT_VERSION,
},
access: 'public',
versions: {
[CURRENT_VERSION]: { version: CURRENT_VERSION, engines: { node: '>1' } },
[CURRENT_MAJOR]: { version: CURRENT_MAJOR, engines: { node: '>1' } },
[CURRENT_MINOR]: { version: CURRENT_MINOR, engines: { node: '>1' } },
[CURRENT_PATCH]: { version: CURRENT_PATCH, engines: { node: '>1' } },
[NEXT_VERSION]: { version: NEXT_VERSION, engines: { node: '>1' } },
[NEXT_MINOR]: { version: NEXT_MINOR, engines: { node: '>1' } },
[NEXT_PATCH]: { version: NEXT_PATCH, engines: { node: '>1' } },
[CURRENT_BETA]: { version: CURRENT_BETA, engines: { node: '>1' } },
[HAVE_BETA]: { version: HAVE_BETA, engines: { node: '>1' } },
[NEXT_VERSION_ENGINE_COMPATIBLE]: {
version: NEXT_VERSION_ENGINE_COMPATIBLE,
engiges: { node: '<=1' },
},
[NEXT_VERSION_ENGINE_COMPATIBLE_MINOR]: {
version: NEXT_VERSION_ENGINE_COMPATIBLE_MINOR,
engines: { node: '<=1' },
},
[NEXT_VERSION_ENGINE_COMPATIBLE_PATCH]: {
version: NEXT_VERSION_ENGINE_COMPATIBLE_PATCH,
engines: { node: '<=1' },
},
},
}
const runUpdateNotifier = async (t, {
STAT_ERROR,
WRITE_ERROR,
PACOTE_ERROR,
PACOTE_MOCK_REQ_COUNT = 1,
STAT_MTIME = 0,
mocks: _mocks = {},
command = 'help',
prefixDir,
version = CURRENT_VERSION,
argv = [],
wroteFile = false,
...config
} = {}) => {
const mockFs = {
...require('node:fs/promises'),
stat: async (path) => {
if (basename(path) !== '_update-notifier-last-checked') {
t.fail('no stat allowed for non upate notifier files')
}
if (STAT_ERROR) {
throw STAT_ERROR
}
return { mtime: new Date(STAT_MTIME) }
},
writeFile: async (path, content) => {
wroteFile = true
if (content !== '') {
t.fail('no write file content allowed')
}
if (basename(path) !== '_update-notifier-last-checked') {
t.fail('no writefile allowed for non upate notifier files')
}
if (WRITE_ERROR) {
throw WRITE_ERROR
}
},
}
const mocks = {
'node:fs/promises': mockFs,
'{ROOT}/package.json': { version },
'ci-info': { isCI: false, name: null },
..._mocks,
}
const mock = await mockNpm(t, {
command,
mocks,
config,
exec: true,
prefixDir,
argv,
})
const registry = new MockRegistry({
tap: t,
registry: mock.npm.config.get('registry'),
})
if (PACOTE_MOCK_REQ_COUNT > 0) {
registry.nock.get('/npm').times(PACOTE_MOCK_REQ_COUNT).reply(200, packumentResponse)
}
const updateNotifier = tmock(t, '{LIB}/cli/update-notifier.js', mocks)
const result = await updateNotifier(mock.npm)
return {
wroteFile,
result,
}
}
t.test('duration has elapsed, no updates', async t => {
const { wroteFile, result } = await runUpdateNotifier(t)
t.equal(wroteFile, true)
t.not(result)
})
t.test('situations in which we do not notify', t => {
t.test('nothing to do if notifier disabled', async t => {
const { wroteFile, result } = await runUpdateNotifier(t, {
PACOTE_MOCK_REQ_COUNT: 0,
'update-notifier': false,
})
t.equal(wroteFile, false)
t.equal(result, null)
})
t.test('do not suggest update if already updating', async t => {
const { wroteFile, result } = await runUpdateNotifier(t, {
PACOTE_MOCK_REQ_COUNT: 0,
command: 'install',
prefixDir: { 'package.json': `{"name":"${t.testName}"}` },
argv: ['npm'],
global: true,
})
t.equal(wroteFile, false)
t.equal(result, null)
})
t.test('do not suggest update if already updating with spec', async t => {
const { wroteFile, result } = await runUpdateNotifier(t, {
PACOTE_MOCK_REQ_COUNT: 0,
command: 'install',
prefixDir: { 'package.json': `{"name":"${t.testName}"}` },
argv: ['npm@latest'],
global: true,
})
t.equal(wroteFile, false)
t.equal(result, null)
})
t.test('do not update if same as latest', async t => {
const { wroteFile, result } = await runUpdateNotifier(t)
t.equal(wroteFile, true)
t.equal(result, null)
})
t.test('check if stat errors (here for coverage)', async t => {
const STAT_ERROR = new Error('blorg')
const { wroteFile, result } = await runUpdateNotifier(t, { STAT_ERROR })
t.equal(wroteFile, true)
t.equal(result, null)
})
t.test('ok if write errors (here for coverage)', async t => {
const WRITE_ERROR = new Error('grolb')
const { wroteFile, result } = await runUpdateNotifier(t, { WRITE_ERROR })
t.equal(wroteFile, true)
t.equal(result, null)
})
t.test('ignore pacote failures (here for coverage)', async t => {
const PACOTE_ERROR = new Error('pah-KO-tchay')
const { wroteFile, result } = await runUpdateNotifier(t, {
PACOTE_ERROR, PACOTE_MOCK_REQ_COUNT: 0,
})
t.equal(result, null)
t.equal(wroteFile, true)
})
t.test('do not update if newer than latest, but same as next', async t => {
const {
wroteFile,
result,
} = await runUpdateNotifier(t, { version: NEXT_VERSION })
t.equal(result, null)
t.equal(wroteFile, true)
})
t.test('do not update if on the latest beta', async t => {
const {
wroteFile,
result,
} = await runUpdateNotifier(t, { version: CURRENT_BETA })
t.equal(result, null)
t.equal(wroteFile, true)
})
t.test('do not update in CI', async t => {
const { wroteFile, result } = await runUpdateNotifier(t, { mocks: {
'ci-info': { isCI: true, name: 'something' },
},
PACOTE_MOCK_REQ_COUNT: 0 })
t.equal(wroteFile, false)
t.equal(result, null)
})
t.test('only check weekly for GA releases', async t => {
// One week (plus five minutes to account for test environment fuzziness)
const STAT_MTIME = Date.now() - 1000 * 60 * 60 * 24 * 7 + 1000 * 60 * 5
const { wroteFile, result } = await runUpdateNotifier(t, {
STAT_MTIME,
PACOTE_MOCK_REQ_COUNT: 0,
})
t.equal(wroteFile, false, 'duration was not reset')
t.equal(result, null)
})
t.test('only check daily for betas', async t => {
// One day (plus five minutes to account for test environment fuzziness)
const STAT_MTIME = Date.now() - 1000 * 60 * 60 * 24 + 1000 * 60 * 5
const {
wroteFile,
result,
} = await runUpdateNotifier(t, { STAT_MTIME, version: HAVE_BETA, PACOTE_MOCK_REQ_COUNT: 0 })
t.equal(wroteFile, false, 'duration was not reset')
t.equal(result, null)
})
t.end()
})
t.test('notification situation with engine compatibility', async t => {
// no version which are greater than node 1.0.0 should be selected.
mockGlobals(t, { 'process.version': 'v1.0.0' }, { replace: true })
const {
wroteFile,
result,
} = await runUpdateNotifier(t, {
version: NEXT_VERSION_ENGINE_COMPATIBLE_MINOR,
PACOTE_MOCK_REQ_COUNT: 1 })
t.matchSnapshot(result)
t.equal(wroteFile, true)
})
t.test('notification situations', async t => {
const cases = {
[HAVE_BETA]: 1,
[NEXT_PATCH]: 2,
[NEXT_MINOR]: 2,
[CURRENT_PATCH]: 1,
[CURRENT_MINOR]: 1,
[CURRENT_MAJOR]: 1,
}
for (const [version, requestCount] of Object.entries(cases)) {
for (const color of [false, 'always']) {
await t.test(`${version} - color=${color}`, async t => {
const {
wroteFile,
result,
} = await runUpdateNotifier(t, { version, color, PACOTE_MOCK_REQ_COUNT: requestCount })
t.matchSnapshot(result)
t.equal(wroteFile, true)
})
}
}
})

View File

@ -0,0 +1,33 @@
const t = require('tap')
const mockGlobals = require('@npmcli/mock-globals')
const tmock = require('../../fixtures/tmock')
const mockValidateEngines = (t) => {
const validateEngines = tmock(t, '{LIB}/cli/validate-engines.js', {
'{ROOT}/package.json': { version: '1.2.3', engines: { node: '>=0' } },
})
mockGlobals(t, { 'process.version': 'v4.5.6' })
return validateEngines(process, () => (_, r) => r)
}
t.test('validate engines', async t => {
t.equal(process.listenerCount('uncaughtException'), 0)
t.equal(process.listenerCount('unhandledRejection'), 0)
const result = mockValidateEngines(t)
t.equal(process.listenerCount('uncaughtException'), 1)
t.equal(process.listenerCount('unhandledRejection'), 1)
t.match(result, {
node: 'v4.5.6',
npm: 'v1.2.3',
engines: '>=0',
unsupportedMessage: 'npm v1.2.3 does not support Node.js v4.5.6. This version of npm supports the following node versions: `>=0`. You can find the latest version at https://nodejs.org/.',
})
result.off()
t.equal(process.listenerCount('uncaughtException'), 0)
t.equal(process.listenerCount('unhandledRejection'), 0)
})

385
deps/npm/test/lib/commands/access.js vendored Normal file
View File

@ -0,0 +1,385 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const MockRegistry = require('@npmcli/mock-registry')
const token = 'test-auth-token'
const auth = { '//registry.npmjs.org/:_authToken': 'test-auth-token' }
t.test('completion', async t => {
const { access } = await loadMockNpm(t, { command: 'access' })
const testComp = (argv, expect) => {
const res = access.completion({ conf: { argv: { remain: argv } } })
t.resolves(res, expect, argv.join(' '))
}
testComp(['npm', 'access'], ['list', 'get', 'set', 'grant', 'revoke'])
testComp(['npm', 'access', 'list'], ['packages', 'collaborators'])
testComp(['npm', 'access', 'ls'], ['packages', 'collaborators'])
testComp(['npm', 'access', 'get'], ['status'])
testComp(['npm', 'access', 'set'], [
'status=public',
'status=private',
'mfa=none',
'mfa=publish',
'mfa=automation',
'2fa=none',
'2fa=publish',
'2fa=automation',
])
testComp(['npm', 'access', 'grant'], ['read-only', 'read-write'])
testComp(['npm', 'access', 'revoke'], [])
testComp(['npm', 'access', 'grant', ''], [])
await t.rejects(
access.completion({ conf: { argv: { remain: ['npm', 'access', 'foobar'] } } }),
{ message: 'foobar not recognized' }
)
})
t.test('command required', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(npm.exec('access', []), { code: 'EUSAGE' })
})
t.test('unrecognized command', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', ['blerg']), { code: 'EUSAGE' })
})
t.test('subcommand required', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(npm.exec('access', ['get']), { code: 'EUSAGE' })
})
t.test('unrecognized subcommand', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(npm.exec('access', ['list', 'blerg']), { code: 'EUSAGE' })
})
t.test('grant', t => {
t.test('invalid permissions', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(npm.exec('access', ['grant', 'other']), { code: 'EUSAGE' })
})
t.test('no permissions', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(npm.exec('access', ['grant', 'read-only']), { code: 'EUSAGE' })
})
t.test('read-only', async t => {
const authToken = 'abcd1234'
const { npm } = await loadMockNpm(t, {
config: {
'//registry.npmjs.org/:_authToken': authToken,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
const permissions = 'read-only'
registry.setPermissions({ spec: '@npmcli/test-package', team: '@npm:test-team', permissions })
await npm.exec('access', ['grant', permissions, '@npm:test-team', '@npmcli/test-package'])
})
t.end()
})
t.test('revoke', t => {
t.test('success', async t => {
const authToken = 'abcd1234'
const { npm } = await loadMockNpm(t, {
config: {
'//registry.npmjs.org/:_authToken': authToken,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
registry.removePermissions({ spec: '@npmcli/test-package', team: '@npm:test-team' })
await npm.exec('access', ['revoke', '@npm:test-team', '@npmcli/test-package'])
})
t.end()
})
t.test('list', t => {
const packages = {
'@npmcli/test-package': 'read',
'@npmcli/other-package': 'write',
}
const collaborators = {
npm: 'write',
github: 'read',
}
t.test('invalid subcommand', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(npm.exec('access', ['list', 'other'], { code: 'EUSAGE' }))
})
t.test('packages explicit user', async t => {
const { npm, outputs } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.getPackages({ team: '@npm:test-team', packages })
await npm.exec('access', ['list', 'packages', '@npm:test-team'])
t.same(outputs, [
'@npmcli/other-package: read-write',
'@npmcli/test-package: read-only',
])
})
t.test('packages infer user', async t => {
const { npm, outputs } = await loadMockNpm(t, { config: { ...auth } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: token,
})
registry.whoami({ username: 'npm' })
registry.getPackages({ team: 'npm', packages })
await npm.exec('access', ['list', 'packages'])
t.same(outputs, [
'@npmcli/other-package: read-write',
'@npmcli/test-package: read-only',
])
})
t.test('packages json', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { json: true } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.getPackages({ team: '@npm:test-team', packages })
await npm.exec('access', ['list', 'packages', '@npm:test-team'])
t.same(JSON.parse(joinedOutput()), {
'@npmcli/test-package': 'read-only',
'@npmcli/other-package': 'read-write',
})
})
t.test('collaborators explicit package', async t => {
const { npm, outputs } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.getCollaborators({ spec: '@npmcli/test-package', collaborators })
await npm.exec('access', ['list', 'collaborators', '@npmcli/test-package'])
t.same(outputs, [
'github: read-only',
'npm: read-write',
])
})
t.test('collaborators user', async t => {
const { npm, outputs } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.getCollaborators({ spec: '@npmcli/test-package', collaborators })
await npm.exec('access', ['list', 'collaborators', '@npmcli/test-package', 'npm'])
t.same(outputs, [
'npm: read-write',
])
})
t.end()
})
t.test('get', t => {
t.test('invalid subcommand', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(npm.exec('access', ['get', 'other'], { code: 'EUSAGE' }))
})
t.test('status explicit package', async t => {
const { npm, outputs } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.getVisibility({ spec: '@npmcli/test-package', visibility: { public: true } })
await npm.exec('access', ['get', 'status', '@npmcli/test-package'])
t.same(outputs, ['@npmcli/test-package: public'])
})
t.test('status implicit package', async t => {
const { npm, outputs } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: '@npmcli/test-package' }),
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.getVisibility({ spec: '@npmcli/test-package', visibility: { public: true } })
await npm.exec('access', ['get', 'status'])
t.same(outputs, ['@npmcli/test-package: public'])
})
t.test('status no package', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', ['get', 'status']),
{ code: 'ENOENT' }
)
})
t.test('status invalid package', async t => {
const { npm } = await loadMockNpm(t, {
prefixDir: { 'package.json': '[not:valid_json}' },
})
await t.rejects(
npm.exec('access', ['get', 'status']),
{ code: 'EJSONPARSE' }
)
})
t.test('status json', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { json: true } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.getVisibility({ spec: '@npmcli/test-package', visibility: { public: true } })
await npm.exec('access', ['get', 'status', '@npmcli/test-package'])
t.same(JSON.parse(joinedOutput()), { '@npmcli/test-package': 'public' })
})
t.end()
})
t.test('set', t => {
t.test('status=public', async t => {
const { npm, outputs } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.setAccess({ spec: '@npmcli/test-package', body: { access: 'public' } })
registry.getVisibility({ spec: '@npmcli/test-package', visibility: { public: true } })
await npm.exec('access', ['set', 'status=public', '@npmcli/test-package'])
t.same(outputs, ['@npmcli/test-package: public'])
})
t.test('status=private', async t => {
const { npm, outputs } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.setAccess({ spec: '@npmcli/test-package', body: { access: 'restricted' } })
registry.getVisibility({ spec: '@npmcli/test-package', visibility: { public: false } })
await npm.exec('access', ['set', 'status=private', '@npmcli/test-package'])
t.same(outputs, ['@npmcli/test-package: private'])
})
t.test('status=invalid', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', ['set', 'status=invalid', '@npmcli/test-package']),
{ code: 'EUSAGE' }
)
})
t.test('status non scoped package', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', ['set', 'status=public', 'npm']),
{ code: 'EUSAGE' }
)
})
t.test('mfa=none', async t => {
const { npm } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.setAccess({ spec: '@npmcli/test-package',
body: {
publish_requires_tfa: false,
} })
await npm.exec('access', ['set', 'mfa=none', '@npmcli/test-package'])
})
t.test('mfa=publish', async t => {
const { npm } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.setAccess({ spec: '@npmcli/test-package',
body: {
publish_requires_tfa: true,
automation_token_overrides_tfa: false,
} })
await npm.exec('access', ['set', 'mfa=publish', '@npmcli/test-package'])
})
t.test('mfa=automation', async t => {
const { npm } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.setAccess({ spec: '@npmcli/test-package',
body: {
publish_requires_tfa: true,
automation_token_overrides_tfa: true,
} })
await npm.exec('access', ['set', 'mfa=automation', '@npmcli/test-package'])
})
t.test('mfa=invalid', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', ['set', 'mfa=invalid']),
{ code: 'EUSAGE' }
)
})
t.test('2fa=none', async t => {
const { npm } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.setAccess({ spec: '@npmcli/test-package',
body: {
publish_requires_tfa: false,
} })
await npm.exec('access', ['set', '2fa=none', '@npmcli/test-package'])
})
t.test('2fa=publish', async t => {
const { npm } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.setAccess({ spec: '@npmcli/test-package',
body: {
publish_requires_tfa: true,
automation_token_overrides_tfa: false,
} })
await npm.exec('access', ['set', '2fa=publish', '@npmcli/test-package'])
})
t.test('2fa=automation', async t => {
const { npm } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.setAccess({ spec: '@npmcli/test-package',
body: {
publish_requires_tfa: true,
automation_token_overrides_tfa: true,
} })
await npm.exec('access', ['set', '2fa=automation', '@npmcli/test-package'])
})
t.test('2fa=invalid', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('access', ['set', '2fa=invalid']),
{ code: 'EUSAGE' }
)
})
t.end()
})

181
deps/npm/test/lib/commands/adduser.js vendored Normal file
View File

@ -0,0 +1,181 @@
const t = require('tap')
const fs = require('node:fs')
const path = require('node:path')
const ini = require('ini')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const mockGlobals = require('@npmcli/mock-globals')
const MockRegistry = require('@npmcli/mock-registry')
const stream = require('node:stream')
const mockAddUser = async (t, { stdin: stdinLines, registry: registryUrl, ...options } = {}) => {
if (stdinLines) {
const stdin = new stream.PassThrough()
for (const l of stdinLines) {
stdin.write(l + '\n')
}
mockGlobals(t, {
'process.stdin': stdin,
'process.stdout': new stream.PassThrough(), // to quiet readline
}, { replace: true })
}
const mock = await loadMockNpm(t, {
...options,
command: 'adduser',
})
const registry = new MockRegistry({
tap: t,
registry: registryUrl ?? mock.npm.config.get('registry'),
})
return {
registry,
rc: () => ini.parse(fs.readFileSync(path.join(mock.home, '.npmrc'), 'utf8')),
...mock,
}
}
t.test('usage', async t => {
const { adduser } = await loadMockNpm(t, { command: 'adduser' })
t.match(adduser.usage, 'adduser', 'usage has command name in it')
})
t.test('legacy', async t => {
t.test('simple adduser', async t => {
const { npm, rc, registry, adduser } = await mockAddUser(t, {
stdin: ['test-user', 'test-password', 'test-email@npmjs.org'],
config: { 'auth-type': 'legacy' },
homeDir: {
'.npmrc': [
'//registry.npmjs.org/:_authToken=user',
'//registry.npmjs.org/:always-auth=user',
'//registry.npmjs.org/:email=test-email-old@npmjs.org',
].join('\n'),
},
})
registry.couchadduser({
username: 'test-user',
password: 'test-password',
email: 'test-email@npmjs.org',
token: 'npm_test-token',
})
await adduser.exec([])
t.same(npm.config.get('email'), 'test-email-old@npmjs.org')
t.same(npm.config.get('//registry.npmjs.org/:_authToken'), 'npm_test-token')
t.same(rc(), {
'//registry.npmjs.org/:_authToken': 'npm_test-token',
email: 'test-email-old@npmjs.org',
}, 'should only have token and un-nerfed old email')
})
t.test('scoped adduser', async t => {
const { npm, rc, registry, adduser } = await mockAddUser(t, {
stdin: ['test-user', 'test-password', 'test-email@npmjs.org'],
config: {
'auth-type': 'legacy',
scope: '@myscope',
},
})
registry.couchadduser({
username: 'test-user',
password: 'test-password',
email: 'test-email@npmjs.org',
token: 'npm_test-token',
})
await adduser.exec([])
t.same(npm.config.get('//registry.npmjs.org/:_authToken'), 'npm_test-token')
t.same(npm.config.get('@myscope:registry'), 'https://registry.npmjs.org/')
t.same(rc(), {
'//registry.npmjs.org/:_authToken': 'npm_test-token',
'@myscope:registry': 'https://registry.npmjs.org/',
}, 'should only have token and scope:registry')
})
t.test('scoped adduser with valid scoped registry config', async t => {
const { npm, rc, registry, adduser } = await mockAddUser(t, {
stdin: ['test-user', 'test-password', 'test-email@npmjs.org'],
registry: 'https://diff-registry.npmjs.org',
homeDir: {
'.npmrc': '@myscope:registry=https://diff-registry.npmjs.org',
},
config: {
'auth-type': 'legacy',
scope: '@myscope',
},
})
registry.couchadduser({
username: 'test-user',
password: 'test-password',
email: 'test-email@npmjs.org',
token: 'npm_test-token',
})
await adduser.exec([])
t.same(npm.config.get('//diff-registry.npmjs.org/:_authToken'), 'npm_test-token')
t.same(npm.config.get('@myscope:registry'), 'https://diff-registry.npmjs.org')
t.same(rc(), {
'@myscope:registry': 'https://diff-registry.npmjs.org',
'//diff-registry.npmjs.org/:_authToken': 'npm_test-token',
}, 'should only have token and scope:registry')
})
t.test('save config failure', async t => {
const { registry, adduser } = await mockAddUser(t, {
stdin: ['test-user', 'test-password', 'test-email@npmjs.org'],
config: { 'auth-type': 'legacy' },
homeDir: {
'.npmrc': {},
},
})
registry.couchadduser({
username: 'test-user',
password: 'test-password',
email: 'test-email@npmjs.org',
token: 'npm_test-token',
})
await t.rejects(adduser.exec([]))
})
t.end()
})
t.test('web', t => {
t.test('basic adduser', async t => {
const { npm, rc, registry, adduser } = await mockAddUser(t, {
config: { 'auth-type': 'web' },
})
registry.webadduser({ token: 'npm_test-token' })
await adduser.exec([])
t.same(npm.config.get('//registry.npmjs.org/:_authToken'), 'npm_test-token')
t.same(rc(), {
'//registry.npmjs.org/:_authToken': 'npm_test-token',
})
})
t.test('server error', async t => {
const { adduser, registry } = await mockAddUser(t, {
config: { 'auth-type': 'web' },
})
registry.nock.post(registry.fullPath('/-/v1/login'))
.reply(503, {})
await t.rejects(
adduser.exec([]),
{ message: /503/ }
)
})
t.test('fallback', async t => {
const { npm, registry, adduser } = await mockAddUser(t, {
stdin: ['test-user', 'test-password', 'test-email@npmjs.org'],
config: { 'auth-type': 'web' },
})
registry.nock.post(registry.fullPath('/-/v1/login'))
.reply(404, {})
registry.couchadduser({
username: 'test-user',
password: 'test-password',
email: 'test-email@npmjs.org',
token: 'npm_test-token',
})
await adduser.exec([])
t.same(npm.config.get('//registry.npmjs.org/:_authToken'), 'npm_test-token')
})
t.end()
})

2109
deps/npm/test/lib/commands/audit.js vendored Normal file

File diff suppressed because it is too large Load Diff

96
deps/npm/test/lib/commands/bugs.js vendored Normal file
View File

@ -0,0 +1,96 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const pacote = {
manifest: async (spec) => {
return spec === 'nobugs' ? {
name: 'nobugs',
version: '1.2.3',
} : spec === 'nullbugs' ? {
name: 'nullbugs',
version: '1.2.3',
bugs: null,
} : spec === 'bugsurl' ? {
name: 'bugsurl',
version: '1.2.3',
bugs: 'https://bugzilla.localhost/bugsurl',
} : spec === 'bugsobj' ? {
name: 'bugsobj',
version: '1.2.3',
bugs: { url: 'https://bugzilla.localhost/bugsobj' },
} : spec === 'bugsobj-nourl' ? {
name: 'bugsobj-nourl',
version: '1.2.3',
bugs: { no: 'url here' },
} : spec === 'repourl' ? {
name: 'repourl',
version: '1.2.3',
repository: 'https://github.com/foo/repourl',
} : spec === 'repoobj' ? {
name: 'repoobj',
version: '1.2.3',
repository: { url: 'https://github.com/foo/repoobj' },
} : spec === 'mailtest' ? {
name: 'mailtest',
version: '3.7.4',
bugs: { email: 'hello@example.com' },
} : spec === 'secondmailtest' ? {
name: 'secondmailtest',
version: '0.1.1',
bugs: { email: 'ABC432abc@a.b.example.net' },
} : spec === '.' ? {
name: 'thispkg',
version: '1.2.3',
bugs: 'https://example.com',
} : null
},
}
t.test('usage', async (t) => {
const { bugs } = await loadMockNpm(t, { command: 'bugs' })
t.match(bugs.usage, 'bugs', 'usage has command name in it')
})
t.test('open bugs urls & emails', async t => {
// keep a tally of which urls got opened
let opened = {}
const openUrl = async (_, url) => {
opened[url] = opened[url] || 0
opened[url]++
}
const { npm } = await loadMockNpm(t, {
mocks: {
pacote,
'{LIB}/utils/open-url.js': { openUrl },
},
})
const expected = {
'.': 'https://example.com',
nobugs: 'https://www.npmjs.com/package/nobugs',
nullbugs: 'https://www.npmjs.com/package/nullbugs',
'bugsobj-nourl': 'https://www.npmjs.com/package/bugsobj-nourl',
bugsurl: 'https://bugzilla.localhost/bugsurl',
bugsobj: 'https://bugzilla.localhost/bugsobj',
repourl: 'https://github.com/foo/repourl/issues',
repoobj: 'https://github.com/foo/repoobj/issues',
mailtest: 'mailto:hello@example.com',
secondmailtest: 'mailto:ABC432abc@a.b.example.net',
}
for (const [pkg, expect] of Object.entries(expected)) {
await t.test(pkg, async t => {
await npm.exec('bugs', [pkg])
t.equal(opened[expect], 1, 'opened expected url', { opened })
})
}
opened = {}
await t.test('open default package if none specified', async t => {
await npm.exec('bugs', [])
t.equal(opened['https://example.com'], 1, 'opened expected url', { opened })
})
})

717
deps/npm/test/lib/commands/cache.js vendored Normal file
View File

@ -0,0 +1,717 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const MockRegistry = require('@npmcli/mock-registry')
const cacache = require('cacache')
const fs = require('node:fs')
const path = require('node:path')
const { cleanCwd } = require('../../fixtures/clean-snapshot.js')
const pkg = 'test-package'
const createNpxCacheEntry = (npxCacheDir, hash, pkgJson, shrinkwrapJson) => {
fs.mkdirSync(path.join(npxCacheDir, hash))
fs.writeFileSync(
path.join(npxCacheDir, hash, 'package.json'),
JSON.stringify(pkgJson)
)
if (shrinkwrapJson) {
fs.writeFileSync(
path.join(npxCacheDir, hash, 'npm-shrinkwrap.json'),
JSON.stringify(shrinkwrapJson)
)
}
}
t.cleanSnapshot = str => {
return cleanCwd(str)
.replace(/Finished in [0-9.s]+/g, 'Finished in xxxs')
.replace(/Cache verified and compressed (.*)/, 'Cache verified and compressed ({PATH})')
}
t.test('cache no args', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('cache', []),
{ code: 'EUSAGE' },
'should throw usage instructions'
)
})
t.test('cache clean', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('cache', ['clean']),
/the npm cache self-heals/,
'should throw warning'
)
})
t.test('cache clean (force)', async t => {
const { npm } = await loadMockNpm(t, {
cacheDir: { _cacache: {} },
config: { force: true },
})
const cache = path.join(npm.cache, '_cacache')
await npm.exec('cache', ['clean'])
t.notOk(fs.existsSync(cache), 'cache dir was removed')
})
t.test('cache add no arg', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('cache', ['add']),
{
code: 'EUSAGE',
message: 'First argument to `add` is required',
},
'throws usage error'
)
})
t.test('cache add single pkg', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
package: {
'package.json': JSON.stringify({
name: pkg,
version: '1.0.0',
}),
},
},
})
const cache = path.join(npm.cache, '_cacache')
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({ name: pkg })
await registry.package({
manifest,
times: 2,
tarballs: { '1.0.0': path.join(npm.prefix, 'package') },
})
await npm.exec('cache', ['add', pkg])
t.equal(joinedOutput(), '')
t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package/-/test-package-1.0.0.tgz'))
t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package'))
})
t.test('cache add multiple pkgs', async t => {
const pkg2 = 'test-package-two'
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
package: {
'package.json': JSON.stringify({
name: pkg,
version: '1.0.0',
}),
},
},
})
const cache = path.join(npm.cache, '_cacache')
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({ name: pkg })
const manifest2 = registry.manifest({ name: pkg2 })
await registry.package({
manifest,
times: 2,
tarballs: { '1.0.0': path.join(npm.prefix, 'package') },
})
await registry.package({
manifest: manifest2, times: 2, tarballs: { '1.0.0': path.join(npm.prefix, 'package') },
})
await npm.exec('cache', ['add', pkg, pkg2])
t.equal(joinedOutput(), '')
t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package/-/test-package-1.0.0.tgz'))
t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package'))
t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package-two/-/test-package-two-1.0.0.tgz'))
t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package-two'))
})
t.test('cache ls', async t => {
const keys = [
'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package',
'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package/-/test-package-1.0.0.tgz',
]
const { npm, joinedOutput } = await loadMockNpm(t)
const cache = path.join(npm.cache, '_cacache')
for (const key of keys) {
await cacache.put(cache, key, 'test data')
}
await npm.exec('cache', ['ls'])
t.matchSnapshot(joinedOutput(), 'logs cache entries')
})
t.test('cache ls pkgs', async t => {
const keys = [
'make-fetch-happen:request-cache:https://registry.npmjs.org/npm',
'make-fetch-happen:request-cache:https://registry.npmjs.org/npm/-/npm-1.2.0.tgz',
'make-fetch-happen:request-cache:https://registry.npmjs.org/webpack/-/webpack-4.47.0.tgz',
'make-fetch-happen:request-cache:https://registry.npmjs.org/webpack/-/webpack-4.40.0.tgz',
]
const { npm, joinedOutput } = await loadMockNpm(t)
const cache = path.join(npm.cache, '_cacache')
for (const key of keys) {
await cacache.put(cache, key, 'test data')
}
await cacache.put(cache,
'make-fetch-happen:request-cache:https://registry.npmjs.org/webpack',
JSON.stringify({ versions: {
'4.40.0': { dist: { tarball: 'https://registry.npmjs.org/webpack/-/webpack-4.40.0.tgz' } },
'4.47.0': { dist: { tarball: 'https://registry.npmjs.org/webpack/-/webpack-4.47.0.tgz' } },
} })
)
await npm.exec('cache', ['ls', 'webpack@>4.44.1', 'npm'])
t.matchSnapshot(joinedOutput(), 'logs cache entries for npm and webpack and one webpack tgz')
})
t.test('cache ls special', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const cache = path.join(npm.cache, '_cacache')
await cacache.put(cache,
'make-fetch-happen:request-cache:https://registry.npmjs.org/foo',
JSON.stringify({ versions: { '1.2.3-beta': {} } })
)
await cacache.put(cache,
'make-fetch-happen:request-cache:https://registry.npmjs.org/foo/-/foo-1.2.3-beta.tgz',
'test-data'
)
await npm.exec('cache', ['ls', 'foo@1.2.3-beta'])
t.matchSnapshot(joinedOutput(), 'logs cache entries for foo')
})
t.test('cache ls nonpublic registry', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const cache = path.join(npm.cache, '_cacache')
await cacache.put(cache,
'make-fetch-happen:request-cache:https://somerepo.github.org/extemporaneously',
JSON.stringify({
versions: { '1.0.0': { dist: { tarball: 'https://somerepo.github.org/aabbcc/' } } },
})
)
await cacache.put(cache,
'make-fetch-happen:request-cache:https://somerepo.github.org/aabbcc/',
'test data'
)
await npm.exec('cache', ['ls', 'extemporaneously'])
t.matchSnapshot(joinedOutput(), 'logs cache entry for extemporaneously and its tarball')
})
t.test('cache ls tagged', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('cache', ['ls', 'webpack@latest']),
{ code: 'EUSAGE' },
'should throw usage error'
)
})
t.test('cache ls scoped and scoped slash', async t => {
const keys = [
'make-fetch-happen:request-cache:https://registry.npmjs.org/@fritzy/staydown/-/@fritzy/staydown-3.1.1.tgz',
'make-fetch-happen:request-cache:https://registry.npmjs.org/@fritzy%2fstaydown',
'make-fetch-happen:request-cache:https://registry.npmjs.org/@gar/npm-expansion/-/@gar/npm-expansion-2.1.0.tgz',
'make-fetch-happen:request-cache:https://registry.npmjs.org/@gar%2fnpm-expansion',
]
const { npm, joinedOutput } = await loadMockNpm(t)
const cache = path.join(npm.cache, '_cacache')
for (const key of keys) {
await cacache.put(cache, key, 'test data')
}
await npm.exec('cache', ['ls', '@fritzy/staydown', '@gar/npm-expansion'])
t.matchSnapshot(joinedOutput(), 'logs cache entries for @gar and @fritzy')
})
t.test('cache ls corrupted', async t => {
const keys = [
'make-fetch-happen:request-cache:https://registry.npmjs.org/corrupted',
'make-fetch-happen:request-cache:https://registry.npmjs.org/corrupted/-/corrupted-3.1.0.tgz',
]
const { npm, joinedOutput } = await loadMockNpm(t)
const cache = path.join(npm.cache, '_cacache')
for (const key of keys) {
await cacache.put(cache, key, Buffer.from('<>>>}"'))
}
await npm.exec('cache', ['ls', 'corrupted'])
t.matchSnapshot(joinedOutput(), 'logs cache entries with bad data')
})
t.test('cache ls missing packument version not an object', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const cache = path.join(npm.cache, '_cacache')
await cacache.put(cache,
'make-fetch-happen:request-cache:https://registry.npmjs.org/missing-version',
JSON.stringify({ versions: 'not an object' })
)
await npm.exec('cache', ['ls', 'missing-version'])
t.matchSnapshot(joinedOutput(), 'logs cache entry for packument')
})
t.test('cache rm', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const cache = path.join(npm.cache, '_cacache')
await cacache.put(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package', '{}')
await cacache.put(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package/-/test-package-1.0.0.tgz', 'test data')
await npm.exec('cache', ['rm', 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package/-/test-package-1.0.0.tgz'])
t.matchSnapshot(joinedOutput(), 'logs deleting single entry')
t.resolves(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package'))
t.rejects(cacache.get(cache, 'make-fetch-happen:request-cache:https://registry.npmjs.org/test-package/-/test-package-1.0.0.tgz'))
})
t.test('cache rm unfound', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
await npm.exec('cache', ['rm', 'made-up-key'])
t.same(joinedOutput(), '', 'no errors, no output')
})
t.test('cache verify', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
await npm.exec('cache', ['verify'])
t.matchSnapshot(joinedOutput(), 'shows verified cache output')
})
t.test('cache verify as part of home', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
globals: ({ prefix }) => ({ 'process.env.HOME': path.dirname(prefix) }),
})
await npm.exec('cache', ['verify'])
t.match(joinedOutput(), 'Cache verified and compressed (~', 'contains ~ shorthand')
})
t.test('cache verify w/ extra output', async t => {
const verify = {
runTime: {
markStartTime: 0,
fixPerms: 3,
garbageCollect: 54982,
rebuildIndex: 62779,
cleanTmp: 62781,
writeVerifile: 62783,
markEndTime: 62783,
total: 62783,
},
verifiedContent: 17057,
reclaimedCount: 1144,
reclaimedSize: 248164665,
badContentCount: 12345,
keptSize: 1644485260,
missingContent: 92,
rejectedEntries: 92,
totalEntries: 20175,
}
const { npm, joinedOutput } = await loadMockNpm(t, {
mocks: { cacache: { verify: () => verify } },
})
await npm.exec('cache', ['verify'])
t.matchSnapshot(joinedOutput(), 'shows extra output')
})
t.test('cache completion', async t => {
const { cache } = await loadMockNpm(t, { command: 'cache' })
const { completion } = cache
const testComp = (argv, expect) => {
return t.resolveMatch(completion({ conf: { argv: { remain: argv } } }), expect, argv.join(' '))
}
await Promise.all([
testComp(['npm', 'cache'], ['add', 'clean', 'verify']),
testComp(['npm', 'cache', 'add'], []),
testComp(['npm', 'cache', 'clean'], []),
testComp(['npm', 'cache', 'verify'], []),
])
})
t.test('cache npx ls: empty cache', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
await npm.exec('cache', ['npx', 'ls'])
t.matchSnapshot(joinedOutput(), 'logs message for empty npx cache')
})
t.test('cache npx ls: some entries', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const npxCacheDir = path.join(npm.flatOptions.npxCache || path.join(npm.cache, '..', '_npx'))
fs.mkdirSync(npxCacheDir, { recursive: true })
// Make two fake entries: one valid, one invalid
const hash1 = 'abc123'
const hash2 = 'z9y8x7'
fs.mkdirSync(path.join(npxCacheDir, hash1))
fs.writeFileSync(
path.join(npxCacheDir, hash1, 'package.json'),
JSON.stringify({
name: 'fake-npx-package',
version: '1.0.0',
_npx: { packages: ['fake-npx-package@1.0.0'] },
})
)
// invalid (missing or broken package.json) directory
fs.mkdirSync(path.join(npxCacheDir, hash2))
await npm.exec('cache', ['npx', 'ls'])
t.matchSnapshot(joinedOutput(), 'lists one valid and one invalid entry')
})
t.test('cache npx info: valid and invalid entry', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const npxCacheDir = path.join(npm.flatOptions.npxCache || path.join(npm.cache, '..', '_npx'))
fs.mkdirSync(npxCacheDir, { recursive: true })
const goodHash = 'deadbeef'
fs.mkdirSync(path.join(npxCacheDir, goodHash))
fs.writeFileSync(
path.join(npxCacheDir, goodHash, 'package.json'),
JSON.stringify({
name: 'good-npx-package',
version: '2.0.0',
dependencies: {
rimraf: '^3.0.0',
},
_npx: { packages: ['good-npx-package@2.0.0'] },
})
)
const badHash = 'badc0de'
fs.mkdirSync(path.join(npxCacheDir, badHash))
await npm.exec('cache', ['npx', 'info', goodHash])
t.matchSnapshot(joinedOutput(), 'shows valid package info')
await npm.exec('cache', ['npx', 'info', badHash])
t.matchSnapshot(joinedOutput(), 'shows invalid package info')
})
t.test('cache npx rm: remove single entry', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const npxCacheDir = path.join(npm.flatOptions.npxCache || path.join(npm.cache, '..', '_npx'))
fs.mkdirSync(npxCacheDir, { recursive: true })
const removableHash = '123removeme'
fs.mkdirSync(path.join(npxCacheDir, removableHash))
fs.writeFileSync(
path.join(npxCacheDir, removableHash, 'package.json'),
JSON.stringify({ name: 'removable-package', _npx: { packages: ['removable-package@1.0.0'] } })
)
npm.config.set('dry-run', true)
await npm.exec('cache', ['npx', 'rm', removableHash])
t.ok(fs.existsSync(path.join(npxCacheDir, removableHash)), 'entry folder remains')
npm.config.set('dry-run', false)
await npm.exec('cache', ['npx', 'rm', removableHash])
t.matchSnapshot(joinedOutput(), 'logs removing single npx cache entry')
t.notOk(fs.existsSync(path.join(npxCacheDir, removableHash)), 'entry folder removed')
})
t.test('cache npx rm: removing all without --force fails', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const npxCacheDir = path.join(npm.flatOptions.npxCache || path.join(npm.cache, '..', '_npx'))
fs.mkdirSync(npxCacheDir, { recursive: true })
const testHash = 'remove-all-no-force'
fs.mkdirSync(path.join(npxCacheDir, testHash))
fs.writeFileSync(
path.join(npxCacheDir, testHash, 'package.json'),
JSON.stringify({ name: 'no-force-pkg', _npx: { packages: ['no-force-pkg@1.0.0'] } })
)
await t.rejects(
npm.exec('cache', ['npx', 'rm']),
/Please use --force to remove entire npx cache/,
'fails without --force'
)
t.matchSnapshot(joinedOutput(), 'logs usage error when removing all without --force')
t.ok(fs.existsSync(path.join(npxCacheDir, testHash)), 'folder still exists')
})
t.test('cache npx rm: removing all with --force works', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: { force: true },
})
const npxCacheDir = path.join(npm.flatOptions.npxCache || path.join(npm.cache, '..', '_npx'))
fs.mkdirSync(npxCacheDir, { recursive: true })
const testHash = 'remove-all-yes-force'
fs.mkdirSync(path.join(npxCacheDir, testHash))
fs.writeFileSync(
path.join(npxCacheDir, testHash, 'package.json'),
JSON.stringify({ name: 'yes-force-pkg', _npx: { packages: ['yes-force-pkg@1.0.0'] } })
)
npm.config.set('dry-run', true)
await npm.exec('cache', ['npx', 'rm'])
t.ok(fs.existsSync(npxCacheDir), 'npx cache directory remains')
npm.config.set('dry-run', false)
await npm.exec('cache', ['npx', 'rm'])
t.matchSnapshot(joinedOutput(), 'logs removing everything')
t.notOk(fs.existsSync(npxCacheDir), 'npx cache directory removed')
})
t.test('cache npx rm: removing more than 1, less than all entries', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const npxCacheDir = path.join(npm.flatOptions.npxCache || path.join(npm.cache, '..', '_npx'))
fs.mkdirSync(npxCacheDir, { recursive: true })
// Removable folder
const removableHash = '123removeme'
fs.mkdirSync(path.join(npxCacheDir, removableHash))
fs.writeFileSync(
path.join(npxCacheDir, removableHash, 'package.json'),
JSON.stringify({ name: 'removable-package', _npx: { packages: ['removable-package@1.0.0'] } })
)
// Another Removable folder
const anotherRemovableHash = '456removeme'
fs.mkdirSync(path.join(npxCacheDir, anotherRemovableHash))
fs.writeFileSync(
path.join(npxCacheDir, anotherRemovableHash, 'package.json'),
JSON.stringify({ name: 'another-removable-package', _npx: { packages: ['another-removable-package@1.0.0'] } })
)
// Another folder that should remain
const keepHash = '999keep'
fs.mkdirSync(path.join(npxCacheDir, keepHash))
fs.writeFileSync(
path.join(npxCacheDir, keepHash, 'package.json'),
JSON.stringify({ name: 'keep-package', _npx: { packages: ['keep-package@1.0.0'] } })
)
npm.config.set('dry-run', true)
await npm.exec('cache', ['npx', 'rm', removableHash, anotherRemovableHash])
t.ok(fs.existsSync(path.join(npxCacheDir, removableHash)), 'entry folder remains')
t.ok(fs.existsSync(path.join(npxCacheDir, anotherRemovableHash)), 'entry folder remains')
t.ok(fs.existsSync(path.join(npxCacheDir, keepHash)), 'entry folder remains')
npm.config.set('dry-run', false)
await npm.exec('cache', ['npx', 'rm', removableHash, anotherRemovableHash])
t.matchSnapshot(joinedOutput(), 'logs removing 2 of 3 entries')
t.notOk(fs.existsSync(path.join(npxCacheDir, removableHash)), 'removed folder no longer exists')
t.notOk(fs.existsSync(path.join(npxCacheDir, anotherRemovableHash)), 'the other folder no longer exists')
t.ok(fs.existsSync(path.join(npxCacheDir, keepHash)), 'the other folder remains')
})
t.test('cache npx should throw usage error', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('cache', ['npx', 'badcommand']),
{ code: 'EUSAGE' },
'should throw usage error'
)
})
t.test('cache npx should throw usage error for invalid key', async t => {
const { npm } = await loadMockNpm(t)
const npxCacheDir = path.join(npm.flatOptions.npxCache || path.join(npm.cache, '..', '_npx'))
fs.mkdirSync(npxCacheDir, { recursive: true })
const key = 'badkey'
await t.rejects(
npm.exec('cache', ['npx', 'rm', key]),
{ code: 'EUSAGE' },
`Invalid npx key ${key}`
)
})
t.test('cache npx ls: entry with unknown package', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const npxCacheDir = path.join(npm.flatOptions.npxCache || path.join(npm.cache, '..', '_npx'))
fs.mkdirSync(npxCacheDir, { recursive: true })
// Create an entry without the _npx property
const unknownHash = 'unknown123'
fs.mkdirSync(path.join(npxCacheDir, unknownHash))
fs.writeFileSync(
path.join(npxCacheDir, unknownHash, 'package.json'),
JSON.stringify({
name: 'unknown-package',
version: '1.0.0',
})
)
await npm.exec('cache', ['npx', 'ls'])
t.matchSnapshot(joinedOutput(), 'lists entry with unknown package')
})
t.test('cache npx info: should throw usage error when no keys are provided', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('cache', ['npx', 'info']),
{ code: 'EUSAGE' },
'should throw usage error when no keys are provided'
)
})
t.test('cache npx info: valid entry with _npx packages', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const npxCacheDir = path.join(npm.flatOptions.npxCache || path.join(npm.cache, '..', '_npx'))
fs.mkdirSync(npxCacheDir, { recursive: true })
const validHash = 'valid123'
createNpxCacheEntry(npxCacheDir, validHash, {
name: 'valid-package',
version: '1.0.0',
_npx: { packages: ['valid-package@1.0.0'] },
}, {
name: 'valid-package',
version: '1.0.0',
dependencies: {
'valid-package': {
version: '1.0.0',
resolved: 'https://registry.npmjs.org/valid-package/-/valid-package-1.0.0.tgz',
integrity: 'sha512-...',
},
},
})
const nodeModulesDir = path.join(npxCacheDir, validHash, 'node_modules')
fs.mkdirSync(nodeModulesDir, { recursive: true })
fs.mkdirSync(path.join(nodeModulesDir, 'valid-package'))
fs.writeFileSync(
path.join(nodeModulesDir, 'valid-package', 'package.json'),
JSON.stringify({
name: 'valid-package',
version: '1.0.0',
})
)
await npm.exec('cache', ['npx', 'info', validHash])
t.matchSnapshot(joinedOutput(), 'shows valid package info with _npx packages')
})
t.test('cache npx info: valid entry with dependencies', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const npxCacheDir = path.join(npm.flatOptions.npxCache || path.join(npm.cache, '..', '_npx'))
fs.mkdirSync(npxCacheDir, { recursive: true })
const validHash = 'valid456'
createNpxCacheEntry(npxCacheDir, validHash, {
name: 'valid-package',
version: '1.0.0',
dependencies: {
'dep-package': '1.0.0',
},
}, {
name: 'valid-package',
version: '1.0.0',
dependencies: {
'dep-package': {
version: '1.0.0',
resolved: 'https://registry.npmjs.org/dep-package/-/dep-package-1.0.0.tgz',
integrity: 'sha512-...',
},
},
})
const nodeModulesDir = path.join(npxCacheDir, validHash, 'node_modules')
fs.mkdirSync(nodeModulesDir, { recursive: true })
fs.mkdirSync(path.join(nodeModulesDir, 'dep-package'))
fs.writeFileSync(
path.join(nodeModulesDir, 'dep-package', 'package.json'),
JSON.stringify({
name: 'dep-package',
version: '1.0.0',
})
)
await npm.exec('cache', ['npx', 'info', validHash])
t.matchSnapshot(joinedOutput(), 'shows valid package info with dependencies')
})
t.test('cache npx info: valid entry with _npx directory package', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const npxCacheDir = path.join(npm.flatOptions.npxCache || path.join(npm.cache, '..', '_npx'))
fs.mkdirSync(npxCacheDir, { recursive: true })
const validHash = 'valid123'
createNpxCacheEntry(npxCacheDir, validHash, {
name: 'valid-package',
version: '1.0.0',
_npx: { packages: ['/path/to/valid-package'] },
}, {
name: 'valid-package',
version: '1.0.0',
dependencies: {
'valid-package': {
version: '1.0.0',
resolved: 'https://registry.npmjs.org/valid-package/-/valid-package-1.0.0.tgz',
integrity: 'sha512-...',
},
},
})
const nodeModulesDir = path.join(npxCacheDir, validHash, 'node_modules')
fs.mkdirSync(nodeModulesDir, { recursive: true })
fs.mkdirSync(path.join(nodeModulesDir, 'valid-package'))
fs.writeFileSync(
path.join(nodeModulesDir, 'valid-package', 'package.json'),
JSON.stringify({
name: 'valid-package',
version: '1.0.0',
})
)
await npm.exec('cache', ['npx', 'info', validHash])
t.matchSnapshot(joinedOutput(), 'shows valid package info with _npx directory package')
})
t.test('cache npx info: valid entry with a link dependency', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const npxCacheDir = path.join(
npm.flatOptions.npxCache || path.join(npm.cache, '..', '_npx')
)
fs.mkdirSync(npxCacheDir, { recursive: true })
const validHash = 'link123'
const pkgDir = path.join(npxCacheDir, validHash)
fs.mkdirSync(pkgDir)
fs.writeFileSync(
path.join(pkgDir, 'package.json'),
JSON.stringify({
name: 'link-package',
version: '1.0.0',
dependencies: {
'linked-dep': 'file:./some-other-loc',
},
})
)
fs.writeFileSync(
path.join(pkgDir, 'npm-shrinkwrap.json'),
JSON.stringify({
name: 'link-package',
version: '1.0.0',
dependencies: {
'linked-dep': {
version: 'file:../some-other-loc',
},
},
})
)
const nodeModulesDir = path.join(pkgDir, 'node_modules')
fs.mkdirSync(nodeModulesDir, { recursive: true })
const linkTarget = path.join(pkgDir, 'some-other-loc')
fs.mkdirSync(linkTarget)
fs.writeFileSync(
path.join(linkTarget, 'package.json'),
JSON.stringify({ name: 'linked-dep', version: '1.0.0' })
)
fs.symlinkSync('../some-other-loc', path.join(nodeModulesDir, 'linked-dep'))
await npm.exec('cache', ['npx', 'info', validHash])
t.matchSnapshot(
joinedOutput(),
'shows link dependency realpath (child.isLink branch)'
)
})

310
deps/npm/test/lib/commands/ci.js vendored Normal file
View File

@ -0,0 +1,310 @@
const fs = require('node:fs')
const path = require('node:path')
const t = require('tap')
const {
loadNpmWithRegistry: loadMockNpm,
workspaceMock,
} = require('../../fixtures/mock-npm')
// t.cleanSnapshot = str => str.replace(/ in [0-9ms]+/g, ' in {TIME}')
const packageJson = {
name: 'test-package',
version: '1.0.0',
dependencies: {
abbrev: '^1.0.0',
},
}
const packageLock = {
name: 'test-package',
version: '1.0.0',
lockfileVersion: 2,
requires: true,
packages: {
'': {
name: 'test-package',
version: '1.0.0',
dependencies: {
abbrev: '^1.0.0',
},
},
'node_modules/abbrev': {
version: '1.0.0',
resolved: 'https://registry.npmjs.org/abbrev/-/abbrev-1.0.0.tgz',
// integrity changes w/ each test cause the path is different?
},
},
dependencies: {
abbrev: {
version: '1.0.0',
resolved: 'https://registry.npmjs.org/abbrev/-/abbrev-1.0.0.tgz',
// integrity changes w/ each test cause the path is different?
},
},
}
const abbrev = {
'package.json': '{"name": "abbrev", "version": "1.0.0"}',
test: 'test file',
}
t.test('reifies, but doesn\'t remove node_modules because --dry-run', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
'dry-run': true,
},
prefixDir: {
abbrev: abbrev,
'package.json': JSON.stringify(packageJson),
'package-lock.json': JSON.stringify(packageLock),
node_modules: { test: 'test file that will not be removed' },
},
})
await npm.exec('ci', [])
t.match(joinedOutput(), 'added 1 package, and removed 1 package in')
const nmTest = path.join(npm.prefix, 'node_modules', 'test')
t.equal(fs.existsSync(nmTest), true, 'existing node_modules is not removed')
const nmAbbrev = path.join(npm.prefix, 'node_modules', 'abbrev')
t.equal(fs.existsSync(nmAbbrev), false, 'does not install abbrev')
})
t.test('reifies, audits, removes node_modules', async t => {
const { npm, joinedOutput, registry } = await loadMockNpm(t, {
prefixDir: {
abbrev: abbrev,
'package.json': JSON.stringify(packageJson),
'package-lock.json': JSON.stringify(packageLock),
node_modules: { test: 'test file that will be removed' },
},
})
const manifest = registry.manifest({ name: 'abbrev' })
await registry.tarball({
manifest: manifest.versions['1.0.0'],
tarball: path.join(npm.prefix, 'abbrev'),
})
registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
await npm.exec('ci', [])
t.match(joinedOutput(), 'added 1 package, and audited 2 packages in')
const nmTest = path.join(npm.prefix, 'node_modules', 'test')
t.equal(fs.existsSync(nmTest), false, 'existing node_modules is removed')
const nmAbbrev = path.join(npm.prefix, 'node_modules', 'abbrev')
t.equal(fs.existsSync(nmAbbrev), true, 'installs abbrev')
})
t.test('reifies, audits, removes node_modules on repeat run', async t => {
const { npm, joinedOutput, registry } = await loadMockNpm(t, {
prefixDir: {
abbrev: abbrev,
'package.json': JSON.stringify(packageJson),
'package-lock.json': JSON.stringify(packageLock),
node_modules: { test: 'test file that will be removed' },
},
})
const manifest = registry.manifest({ name: 'abbrev' })
await registry.tarball({
times: 2,
manifest: manifest.versions['1.0.0'],
tarball: path.join(npm.prefix, 'abbrev'),
})
await registry.tarball({
manifest: manifest.versions['1.0.0'],
tarball: path.join(npm.prefix, 'abbrev'),
})
registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
await npm.exec('ci', [])
await npm.exec('ci', [])
t.match(joinedOutput(), 'added 1 package, and audited 2 packages in')
const nmTest = path.join(npm.prefix, 'node_modules', 'test')
t.equal(fs.existsSync(nmTest), false, 'existing node_modules is removed')
const nmAbbrev = path.join(npm.prefix, 'node_modules', 'abbrev')
t.equal(fs.existsSync(nmAbbrev), true, 'installs abbrev')
})
t.test('--no-audit and --ignore-scripts', async t => {
const { npm, joinedOutput, registry } = await loadMockNpm(t, {
config: {
'ignore-scripts': true,
audit: false,
},
prefixDir: {
abbrev: {
'package.json': '{"name": "abbrev", "version": "1.0.0"}',
test: 'test-file',
},
'package.json': JSON.stringify({
...packageJson,
// Would make install fail
scripts: { install: 'echo "SHOULD NOT RUN" && exit 1' },
}),
'package-lock.json': JSON.stringify(packageLock),
},
})
const manifest = registry.manifest({ name: 'abbrev' })
await registry.tarball({
manifest: manifest.versions['1.0.0'],
tarball: path.join(npm.prefix, 'abbrev'),
})
await npm.exec('ci', [])
t.match(joinedOutput(), 'added 1 package in', 'would fail if install script ran')
})
t.test('lifecycle scripts', async t => {
const scripts = []
const { npm, registry } = await loadMockNpm(t, {
prefixDir: {
abbrev: abbrev,
'package.json': JSON.stringify(packageJson),
'package-lock.json': JSON.stringify(packageLock),
},
mocks: {
'@npmcli/run-script': (opts) => {
scripts.push(opts.event)
},
},
})
const manifest = registry.manifest({ name: 'abbrev' })
await registry.tarball({
manifest: manifest.versions['1.0.0'],
tarball: path.join(npm.prefix, 'abbrev'),
})
registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
await npm.exec('ci', [])
t.same(scripts, [
'preinstall',
'install',
'postinstall',
'prepublish',
'preprepare',
'prepare',
'postprepare',
], 'runs appropriate scripts, in order')
})
t.test('should throw if package-lock.json or npm-shrinkwrap missing', async t => {
const { npm } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify(packageJson),
node_modules: {
'test-file': 'should not be removed',
},
},
})
await t.rejects(npm.exec('ci', []), { code: 'EUSAGE', message: /package-lock.json/ })
const nmTestFile = path.join(npm.prefix, 'node_modules', 'test-file')
t.equal(fs.existsSync(nmTestFile), true, 'does not remove node_modules')
})
t.test('should throw ECIGLOBAL', async t => {
const { npm } = await loadMockNpm(t, {
config: { global: true },
})
await t.rejects(npm.exec('ci', []), { code: 'ECIGLOBAL' })
})
t.test('should throw error when ideal inventory mismatches virtual', async t => {
const { npm, registry } = await loadMockNpm(t, {
prefixDir: {
abbrev: abbrev,
'package.json': JSON.stringify({
...packageJson,
dependencies: { notabbrev: '^1.0.0' },
}),
'package-lock.json': JSON.stringify(packageLock),
node_modules: {
'test-file': 'should not be removed',
},
},
})
const manifest = registry.manifest({ name: 'notabbrev' })
await registry.package({ manifest })
await t.rejects(
npm.exec('ci', []),
{ code: 'EUSAGE', message: /in sync/ }
)
const nmTestFile = path.join(npm.prefix, 'node_modules', 'test-file')
t.equal(fs.existsSync(nmTestFile), true, 'does not remove node_modules')
})
t.test('should remove dirty node_modules with unhoisted workspace module', async t => {
const { npm, registry, assert } = await loadMockNpm(t, {
prefixDir: workspaceMock(t, {
clean: false,
workspaces: {
'workspace-a': {
'abbrev@1.1.0': { },
},
'workspace-b': {
'abbrev@1.1.1': { hoist: false },
},
},
}),
})
await registry.setup({
'abbrev@1.1.0': path.join(npm.prefix, 'tarballs/abbrev@1.1.0'),
'abbrev@1.1.1': path.join(npm.prefix, 'tarballs/abbrev@1.1.1'),
})
registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
assert.packageDirty('node_modules/abbrev@1.1.0')
assert.packageDirty('workspace-b/node_modules/abbrev@1.1.1')
await npm.exec('ci', [])
assert.packageInstalled('node_modules/abbrev@1.1.0')
assert.packageInstalled('workspace-b/node_modules/abbrev@1.1.1')
})
t.test('should remove dirty node_modules with hoisted workspace modules', async t => {
const { npm, registry, assert } = await loadMockNpm(t, {
prefixDir: workspaceMock(t, {
clean: false,
workspaces: {
'workspace-a': {
'abbrev@1.1.0': { },
},
'workspace-b': {
'lodash@1.1.1': { },
},
},
}),
})
await registry.setup({
'abbrev@1.1.0': path.join(npm.prefix, 'tarballs/abbrev@1.1.0'),
'lodash@1.1.1': path.join(npm.prefix, 'tarballs/lodash@1.1.1'),
})
registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
assert.packageDirty('node_modules/abbrev@1.1.0')
assert.packageDirty('node_modules/lodash@1.1.1')
await npm.exec('ci', [])
assert.packageInstalled('node_modules/abbrev@1.1.0')
assert.packageInstalled('node_modules/lodash@1.1.1')
})
/** this behaves the same way as install but will remove all workspace node_modules */
t.test('should use --workspace flag', async t => {
t.saveFixture = true
const { npm, registry, assert } = await loadMockNpm(t, {
config: {
workspace: 'workspace-b',
},
prefixDir: workspaceMock(t, {
clean: false,
workspaces: {
'workspace-a': {
'abbrev@1.1.0': { },
},
'workspace-b': {
'lodash@1.1.1': { },
},
},
}),
})
await registry.setup({
'lodash@1.1.1': path.join(npm.prefix, 'tarballs/lodash@1.1.1'),
})
registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
assert.packageDirty('node_modules/abbrev@1.1.0')
assert.packageDirty('node_modules/lodash@1.1.1')
await npm.exec('ci', [])
assert.packageMissing('node_modules/abbrev@1.1.0')
assert.packageInstalled('node_modules/lodash@1.1.1')
})

199
deps/npm/test/lib/commands/completion.js vendored Normal file
View File

@ -0,0 +1,199 @@
const t = require('tap')
const fs = require('node:fs')
const path = require('node:path')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const completionScript = fs
.readFileSync(path.resolve(__dirname, '../../../lib/utils/completion.sh'), { encoding: 'utf8' })
.replace(/^#!.*?\n/, '')
const loadMockCompletion = async (t, o = {}) => {
const { globals = {}, windows, ...options } = o
const res = await loadMockNpm(t, {
command: 'completion',
...options,
globals: (dirs) => ({
'process.platform': windows ? 'win32' : 'posix',
'process.env.term': 'notcygwin',
'process.env.msystem': 'nogmingw',
...(typeof globals === 'function' ? globals(dirs) : globals),
}),
})
return {
resetGlobals: res.mockedGlobals.reset,
...res,
}
}
const loadMockCompletionComp = async (t, word, line) =>
loadMockCompletion(t, {
globals: {
'process.env.COMP_CWORD': word,
'process.env.COMP_LINE': line,
'process.env.COMP_POINT': line.length,
},
})
t.test('completion', async t => {
t.test('completion completion', async t => {
const { outputs, completion } = await loadMockCompletion(t, {
prefixDir: {
'.bashrc': 'aaa',
'.zshrc': 'aaa',
},
globals: ({ prefix }) => ({
'process.env.HOME': prefix,
}),
})
await completion.completion({ w: 2 })
t.matchSnapshot(outputs, 'both shells')
})
t.test('completion completion no known shells', async t => {
const { outputs, completion } = await loadMockCompletion(t, {
globals: ({ prefix }) => ({
'process.env.HOME': prefix,
}),
})
await completion.completion({ w: 2 })
t.matchSnapshot(outputs, 'no responses')
})
t.test('completion completion wrong word count', async t => {
const { outputs, completion } = await loadMockCompletion(t)
await completion.completion({ w: 3 })
t.matchSnapshot(outputs, 'no responses')
})
t.test('dump script when completion is not being attempted', async t => {
let errorHandler, data
const { completion, resetGlobals } = await loadMockCompletion(t, {
globals: {
'process.stdout.on': (event, handler) => {
errorHandler = handler
resetGlobals['process.stdout.on']()
},
'process.stdout.write': (chunk, callback) => {
data = chunk
process.nextTick(() => {
callback()
errorHandler({ errno: 'EPIPE' })
})
resetGlobals['process.stdout.write']()
},
},
})
await completion.exec()
t.equal(data, completionScript, 'wrote the completion script')
})
t.test('dump script exits correctly when EPIPE is emitted on stdout', async t => {
let errorHandler, data
const { completion, resetGlobals } = await loadMockCompletion(t, {
globals: {
'process.stdout.on': (event, handler) => {
if (event === 'error') {
errorHandler = handler
}
resetGlobals['process.stdout.on']()
},
'process.stdout.write': (chunk, callback) => {
data = chunk
process.nextTick(() => {
errorHandler({ errno: 'EPIPE' })
callback()
})
resetGlobals['process.stdout.write']()
},
},
})
await completion.exec()
t.equal(data, completionScript, 'wrote the completion script')
})
t.test('single command name', async t => {
const { outputs, completion } = await loadMockCompletionComp(t, 1, 'npm conf')
await completion.exec(['npm', 'conf'])
t.matchSnapshot(outputs, 'single command name')
})
t.test('multiple command names', async t => {
const { outputs, completion } = await loadMockCompletionComp(t, 1, 'npm a')
await completion.exec(['npm', 'a'])
t.matchSnapshot(outputs, 'multiple command names')
})
t.test('completion of invalid command name does nothing', async t => {
const { outputs, completion } = await loadMockCompletionComp(t, 1, 'npm compute')
await completion.exec(['npm', 'compute'])
t.matchSnapshot(outputs, 'no results')
})
t.test('subcommand completion', async t => {
const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm access ')
await completion.exec(['npm', 'access', ''])
t.matchSnapshot(outputs, 'subcommands')
})
t.test('filtered subcommands', async t => {
const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm access p')
await completion.exec(['npm', 'access', 'p'])
t.matchSnapshot(outputs, 'filtered subcommands')
})
t.test('commands with no completion', async t => {
const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm adduser ')
// quotes around adduser are to ensure coverage when unescaping commands
await completion.exec(['npm', "'adduser'", ''])
t.matchSnapshot(outputs, 'no results')
})
t.test('flags', async t => {
const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm install --v')
await completion.exec(['npm', 'install', '--v'])
t.matchSnapshot(outputs, 'flags')
})
t.test('--no- flags', async t => {
const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm install --no-v')
await completion.exec(['npm', 'install', '--no-v'])
t.matchSnapshot(outputs, 'flags')
})
t.test('double dashes escape from flag completion', async t => {
const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm -- install --')
await completion.exec(['npm', '--', 'install', '--'])
t.matchSnapshot(outputs, 'full command list')
})
t.test('completion cannot complete options that take a value in mid-command', async t => {
const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm --registry install')
await completion.exec(['npm', '--registry', 'install'])
t.matchSnapshot(outputs, 'does not try to complete option arguments in the middle of a command')
})
})
t.test('windows without bash', async t => {
const { outputs, completion } = await loadMockCompletion(t, { windows: true })
await t.rejects(
completion.exec(),
{ code: 'ENOTSUP', message: /completion supported only in MINGW/ },
'returns the correct error'
)
t.matchSnapshot(outputs, 'no output')
})

717
deps/npm/test/lib/commands/config.js vendored Normal file
View File

@ -0,0 +1,717 @@
const { join } = require('node:path')
const fs = require('node:fs/promises')
const ini = require('ini')
const tspawk = require('../../fixtures/tspawk')
const t = require('tap')
const { load: _loadMockNpm } = require('../../fixtures/mock-npm')
const { cleanCwd } = require('../../fixtures/clean-snapshot.js')
const spawk = tspawk(t)
const replaceJsonOrIni = (key) => [
new RegExp(`(\\s(?:${key} = |"${key}": )"?)[^"\\n,]+`, 'g'),
`$1{${key.toUpperCase()}}`,
]
const replaceIniComment = (key) => [
new RegExp(`(; ${key} = ).*`, 'g'),
`$1{${key.replaceAll(' ', '-').toUpperCase()}}`,
]
t.cleanSnapshot = (s) => cleanCwd(s)
.replaceAll(...replaceIniComment('node version'))
.replaceAll(...replaceIniComment('npm version'))
.replaceAll(...replaceIniComment('node bin location'))
.replaceAll(...replaceJsonOrIni('npm-version'))
.replaceAll(...replaceJsonOrIni('viewer'))
.replaceAll(...replaceJsonOrIni('shell'))
.replaceAll(...replaceJsonOrIni('editor'))
.replaceAll(...replaceJsonOrIni('progress'))
.replaceAll(...replaceJsonOrIni('color'))
.replaceAll(...replaceJsonOrIni('cache'))
const loadMockNpm = (t, opts = {}) => _loadMockNpm(t, {
...opts,
config: {
...opts.config,
// Reset configs that mock npm sets by default
'fetch-retries': undefined,
loglevel: undefined,
color: false,
},
})
t.test('config no args', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('config', []),
{
code: 'EUSAGE',
},
'rejects with usage'
)
})
t.test('config ignores workspaces', async t => {
const { npm } = await loadMockNpm(t, {
config: {
workspaces: true,
},
})
await t.rejects(
npm.exec('config'),
{
code: 'ENOWORKSPACES',
},
'rejects with usage'
)
})
t.test('config list', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
'.npmrc': 'projectloaded=yes',
},
globalPrefixDir: {
etc: {
npmrc: 'globalloaded=yes',
},
},
homeDir: {
'.npmrc': [
'userloaded=yes',
'auth=bad',
'_auth=bad',
'//nerfdart:auth=bad',
'//nerfdart:_auth=bad',
].join('\n'),
},
})
await npm.exec('config', ['list'])
const output = joinedOutput()
t.match(output, 'projectloaded = "yes"')
t.match(output, 'globalloaded = "yes"')
t.match(output, 'userloaded = "yes"')
t.matchSnapshot(output, 'output matches snapshot')
})
t.test('config list --long', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
'.npmrc': 'projectloaded=yes',
},
globalPrefixDir: {
etc: {
npmrc: 'globalloaded=yes',
},
},
homeDir: {
'.npmrc': 'userloaded=yes',
},
config: {
long: true,
},
})
await npm.exec('config', ['list'])
const output = joinedOutput()
t.match(output, 'projectloaded = "yes"')
t.match(output, 'globalloaded = "yes"')
t.match(output, 'userloaded = "yes"')
t.matchSnapshot(output, 'output matches snapshot')
})
t.test('config list --json', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
'.npmrc': 'projectloaded=yes',
},
globalPrefixDir: {
etc: {
npmrc: 'globalloaded=yes',
},
},
homeDir: {
'.npmrc': 'userloaded=yes',
},
config: {
json: true,
},
})
await npm.exec('config', ['list'])
const output = joinedOutput()
t.match(output, '"projectloaded": "yes",')
t.match(output, '"globalloaded": "yes",')
t.match(output, '"userloaded": "yes",')
t.matchSnapshot(output, 'output matches snapshot')
})
t.test('config list with publishConfig', async t => {
const loadMockNpmWithPublishConfig = (t, opts) => loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
publishConfig: {
other: 'not defined',
registry: 'https://some.registry',
'//some.registry:_authToken': 'mytoken',
},
}),
},
...opts,
})
t.test('local', async t => {
const { npm, logs, joinedOutput } = await loadMockNpmWithPublishConfig(t)
await npm.exec('config', ['list'])
const output = joinedOutput()
t.match(output, 'registry = "https://some.registry"')
t.matchSnapshot(output, 'output matches snapshot')
t.matchSnapshot(logs.warn, 'warns about unknown config')
})
t.test('global', async t => {
const { npm, joinedOutput } = await loadMockNpmWithPublishConfig(t, {
config: {
global: true,
},
})
await npm.exec('config', ['list'])
const output = joinedOutput()
t.notMatch(output, 'registry = "https://some.registry"')
t.matchSnapshot(output, 'output matches snapshot')
})
})
t.test('config delete no args', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('config', ['delete']),
{
code: 'EUSAGE',
},
'rejects with usage'
)
})
t.test('config delete single key', async t => {
// location defaults to user, so we work with a userconfig
const { npm, home } = await loadMockNpm(t, {
homeDir: {
'.npmrc': 'access=public\nall=true',
},
})
await npm.exec('config', ['delete', 'access'])
t.equal(npm.config.get('access'), null, 'acces should be defaulted')
const contents = await fs.readFile(join(home, '.npmrc'), { encoding: 'utf8' })
const rc = ini.parse(contents)
t.not(rc.access, 'access is not set')
})
t.test('config delete multiple keys', async t => {
const { npm, home } = await loadMockNpm(t, {
homeDir: {
'.npmrc': 'access=public\nall=true\naudit=false',
},
})
await npm.exec('config', ['delete', 'access', 'all'])
t.equal(npm.config.get('access'), null, 'access should be defaulted')
t.equal(npm.config.get('all'), false, 'all should be defaulted')
const contents = await fs.readFile(join(home, '.npmrc'), { encoding: 'utf8' })
const rc = ini.parse(contents)
t.not(rc.access, 'access is not set')
t.not(rc.all, 'all is not set')
})
t.test('config delete key --location=global', async t => {
const { npm, globalPrefix } = await loadMockNpm(t, {
globalPrefixDir: {
etc: {
npmrc: 'access=public\nall=true',
},
},
config: {
location: 'global',
},
})
await npm.exec('config', ['delete', 'access'])
t.equal(npm.config.get('access', 'global'), undefined, 'access should be defaulted')
const contents = await fs.readFile(join(globalPrefix, 'etc/npmrc'), { encoding: 'utf8' })
const rc = ini.parse(contents)
t.not(rc.access, 'access is not set')
})
t.test('config delete key --global', async t => {
const { npm, globalPrefix } = await loadMockNpm(t, {
globalPrefixDir: {
etc: {
npmrc: 'access=public\nall=true',
},
},
config: {
global: true,
},
})
await npm.exec('config', ['delete', 'access'])
t.equal(npm.config.get('access', 'global'), undefined, 'access should no longer be set')
const contents = await fs.readFile(join(globalPrefix, 'etc/npmrc'), { encoding: 'utf8' })
const rc = ini.parse(contents)
t.not(rc.access, 'access is not set')
})
t.test('config set invalid option', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('config', ['set', 'nonexistantconfigoption', 'something']),
/not a valid npm option/
)
})
t.test('config set deprecated option', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('config', ['set', 'shrinkwrap', 'true']),
/deprecated/
)
})
t.test('config set nerf-darted option', async t => {
const { npm } = await loadMockNpm(t)
await npm.exec('config', ['set', '//npm.pkg.github.com/:_authToken', '0xdeadbeef'])
t.equal(
npm.config.get('//npm.pkg.github.com/:_authToken'),
'0xdeadbeef',
'nerf-darted config is set'
)
})
t.test('config set scoped optoin', async t => {
const { npm } = await loadMockNpm(t)
await npm.exec('config', ['set', '@npm:registry', 'https://registry.npmjs.org'])
t.equal(
npm.config.get('@npm:registry'),
'https://registry.npmjs.org',
'scoped config is set'
)
})
t.test('config set no args', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('config', ['set']),
{
code: 'EUSAGE',
},
'rejects with usage'
)
})
t.test('config set key', async t => {
const { npm, home } = await loadMockNpm(t, {
homeDir: {
'.npmrc': 'access=public',
},
})
await npm.exec('config', ['set', 'access'])
t.equal(npm.config.get('access'), null, 'set the value for access')
await t.rejects(fs.stat(join(home, '.npmrc'), { encoding: 'utf8' }), 'removed empty config')
})
t.test('config set key value', async t => {
const { npm, home } = await loadMockNpm(t, {
homeDir: {
'.npmrc': 'access=public',
},
})
await npm.exec('config', ['set', 'access', 'restricted'])
t.equal(npm.config.get('access'), 'restricted', 'set the value for access')
const contents = await fs.readFile(join(home, '.npmrc'), { encoding: 'utf8' })
const rc = ini.parse(contents)
t.equal(rc.access, 'restricted', 'access is set to restricted')
})
t.test('config set key value with equals', async t => {
const { npm, home } = await loadMockNpm(t, {
homeDir: {
'.npmrc': 'access=public',
},
})
await npm.exec('config', ['set', 'access=restricted'])
t.equal(npm.config.get('access'), 'restricted', 'set the value for access')
const contents = await fs.readFile(join(home, '.npmrc'), { encoding: 'utf8' })
const rc = ini.parse(contents)
t.equal(rc.access, 'restricted', 'access is set to restricted')
})
t.test('config set key1 value1 key2=value2 key3', async t => {
const { npm, home } = await loadMockNpm(t, {
homeDir: {
'.npmrc': 'access=public\nall=true\naudit=true',
},
})
await npm.exec('config', ['set', 'access', 'restricted', 'all=false', 'audit'])
t.equal(npm.config.get('access'), 'restricted', 'access was set')
t.equal(npm.config.get('all'), false, 'all was set')
t.equal(npm.config.get('audit'), true, 'audit was unset and restored to its default')
const contents = await fs.readFile(join(home, '.npmrc'), { encoding: 'utf8' })
const rc = ini.parse(contents)
t.equal(rc.access, 'restricted', 'access is set to restricted')
t.equal(rc.all, false, 'all is set to false')
t.not(contents.includes('audit='), 'config file does not set audit')
})
t.test('config set invalid key logs warning', async t => {
const { npm, logs, home } = await loadMockNpm(t)
// this doesn't reject, it only logs a warning
await npm.exec('config', ['set', 'access=foo'])
t.equal(logs.warn[0],
`invalid config access="foo" set in ${join(home, '.npmrc')}`,
'logged warning'
)
})
t.test('config set key=value --location=global', async t => {
const { npm, globalPrefix } = await loadMockNpm(t, {
globalPrefixDir: {
etc: {
npmrc: 'access=public\nall=true',
},
},
config: {
location: 'global',
},
})
await npm.exec('config', ['set', 'access=restricted'])
t.equal(npm.config.get('access', 'global'), 'restricted', 'foo should be set')
const contents = await fs.readFile(join(globalPrefix, 'etc/npmrc'), { encoding: 'utf8' })
const rc = ini.parse(contents)
t.equal(rc.access, 'restricted', 'access is set to restricted')
})
t.test('config set key=value --global', async t => {
const { npm, globalPrefix } = await loadMockNpm(t, {
globalPrefixDir: {
etc: {
npmrc: 'access=public\nall=true',
},
},
config: {
global: true,
},
})
await npm.exec('config', ['set', 'access=restricted'])
t.equal(npm.config.get('access', 'global'), 'restricted', 'access should be set')
const contents = await fs.readFile(join(globalPrefix, 'etc/npmrc'), { encoding: 'utf8' })
const rc = ini.parse(contents)
t.equal(rc.access, 'restricted', 'access is set to restricted')
})
t.test('config get no args', async t => {
const { npm, joinedOutput, clearOutput } = await loadMockNpm(t)
await npm.exec('config', ['get'])
const getOutput = joinedOutput()
clearOutput()
await npm.exec('config', ['list'])
const listOutput = joinedOutput()
t.equal(listOutput, getOutput, 'get with no args outputs list')
})
t.test('config get single key', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
await npm.exec('config', ['get', 'all'])
t.equal(joinedOutput(), `${npm.config.get('all')}`, 'should get the value')
})
t.test('config get multiple keys', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
await npm.exec('config', ['get', 'yes', 'all'])
t.equal(joinedOutput(), `yes=${npm.config.get('yes')}\nall=${npm.config.get('all')}`)
})
t.test('config get private key', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('config', ['get', '_authToken']),
/_authToken option is protected/,
'rejects with protected string'
)
await t.rejects(
npm.exec('config', ['get', 'authToken']),
/authToken option is protected/,
'rejects with protected string'
)
await t.rejects(
npm.exec('config', ['get', '//localhost:8080/:_password']),
/_password option is protected/,
'rejects with protected string'
)
})
t.test('config redacted values', async t => {
const { npm, joinedOutput, clearOutput } = await loadMockNpm(t)
await npm.exec('config', ['set', 'proxy', 'https://proxy.npmjs.org/'])
await npm.exec('config', ['get', 'proxy'])
t.equal(joinedOutput(), 'https://proxy.npmjs.org/')
clearOutput()
await npm.exec('config', ['set', 'proxy', 'https://u:password@proxy.npmjs.org/'])
await t.rejects(npm.exec('config', ['get', 'proxy']), /proxy option is protected/)
await npm.exec('config', ['ls'])
t.match(joinedOutput(), 'proxy = "https://u:***@proxy.npmjs.org/"')
clearOutput()
})
t.test('config edit', async t => {
const EDITOR = 'vim'
const editor = spawk.spawn(EDITOR).exit(0)
const { npm, home } = await loadMockNpm(t, {
homeDir: {
'.npmrc': 'foo=bar\nbar=baz',
},
config: {
editor: EDITOR,
},
})
await npm.exec('config', ['edit'])
t.ok(editor.called, 'editor was spawned')
t.same(
editor.calledWith.args,
[join(home, '.npmrc')],
'editor opened the user config file'
)
const contents = await fs.readFile(join(home, '.npmrc'), { encoding: 'utf8' })
t.ok(contents.includes('foo=bar'), 'kept foo')
t.ok(contents.includes('bar=baz'), 'kept bar')
t.ok(contents.includes('shown below with default values'), 'appends defaults to file')
})
t.test('config edit - editor exits non-0', async t => {
const EDITOR = 'vim'
const editor = spawk.spawn(EDITOR).exit(1)
const { npm, home } = await loadMockNpm(t, {
config: {
editor: EDITOR,
},
})
await t.rejects(
npm.exec('config', ['edit']),
{
message: 'editor process exited with code: 1',
},
'rejects with error about editor code'
)
t.ok(editor.called, 'editor was spawned')
t.same(
editor.calledWith.args,
[join(home, '.npmrc')],
'editor opened the user config file'
)
})
t.test('config fix', (t) => {
t.test('no problems', async (t) => {
const { npm, joinedOutput } = await loadMockNpm(t, {
homeDir: {
'.npmrc': '',
},
})
await npm.exec('config', ['fix'])
t.equal(joinedOutput(), '', 'printed nothing')
})
t.test('repairs all configs by default', async (t) => {
const { npm, home, globalPrefix, joinedOutput } = await loadMockNpm(t, {
globalPrefixDir: {
etc: {
npmrc: '_authtoken=notatoken\n_authToken=afaketoken',
},
},
homeDir: {
'.npmrc': '_authtoken=thisisinvalid\n_auth=beef',
},
})
const registry = `//registry.npmjs.org/`
await npm.exec('config', ['fix'])
// global config fixes
t.match(joinedOutput(), '`_authtoken` deleted from global config',
'output has deleted global _authtoken')
t.match(joinedOutput(), `\`_authToken\` renamed to \`${registry}:_authToken\` in global config`,
'output has renamed global _authToken')
t.not(npm.config.get('_authtoken', 'global'), '_authtoken is not set globally')
t.not(npm.config.get('_authToken', 'global'), '_authToken is not set globally')
t.equal(npm.config.get(`${registry}:_authToken`, 'global'), 'afaketoken',
'global _authToken was scoped')
const globalConfig = await fs.readFile(join(globalPrefix, 'etc/npmrc'), { encoding: 'utf8' })
t.equal(globalConfig, `${registry}:_authToken=afaketoken\n`, 'global config was written')
// user config fixes
t.match(joinedOutput(), '`_authtoken` deleted from user config',
'output has deleted user _authtoken')
t.match(joinedOutput(), `\`_auth\` renamed to \`${registry}:_auth\` in user config`,
'output has renamed user _auth')
t.not(npm.config.get('_authtoken', 'user'), '_authtoken is not set in user config')
t.not(npm.config.get('_auth'), '_auth is not set in user config')
t.equal(npm.config.get(`${registry}:_auth`, 'user'), 'beef', 'user _auth was scoped')
const userConfig = await fs.readFile(join(home, '.npmrc'), { encoding: 'utf8' })
t.equal(userConfig, `${registry}:_auth=beef\n`, 'user config was written')
})
t.test('repairs only the config specified by --location if asked', async (t) => {
const { npm, home, globalPrefix, joinedOutput } = await loadMockNpm(t, {
globalPrefixDir: {
etc: {
npmrc: '_authtoken=notatoken\n_authToken=afaketoken',
},
},
homeDir: {
'.npmrc': '_authtoken=thisisinvalid\n_auth=beef',
},
config: {
location: 'user',
},
})
const registry = `//registry.npmjs.org/`
await npm.exec('config', ['fix'])
// global config should be untouched
t.notMatch(joinedOutput(), '`_authtoken` deleted from global',
'output has deleted global _authtoken')
t.notMatch(joinedOutput(), `\`_authToken\` renamed to \`${registry}:_authToken\` in global`,
'output has renamed global _authToken')
t.equal(npm.config.get('_authtoken', 'global'), 'notatoken', 'global _authtoken untouched')
t.equal(npm.config.get('_authToken', 'global'), 'afaketoken', 'global _authToken untouched')
t.not(npm.config.get(`${registry}:_authToken`, 'global'), 'global _authToken not scoped')
const globalConfig = await fs.readFile(join(globalPrefix, 'etc/npmrc'), { encoding: 'utf8' })
t.equal(globalConfig, '_authtoken=notatoken\n_authToken=afaketoken',
'global config was not written')
// user config fixes
t.match(joinedOutput(), '`_authtoken` deleted from user',
'output has deleted user _authtoken')
t.match(joinedOutput(), `\`_auth\` renamed to \`${registry}:_auth\` in user`,
'output has renamed user _auth')
t.not(npm.config.get('_authtoken', 'user'), '_authtoken is not set in user config')
t.not(npm.config.get('_auth', 'user'), '_auth is not set in user config')
t.equal(npm.config.get(`${registry}:_auth`, 'user'), 'beef', 'user _auth was scoped')
const userConfig = await fs.readFile(join(home, '.npmrc'), { encoding: 'utf8' })
t.equal(userConfig, `${registry}:_auth=beef\n`, 'user config was written')
})
t.end()
})
t.test('completion', async t => {
const { config, npm } = await loadMockNpm(t, { command: 'config' })
const allKeys = Object.keys(npm.config.definitions)
const testComp = async (argv, expect, msg) => {
const options = Array.isArray(argv) ? {
conf: {
argv: {
remain: ['config', ...argv],
},
},
} : argv
options.conf.argv.remain.unshift('npm')
const res = await config.completion(options)
t.strictSame(res, expect, msg ?? argv.join(' '))
}
await testComp([], ['get', 'set', 'delete', 'ls', 'rm', 'edit', 'fix', 'list'])
await testComp(['set', 'foo'], [])
await testComp(['get'], allKeys)
await testComp(['set'], allKeys)
await testComp(['delete'], allKeys)
await testComp(['rm'], allKeys)
await testComp(['edit'], [])
await testComp(['fix'], [])
await testComp(['list'], [])
await testComp(['ls'], [])
await testComp({
conf: { argv: { remain: ['get'] } },
}, allKeys, 'also works for just npm get')
await testComp({
partialWord: 'l',
conf: { argv: { remain: ['config'] } },
}, ['get', 'set', 'delete', 'ls', 'rm', 'edit', 'fix'], 'and works on partials')
})

96
deps/npm/test/lib/commands/dedupe.js vendored Normal file
View File

@ -0,0 +1,96 @@
const t = require('tap')
const path = require('node:path')
const fs = require('node:fs')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const MockRegistry = require('@npmcli/mock-registry')
t.test('should throw in global mode', async (t) => {
const { npm } = await loadMockNpm(t, {
config: {
global: true,
},
})
t.rejects(
npm.exec('dedupe', []),
{ code: 'EDEDUPEGLOBAL' },
'throws EDEDUPEGLOBALE'
)
})
const treeWithDupes = {
'package.json': JSON.stringify({
name: 'test-top',
version: '1.0.0',
dependencies: {
'test-dep-a': '*',
'test-dep-b': '*',
},
}),
node_modules: {
'test-dep-a': {
'package.json': JSON.stringify({
name: 'test-dep-a',
version: '1.0.1',
dependencies: { 'test-sub': '*' },
}),
node_modules: {
'test-sub': {
'package.json': JSON.stringify({
name: 'test-sub',
version: '1.0.0',
}),
},
},
},
'test-dep-b': {
'package.json': JSON.stringify({
name: 'test-dep-b',
version: '1.0.0',
dependencies: { 'test-sub': '*' },
}),
node_modules: {
'test-sub': {
'package.json': JSON.stringify({
name: 'test-sub',
version: '1.0.0',
}),
},
},
},
},
}
t.test('dedupe', async (t) => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: treeWithDupes,
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifestSub = registry.manifest({
name: 'test-sub',
packuments: [{ version: '1.0.0' }],
})
await registry.package({
manifest: manifestSub,
tarballs: {
'1.0.0': path.join(npm.prefix, 'node_modules', 'test-dep-a', 'node_modules', 'test-sub'),
},
})
await npm.exec('dedupe', [])
t.match(joinedOutput(), /added 1 package, and removed 2 packages/)
t.ok(
fs.existsSync(path.join(npm.prefix, 'node_modules', 'test-sub')),
'test-sub was hoisted'
)
t.notOk(
fs.existsSync(path.join(npm.prefix, 'node_modules', 'test-dep-a', 'node_modules', 'test-sub')),
'test-dep-a/test-sub was removed'
)
t.notOk(
fs.existsSync(path.join(npm.prefix, 'node_modules', 'test-dep-b', 'node_modules', 'test-sub')),
'test-dep-b/test-sub was removed')
})

203
deps/npm/test/lib/commands/deprecate.js vendored Normal file
View File

@ -0,0 +1,203 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const MockRegistry = require('@npmcli/mock-registry')
const user = 'test-user'
const token = 'test-auth-token'
const auth = { '//registry.npmjs.org/:_authToken': token }
const versions = ['1.0.0', '1.0.1', '1.0.1-pre']
// libnpmaccess maps these to read-write and read-only
const packages = { foo: 'write', bar: 'write', baz: 'write', buzz: 'read' }
t.test('completion', async t => {
const { npm, deprecate } = await loadMockNpm(t, {
command: 'deprecate',
config: {
...auth,
},
})
const testComp = async (argv, expect) => {
const res =
await deprecate.completion({ conf: { argv: { remain: argv } } })
t.strictSame(res, expect, `completion: ${argv}`)
}
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: token,
})
registry.whoami({ username: user, times: 4 })
registry.getPackages({ team: user, packages, times: 4 })
await Promise.all([
testComp([], ['foo', 'bar', 'baz']),
testComp(['b'], ['bar', 'baz']),
testComp(['fo'], ['foo']),
testComp(['g'], []),
])
await testComp(['foo', 'something'], [])
registry.whoami({ responseCode: 401, body: {} })
await t.rejects(
testComp([], []),
{ code: 'E401' }
)
})
t.test('no args', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('deprecate', []),
{ code: 'EUSAGE' },
'logs usage'
)
})
t.test('only one arg', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('deprecate', ['foo']),
{ code: 'EUSAGE' },
'logs usage'
)
})
t.test('invalid semver range', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('deprecate', ['foo@notaversion', 'this will fail']),
/invalid version range/,
'logs semver error'
)
})
t.test('undeprecate', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { ...auth } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: token,
})
const manifest = registry.manifest({
name: 'foo',
versions,
})
await registry.package({ manifest, query: { write: true } })
registry.nock.put('/foo', body => {
for (const version of versions) {
if (body.versions[version].deprecated !== '') {
return false
}
}
return true
}).reply(200, {})
await npm.exec('deprecate', ['foo', ''])
t.match(joinedOutput(), '')
})
t.test('deprecates given range', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { ...auth } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: token,
})
const manifest = registry.manifest({
name: 'foo',
versions,
})
await registry.package({ manifest, query: { write: true } })
const message = 'test deprecation message'
registry.nock.put('/foo', body => {
if (body.versions['1.0.1'].deprecated) {
return false
}
if (body.versions['1.0.1-pre'].deprecated) {
return false
}
return body.versions['1.0.0'].deprecated === message
}).reply(200, {})
await npm.exec('deprecate', ['foo@1.0.0', message])
t.match(joinedOutput(), '')
})
t.test('deprecates all versions when no range is specified', async t => {
const { npm, logs, joinedOutput } = await loadMockNpm(t, { config: { ...auth } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: token,
})
const manifest = registry.manifest({
name: 'foo',
versions,
})
await registry.package({ manifest, query: { write: true } })
const message = 'test deprecation message'
registry.nock.put('/foo', body => {
for (const version of versions) {
if (body.versions[version].deprecated !== message) {
return false
}
}
return true
}).reply(200, {})
await npm.exec('deprecate', ['foo', message])
t.match(logs.notice, [
`deprecating foo@1.0.0 with message "${message}"`,
`deprecating foo@1.0.1 with message "${message}"`,
`deprecating foo@1.0.1-pre with message "${message}"`,
])
t.match(joinedOutput(), '')
})
t.test('dry-run', async t => {
const { npm, logs, joinedOutput } = await loadMockNpm(t, { config: {
'dry-run': true,
...auth,
} })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: token,
})
const manifest = registry.manifest({
name: 'foo',
versions,
})
await registry.package({ manifest, query: { write: true } })
const message = 'test deprecation message'
await npm.exec('deprecate', ['foo', message])
t.match(logs.notice, [
`deprecating foo@1.0.0 with message "${message}"`,
`deprecating foo@1.0.1 with message "${message}"`,
`deprecating foo@1.0.1-pre with message "${message}"`,
])
t.match(joinedOutput(), '')
})
t.test('does nothing if version does not actually exist', async t => {
const { npm, logs, joinedOutput } = await loadMockNpm(t, { config: { ...auth } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: token,
})
const manifest = registry.manifest({
name: 'foo',
versions,
})
await registry.package({ manifest, query: { write: true } })
await npm.exec('deprecate', ['foo@1.0.99', 'this should be ignored'])
t.match(joinedOutput(), '')
t.equal(logs.warn[0], 'deprecate No version found for 1.0.99')
})

1044
deps/npm/test/lib/commands/diff.js vendored Normal file

File diff suppressed because it is too large Load Diff

374
deps/npm/test/lib/commands/dist-tag.js vendored Normal file
View File

@ -0,0 +1,374 @@
const t = require('tap')
const realFetch = require('npm-registry-fetch')
const mockNpm = require('../../fixtures/mock-npm')
const fixtures = {
workspace: {
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
workspaces: ['workspace-a', 'workspace-b', 'workspace-c'],
}),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.0.0',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '1.0.0',
}),
},
'workspace-c': {
'package.json': JSON.stringify({
name: 'workspace-c',
version: '1.0.0',
}),
},
},
}
const tags = {
'/-/package/@scoped%2fpkg/dist-tags': {
latest: '1.0.0',
a: '0.0.1',
b: '0.5.0',
},
'/-/package/@scoped%2fanother/dist-tags': {
latest: '2.0.0',
a: '0.0.2',
b: '0.6.0',
},
'/-/package/@scoped%2fanother/dist-tags/c': {
latest: '7.7.7',
a: '0.0.2',
b: '0.6.0',
c: '7.7.7',
},
'/-/package/workspace-a/dist-tags': {
latest: '1.0.0',
'latest-a': '1.0.0',
},
'/-/package/workspace-b/dist-tags': {
latest: '2.0.0',
'latest-b': '2.0.0',
},
'/-/package/workspace-c/dist-tags': {
latest: '3.0.0',
'latest-c': '3.0.0',
},
}
const mockDist = async (t, { ...npmOpts } = {}) => {
const getTag = async (url) => ({ ...tags })[url]
let fetchOpts
const nrf = async (url, opts) => {
fetchOpts = opts
if (url === '/-/package/foo/dist-tags') {
throw new Error('no package found')
}
return getTag(url)
}
const mock = await mockNpm(t, {
...npmOpts,
command: 'dist-tag',
mocks: {
'npm-registry-fetch': Object.assign(nrf, realFetch, { json: getTag }),
},
})
return {
...mock,
distTag: mock['dist-tag'],
fetchOpts: () => fetchOpts,
result: () => mock.joinedOutput(),
joinedLogs: () => mock.logs.byTitle('dist-tag').join('\n').trim(),
}
}
t.test('ls in current package', async t => {
const { distTag, result } = await mockDist(t, {
prefixDir: {
'package.json': JSON.stringify({
name: '@scoped/pkg',
}),
},
})
await distTag.exec(['ls'])
t.matchSnapshot(
result(),
'should list available tags for current package'
)
})
t.test('ls global', async t => {
const { distTag } = await mockDist(t, {
config: {
global: true,
},
})
await t.rejects(
distTag.exec(['ls']),
distTag.usage,
'should throw basic usage'
)
})
t.test('no args in current package', async t => {
const { distTag, result } = await mockDist(t, {
prefixDir: {
'package.json': JSON.stringify({
name: '@scoped/pkg',
}),
},
})
await distTag.exec([])
t.matchSnapshot(
result(),
'should default to listing available tags for current package'
)
})
t.test('borked cmd usage', async t => {
const { distTag } = await mockDist(t)
await t.rejects(
distTag.exec(['borked', '@scoped/pkg']),
distTag.usage,
'should show usage error'
)
})
t.test('ls on named package', async t => {
const { distTag, result } = await mockDist(t)
await distTag.exec(['ls', '@scoped/another'])
t.matchSnapshot(
result(),
'should list tags for the specified package'
)
})
t.test('ls on missing package', async t => {
const { distTag, joinedLogs } = await mockDist(t)
await t.rejects(
distTag.exec(['ls', 'foo']),
distTag.usage
)
t.matchSnapshot(
joinedLogs(),
'should log no dist-tag found msg'
)
})
t.test('ls on missing name in current package', async t => {
const { distTag } = await mockDist(t, {
prefixDir: {
'package.json': JSON.stringify({
version: '1.0.0',
}),
},
})
await t.rejects(
distTag.exec(['ls']),
distTag.usage,
'should throw usage error message'
)
})
t.test('only named package arg', async t => {
const { distTag, result } = await mockDist(t)
await distTag.exec(['@scoped/another'])
t.matchSnapshot(
result(),
'should default to listing tags for the specified package'
)
})
t.test('workspaces', async t => {
const mockWorkspaces = async (t, exec = [], workspaces = true, prefixDir = {}) => {
const mock = await mockDist(t, {
prefixDir: {
...fixtures.workspace,
...prefixDir,
},
config: workspaces === true ? { workspaces } : { workspace: workspaces },
})
await mock.distTag.exec(exec)
return mock
}
t.test('no args', async t => {
const { result } = await mockWorkspaces(t)
t.matchSnapshot(result(), 'printed the expected output')
})
t.test('no args, one workspace', async t => {
const { result } = await mockWorkspaces(t, [], 'workspace-a')
t.matchSnapshot(result(), 'printed the expected output')
})
t.test('one arg -- cwd', async t => {
const { result } = await mockWorkspaces(t, ['.'])
t.matchSnapshot(result(), 'printed the expected output')
})
t.test('one arg -- .@1, ignores version spec', async t => {
const { result } = await mockWorkspaces(t, ['.@'])
t.matchSnapshot(result(), 'printed the expected output')
})
t.test('one arg -- list', async t => {
const { result } = await mockWorkspaces(t, ['list'])
t.matchSnapshot(result(), 'printed the expected output')
})
t.test('two args -- list, cwd', async t => {
const { result } = await mockWorkspaces(t, ['list', '.'])
t.matchSnapshot(result(), 'printed the expected output')
})
t.test('two args -- list, .@1, ignores version spec', async t => {
const { result } = await mockWorkspaces(t, ['list', '.@'])
t.matchSnapshot(result(), 'printed the expected output')
})
t.test('two args -- list, @scoped/pkg, logs a warning and ignores workspaces', async t => {
const { result, joinedLogs } = await mockWorkspaces(t, ['list', '@scoped/pkg'])
t.match(joinedLogs(), 'Ignoring workspaces for specified package', 'logs a warning')
t.matchSnapshot(result(), 'printed the expected output')
})
t.test('no args, one failing workspace sets exitCode to 1', async t => {
const { result, logs } = await mockWorkspaces(t, [], true, {
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
workspaces: ['workspace-a', 'workspace-b', 'workspace-c', 'workspace-d'],
}),
'workspace-d': {
'package.json': JSON.stringify({
name: 'workspace-d',
version: '1.0.0',
}),
},
})
const error = logs.error.byTitle('dist-tag ls')[0]
t.match(error, 'Couldn\'t get dist-tag data for Result {')
t.match(error, `name: 'workspace-d',`)
t.matchSnapshot(result(), 'printed the expected output')
})
})
t.test('add new tag', async t => {
const { distTag, result, fetchOpts } = await mockDist(t)
await distTag.exec(['add', '@scoped/another@7.7.7', 'c'])
const opts = fetchOpts()
t.equal(opts.method, 'PUT', 'should trigger request to add new tag')
t.equal(opts.body, '"7.7.7"', 'should point to expected version')
t.matchSnapshot(
result(),
'should return success msg'
)
})
t.test('add using valid semver range as name', async t => {
const { distTag, joinedLogs } = await mockDist(t)
await t.rejects(
distTag.exec(['add', '@scoped/another@7.7.7', '1.0.0']),
/Tag name must not be a valid SemVer range: 1.0.0/,
'should exit with semver range error'
)
t.matchSnapshot(
joinedLogs(),
'should return success msg'
)
})
t.test('add missing args', async t => {
const { distTag } = await mockDist(t, {
config: {
tag: '',
},
})
await t.rejects(
distTag.exec(['add', '@scoped/another@7.7.7']),
distTag.usage,
'should exit usage error message'
)
})
t.test('add missing pkg name', async t => {
const { distTag } = await mockDist(t)
await t.rejects(
distTag.exec(['add', null]),
distTag.usage,
'should exit usage error message'
)
})
t.test('add invalid tag', async t => {
const { distTag } = await mockDist(t)
await t.rejects(
distTag.exec(['add', '@tag']),
{ code: 'EINVALIDTAGNAME' },
'should exit with invalid tag name error'
)
})
t.test('set existing version', async t => {
const { distTag, joinedLogs } = await mockDist(t)
await distTag.exec(['set', '@scoped/another@0.6.0', 'b'])
t.matchSnapshot(
joinedLogs(),
'should log warn msg'
)
})
t.test('remove existing tag', async t => {
const { distTag, result, joinedLogs, fetchOpts } = await mockDist(t)
await distTag.exec(['rm', '@scoped/another', 'c'])
const opts = fetchOpts()
t.equal(opts.method, 'DELETE', 'should trigger request to remove tag')
t.matchSnapshot(joinedLogs(), 'should log remove info')
t.matchSnapshot(result(), 'should return success msg')
})
t.test('remove non-existing tag', async t => {
const { distTag, joinedLogs } = await mockDist(t)
await t.rejects(
distTag.exec(['rm', '@scoped/another', 'nonexistent']),
/nonexistent is not a dist-tag on @scoped\/another/,
'should exit with error'
)
t.matchSnapshot(joinedLogs(), 'should log error msg')
})
t.test('remove missing pkg name', async t => {
const { distTag } = await mockDist(t)
await t.rejects(
distTag.exec(['rm', null]),
distTag.usage,
'should exit usage error message'
)
})
t.test('completion', async t => {
const { distTag } = await mockDist(t)
const match = distTag.completion({ conf: { argv: { remain: ['npm', 'dist-tag'] } } })
t.resolveMatch(match, ['add', 'rm', 'ls'],
'should list npm dist-tag commands for completion')
const noMatch = distTag.completion({ conf: { argv: { remain: ['npm', 'dist-tag', 'foobar'] } } })
t.resolveMatch(noMatch, [])
})

163
deps/npm/test/lib/commands/docs.js vendored Normal file
View File

@ -0,0 +1,163 @@
const t = require('tap')
const mockNpm = require('../../fixtures/mock-npm.js')
const { sep } = require('node:path')
const fixtures = {
pkg: {
'package.json': JSON.stringify({
name: 'thispkg',
version: '1.2.3',
homepage: 'https://example.com',
}),
nodocs: {
'package.json': JSON.stringify({
name: 'nodocs',
version: '1.2.3',
}),
},
docsurl: {
'package.json': JSON.stringify({
name: 'docsurl',
version: '1.2.3',
homepage: 'https://bugzilla.localhost/docsurl',
}),
},
repourl: {
'package.json': JSON.stringify({
name: 'repourl',
version: '1.2.3',
repository: 'https://github.com/foo/repourl',
}),
},
repoobj: {
'package.json': JSON.stringify({
name: 'repoobj',
version: '1.2.3',
repository: { url: 'https://github.com/foo/repoobj' },
}),
},
repourlobj: {
'package.json': JSON.stringify({
name: 'repourlobj',
version: '1.2.3',
repository: { url: { works: false } },
}),
},
},
workspaces: {
'package.json': JSON.stringify({
name: 'workspaces-test',
version: '1.2.3-test',
workspaces: ['workspace-a', 'workspace-b', 'workspace-c'],
}),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.2.3-a',
homepage: 'http://docs.workspace-a/',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '1.2.3-n',
repository: 'https://github.com/npm/workspace-b',
}),
},
'workspace-c': JSON.stringify({
'package.json': {
name: 'workspace-n',
version: '1.2.3-n',
},
}),
},
}
const setup = async (t, { prefixDir = fixtures.pkg, config } = {}) => {
// keep a tally of which urls got opened
const opened = {}
const openUrl = async (_, url) => {
opened[url] = opened[url] || 0
opened[url]++
}
const res = await mockNpm(t, {
prefixDir,
mocks: {
'{LIB}/utils/open-url.js': { openUrl },
},
config,
})
return {
...res,
opened,
}
}
t.test('open docs urls', async t => {
const expect = {
nodocs: 'https://www.npmjs.com/package/nodocs',
docsurl: 'https://bugzilla.localhost/docsurl',
repourl: 'https://github.com/foo/repourl#readme',
repoobj: 'https://github.com/foo/repoobj#readme',
repourlobj: 'https://www.npmjs.com/package/repourlobj',
'.': 'https://example.com',
}
for (const [key, url] of Object.entries(expect)) {
await t.test(`open ${key} url`, async t => {
const { npm, opened } = await setup(t)
await npm.exec('docs', [['.', key].join(sep)])
t.strictSame({ [url]: 1 }, opened, `opened ${url}`)
})
}
})
t.test('open default package if none specified', async t => {
const { npm, opened } = await setup(t)
await npm.exec('docs', [])
t.strictSame({ 'https://example.com': 1 }, opened, 'opened expected url')
})
t.test('workspaces', async (t) => {
await t.test('all workspaces', async t => {
const { npm, opened } = await setup(t, {
prefixDir: fixtures.workspaces,
config: { workspaces: true },
})
await npm.exec('docs', [])
t.strictSame({
'http://docs.workspace-a/': 1,
'https://github.com/npm/workspace-b#readme': 1,
}, opened, 'opened two valid docs urls')
})
await t.test('one workspace', async t => {
const { npm, opened } = await setup(t, {
prefixDir: fixtures.workspaces,
config: { workspace: 'workspace-a' },
})
await npm.exec('docs', [])
t.strictSame({
'http://docs.workspace-a/': 1,
}, opened, 'opened one requested docs urls')
})
await t.test('invalid workspace', async t => {
const { npm, opened } = await setup(t, {
prefixDir: fixtures.workspaces,
config: { workspace: 'workspace-x' },
})
await t.rejects(
npm.exec('docs', []),
/No workspaces found/
)
await t.rejects(
npm.exec('docs', []),
/workspace-x/
)
t.match({}, opened, 'opened no docs urls')
})
})

671
deps/npm/test/lib/commands/doctor.js vendored Normal file
View File

@ -0,0 +1,671 @@
const t = require('tap')
const fs = require('node:fs/promises')
const path = require('node:path')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const tnock = require('../../fixtures/tnock.js')
const mockGlobals = require('@npmcli/mock-globals')
const { cleanCwd, cleanDate } = require('../../fixtures/clean-snapshot.js')
const cleanCacheSha = (str) =>
str.replace(/content-v2\/sha512\/[^"]+/g, 'content-v2/sha512/{sha}')
t.cleanSnapshot = p => cleanCacheSha(cleanDate(cleanCwd(p)))
.replace(/^\s+(doctor\s+at\s).*$\n/gm, '')
const npmManifest = (version) => {
return {
name: 'npm',
versions: {
[version]: {
name: 'npm',
version: version,
},
},
time: {
[version]: new Date(),
},
'dist-tags': { latest: version },
}
}
const nodeVersions = [
{ version: 'v2.0.1', lts: false },
{ version: 'v2.0.0', lts: false },
{ version: 'v1.0.0', lts: 'NpmTestium' },
]
const dirs = {
prefixDir: {
node_modules: {
testLink: t.fixture('symlink', './testDir'),
testDir: {
testFile: 'test contents',
},
'.bin': {},
},
},
globalPrefixDir: {
bin: {},
node_modules: {},
},
}
const globals = ({ globalPrefix }) => {
return {
process: {
'env.PATH': `${globalPrefix}:${path.join(globalPrefix, 'bin')}`,
version: 'v1.0.0',
},
}
}
mockGlobals(t, {
process: {
// set platform to not-windows before any tests because mockNpm
// sets the platform specific location of node_modules based on it
platform: 'test-not-windows',
// getuid and getgid do not exist in windows, so we shim them
// to return 0, as that is the value that lstat will assign the
// gid and uid properties for fs.Stats objects
...(process.platform === 'win32' ? { getuid: () => 0, getgid: () => 0 } : {}),
},
})
const mocks = {
'{ROOT}/package.json': { version: '1.0.0' },
which: async () => '/path/to/git',
cacache: {
verify: () => {
return { badContentCount: 0, reclaimedCount: 0, missingContent: 0, verifiedContent: 0 }
},
},
}
t.test('all clear', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await npm.exec('doctor', [])
t.matchSnapshot(joinedOutput(), 'output')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('all clear in color', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
config: {
color: 'always',
},
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await npm.exec('doctor', [])
t.matchSnapshot(joinedOutput(), 'everything is ok in color')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('silent success', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
config: {
loglevel: 'silent',
},
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await npm.exec('doctor', [])
t.matchSnapshot(joinedOutput(), 'output')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('silent errors', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
config: {
loglevel: 'silent',
},
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(404, '{}')
await t.rejects(npm.exec('doctor', ['ping']), {
message: /Check logs/,
})
t.matchSnapshot(joinedOutput(), 'output')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('ping 404', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(404, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []), {
message: /See above/,
})
t.matchSnapshot(joinedOutput(), 'ping 404')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('ping 404 in color', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
config: {
color: 'always',
},
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(404, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'ping 404 in color')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('ping exception with code', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').replyWithError({ message: 'Test Error', code: 'TEST' })
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'ping failure')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('ping exception without code', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').replyWithError({ message: 'Test Error', code: false })
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'ping failure')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('npm out of date', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest('2.0.0'))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'npm is out of date')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('node out of date - lts', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals: (context) => {
const g = globals(context)
return {
...g,
process: {
...g.process,
version: 'v0.0.1',
},
}
},
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'node is out of date')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('node out of date - current', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals: (context) => {
const g = globals(context)
return {
...g,
process: {
...g.process,
version: 'v2.0.0',
},
}
},
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'node is out of date')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('non-default registry', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
config: { registry: 'http://some-other-url.npmjs.org' },
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'non default registry')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('missing git', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks: {
...mocks,
which: async () => {
throw new Error('test error')
},
},
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'missing git')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('windows skips permissions checks', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals: (context) => {
const g = globals(context)
return {
...g,
process: {
...g.process,
platform: 'win32',
},
}
},
prefixDir: {},
globalPrefixDir: {},
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await npm.exec('doctor', [])
t.matchSnapshot(joinedOutput(), 'no permissions checks')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('missing global directories', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
prefixDir: dirs.prefixDir,
globalPrefixDir: {},
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'missing global directories')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('missing local node_modules', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
globalPrefixDir: dirs.globalPrefixDir,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await npm.exec('doctor', [])
t.matchSnapshot(joinedOutput(), 'missing local node_modules')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('incorrect owner', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks: {
...mocks,
'node:fs/promises': {
...fs,
lstat: async (p) => {
const stat = await fs.lstat(p)
if (p.endsWith('_cacache')) {
stat.uid += 1
stat.gid += 1
}
return stat
},
},
},
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'incorrect owner')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('incorrect permissions', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks: {
...mocks,
'node:fs/promises': {
...fs,
access: async () => {
throw new Error('Test Error')
},
},
},
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'incorrect owner')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('error reading directory', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks: {
...mocks,
'node:fs/promises': {
...fs,
readdir: async (s, ...args) => {
if (s.endsWith('_logs')) {
return fs.readdir(s, ...args)
}
// if (s.endsWith)
throw new Error('Test Error')
},
},
},
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'readdir error')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('cacache badContent', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks: {
...mocks,
cacache: {
verify: async () => {
return { badContentCount: 1, reclaimedCount: 0, missingContent: 0, verifiedContent: 2 }
},
},
},
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await npm.exec('doctor', [])
t.matchSnapshot(joinedOutput(), 'corrupted cache content')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('cacache reclaimedCount', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks: {
...mocks,
cacache: {
verify: async () => {
return { badContentCount: 0, reclaimedCount: 1, missingContent: 0, verifiedContent: 2 }
},
},
},
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await npm.exec('doctor', [])
t.matchSnapshot(joinedOutput(), 'content garbage collected')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('cacache missingContent', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks: {
...mocks,
cacache: {
verify: async () => {
return { badContentCount: 0, reclaimedCount: 0, missingContent: 1, verifiedContent: 2 }
},
},
},
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await npm.exec('doctor', [])
t.matchSnapshot(joinedOutput(), 'missing content')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('bad proxy', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
config: {
proxy: 'ssh://npmjs.org',
},
...dirs,
})
await t.rejects(npm.exec('doctor', []))
t.matchSnapshot(joinedOutput(), 'output')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('discrete checks', t => {
t.test('ping', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
await npm.exec('doctor', ['ping'])
t.matchSnapshot(joinedOutput(), 'output')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('versions', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/npm').reply(200, npmManifest(npm.version))
tnock(t, 'https://nodejs.org')
.get('/dist/index.json').reply(200, nodeVersions)
await npm.exec('doctor', ['versions'])
t.matchSnapshot(joinedOutput(), 'output')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('registry', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
})
tnock(t, npm.config.get('registry'))
.get('/-/ping').reply(200, '{}')
await npm.exec('doctor', ['registry'])
t.matchSnapshot(joinedOutput(), 'output')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('git', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
})
await npm.exec('doctor', ['git'])
t.matchSnapshot(joinedOutput(), 'output')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('permissions - not windows', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
})
await npm.exec('doctor', ['permissions'])
t.matchSnapshot(joinedOutput(), 'output')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('cache', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals,
...dirs,
})
await npm.exec('doctor', ['cache'])
t.matchSnapshot(joinedOutput(), 'output')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('permissions - windows', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals: (context) => {
const g = globals(context)
return {
...g,
process: {
...g.process,
platform: 'win32',
},
}
},
prefixDir: {},
globalPrefixDir: {},
})
await npm.exec('doctor', ['permissions'])
t.matchSnapshot(joinedOutput(), 'output')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.test('invalid environment', async t => {
const { joinedOutput, logs, npm } = await loadMockNpm(t, {
mocks,
globals: (context) => {
const g = globals(context)
return {
...g,
process: {
...g.process,
'env.PATH': '/nope',
},
}
},
prefixDir: {},
globalPrefixDir: {},
})
await t.rejects(npm.exec('doctor', ['environment']))
t.matchSnapshot(joinedOutput(), 'output')
t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs')
})
t.end()
})

131
deps/npm/test/lib/commands/edit.js vendored Normal file
View File

@ -0,0 +1,131 @@
const t = require('tap')
const path = require('node:path')
const tspawk = require('../../fixtures/tspawk')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const spawk = tspawk(t)
const npmConfig = {
config: {
'ignore-scripts': false,
editor: 'testeditor',
'script-shell': process.platform === 'win32' ? process.env.COMSPEC : 'sh',
},
prefixDir: {
node_modules: {
semver: {
'package.json': JSON.stringify({
scripts: {
install: 'testinstall',
},
}),
node_modules: {
abbrev: {},
},
},
'@npmcli': {
'scoped-package': {},
},
},
},
}
const isCmdRe = /(?:^|\\)cmd(?:\.exe)?$/i
t.test('npm edit', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, npmConfig)
const semverPath = path.resolve(npm.prefix, 'node_modules', 'semver')
spawk.spawn('testeditor', [semverPath])
const scriptShell = npm.config.get('script-shell')
const scriptArgs = isCmdRe.test(scriptShell)
? ['/d', '/s', '/c', 'testinstall']
: ['-c', 'testinstall']
spawk.spawn(scriptShell, scriptArgs, { cwd: semverPath })
await npm.exec('edit', ['semver'])
t.match(joinedOutput(), 'rebuilt dependencies successfully')
})
t.test('rebuild failure', async t => {
const { npm } = await loadMockNpm(t, npmConfig)
const semverPath = path.resolve(npm.prefix, 'node_modules', 'semver')
spawk.spawn('testeditor', [semverPath])
const scriptShell = npm.config.get('script-shell')
const scriptArgs = isCmdRe.test(scriptShell)
? ['/d', '/s', '/c', 'testinstall']
: ['-c', 'testinstall']
spawk.spawn(scriptShell, scriptArgs, { cwd: semverPath }).exit(1).stdout('test error')
await t.rejects(
npm.exec('edit', ['semver']),
{ message: 'command failed' }
)
})
t.test('editor failure', async t => {
const { npm } = await loadMockNpm(t, npmConfig)
const semverPath = path.resolve(npm.prefix, 'node_modules', 'semver')
spawk.spawn('testeditor', [semverPath]).exit(1).stdout('test editor failure')
await t.rejects(
npm.exec('edit', ['semver']),
{ message: 'editor process exited with code: 1' }
)
})
t.test('npm edit editor has flags', async t => {
const { npm } = await loadMockNpm(t, {
...npmConfig,
config: {
...npmConfig.config,
editor: 'testeditor --flag',
},
})
const semverPath = path.resolve(npm.prefix, 'node_modules', 'semver')
spawk.spawn('testeditor', ['--flag', semverPath])
const scriptShell = npm.config.get('script-shell')
const scriptArgs = isCmdRe.test(scriptShell)
? ['/d', '/s', '/c', 'testinstall']
: ['-c', 'testinstall']
spawk.spawn(scriptShell, scriptArgs, { cwd: semverPath })
await npm.exec('edit', ['semver'])
})
t.test('npm edit no args', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('edit', []),
{ code: 'EUSAGE' },
'throws usage error'
)
})
t.test('npm edit nonexistent package', async t => {
const { npm } = await loadMockNpm(t, npmConfig)
await t.rejects(
npm.exec('edit', ['abbrev']),
/lstat/
)
})
t.test('scoped package', async t => {
const { npm } = await loadMockNpm(t, npmConfig)
const scopedPath = path.resolve(npm.prefix, 'node_modules', '@npmcli', 'scoped-package')
spawk.spawn('testeditor', [scopedPath])
await npm.exec('edit', ['@npmcli/scoped-package'])
})
t.test('subdependency', async t => {
const { npm } = await loadMockNpm(t, npmConfig)
const subdepPath = path.resolve(npm.prefix, 'node_modules', 'semver', 'node_modules', 'abbrev')
spawk.spawn('testeditor', [subdepPath])
await npm.exec('edit', ['semver/abbrev'])
})

280
deps/npm/test/lib/commands/exec.js vendored Normal file
View File

@ -0,0 +1,280 @@
const t = require('tap')
const fs = require('node:fs/promises')
const path = require('node:path')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const MockRegistry = require('@npmcli/mock-registry')
t.test('call with args', async t => {
const { npm } = await loadMockNpm(t, {
config: {
call: 'foo',
},
})
await t.rejects(
npm.exec('exec', ['bar']),
{ code: 'EUSAGE' }
)
})
t.test('registry package', async t => {
const registry = new MockRegistry({
tap: t,
registry: 'https://registry.npmjs.org/',
})
const manifest = registry.manifest({ name: '@npmcli/npx-test' })
manifest.versions['1.0.0'].bin = { 'npx-test': 'index.js' }
const { npm } = await loadMockNpm(t, {
config: {
audit: false,
yes: true,
},
prefixDir: {
'npm-exec-test': {
'package.json': JSON.stringify(manifest),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-success', '')`,
},
},
})
await registry.package({
times: 2,
manifest,
tarballs: {
'1.0.0': path.join(npm.prefix, 'npm-exec-test'),
} })
await npm.exec('exec', ['@npmcli/npx-test'])
const exists = await fs.stat(path.join(npm.prefix, 'npm-exec-test-success'))
t.ok(exists.isFile(), 'bin ran, creating file')
})
t.test('--prefix', async t => {
const registry = new MockRegistry({
tap: t,
registry: 'https://registry.npmjs.org/',
})
const manifest = registry.manifest({ name: '@npmcli/npx-test' })
manifest.versions['1.0.0'].bin = { 'npx-test': 'index.js' }
const { npm } = await loadMockNpm(t, {
config: {
audit: false,
yes: true,
},
prefixDir: {
'npm-exec-test': {
'package.json': JSON.stringify(manifest),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-success', '')`,
},
},
})
// This is what `--prefix` does
npm.config.globalPrefix = npm.config.localPrefix
await registry.package({
manifest,
tarballs: {
'1.0.0': path.join(npm.prefix, 'npm-exec-test'),
} })
await npm.exec('exec', ['@npmcli/npx-test'])
const exists = await fs.stat(path.join(npm.prefix, 'npm-exec-test-success'))
t.ok(exists.isFile(), 'bin ran, creating file')
})
t.test('runs in workspace path', async t => {
const registry = new MockRegistry({
tap: t,
registry: 'https://registry.npmjs.org/',
})
const manifest = registry.manifest({ name: '@npmcli/npx-test' })
manifest.versions['1.0.0'].bin = { 'npx-test': 'index.js' }
const { npm } = await loadMockNpm(t, {
config: {
audit: false,
yes: true,
workspace: ['workspace-a'],
},
prefixDir: {
'npm-exec-test': {
'package.json': JSON.stringify(manifest),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-success', '')`,
},
'package.json': JSON.stringify({
name: '@npmcli/npx-workspace-test',
workspaces: ['workspace-a'],
}),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
}),
},
},
})
await registry.package({ manifest,
tarballs: {
'1.0.0': path.join(npm.prefix, 'npm-exec-test'),
},
})
await npm.exec('exec', ['@npmcli/npx-test'])
const exists = await fs.stat(path.join(npm.prefix, 'workspace-a', 'npm-exec-test-success'))
t.ok(exists.isFile(), 'bin ran, creating file inside workspace')
})
t.test('finds workspace bin first', async t => {
const { npm } = await loadMockNpm(t, {
config: {
workspace: ['workspace-a'],
},
prefixDir: {
'package.json': JSON.stringify({
name: '@npmcli/npx-workspace-root-test',
bin: { 'npx-test': 'index.js' },
workspaces: ['workspace-a'],
}),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-fail', '')`,
'workspace-a': {
'package.json': JSON.stringify({
name: '@npmcli/npx-workspace-test',
bin: { 'npx-test': 'index.js' },
}),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-success', '')`,
},
},
})
await npm.exec('install', []) // reify
await npm.exec('exec', ['npx-test'])
const exists = await fs.stat(path.join(npm.prefix, 'workspace-a', 'npm-exec-test-success'))
t.ok(exists.isFile(), 'bin ran, creating file inside workspace')
t.rejects(fs.stat(path.join(npm.prefix, 'npm-exec-test-fail')))
})
t.test('finds workspace dep first', async t => {
const registry = new MockRegistry({
tap: t,
registry: 'https://registry.npmjs.org/',
})
const manifest = registry.manifest({ name: '@npmcli/subdep', versions: ['1.0.0', '2.0.0'] })
manifest.versions['1.0.0'].bin = { 'npx-test': 'index.js' }
manifest.versions['2.0.0'].bin = { 'npx-test': 'index.js' }
const { npm } = await loadMockNpm(t, {
prefixDir: {
subdep: {
one: {
'package.json': JSON.stringify(manifest.versions['1.0.0']),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-one', '')`,
},
two: {
'package.json': JSON.stringify(manifest.versions['2.0.0']),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-two', '')`,
},
},
'package.json': JSON.stringify({
name: '@npmcli/npx-workspace-root-test',
dependencies: { '@npmcli/subdep': '1.0.0' },
bin: { 'npx-test': 'index.js' },
workspaces: ['workspace-a'],
}),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-fail', '')`,
'workspace-a': {
'package.json': JSON.stringify({
name: '@npmcli/npx-workspace-test',
dependencies: { '@npmcli/subdep': '2.0.0' },
bin: { 'npx-test': 'index.js' },
}),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-success', '')`,
},
},
})
await registry.package({ manifest,
tarballs: {
'1.0.0': path.join(npm.prefix, 'subdep', 'one'),
'2.0.0': path.join(npm.prefix, 'subdep', 'two'),
},
})
await npm.exec('install', [])
npm.config.set('workspace', ['workspace-a'])
await npm.exec('exec', ['npx-test'])
const exists = await fs.stat(path.join(npm.prefix, 'workspace-a', 'npm-exec-test-success'))
t.ok(exists.isFile(), 'bin ran, creating file')
})
t.test('npx --no-install @npmcli/npx-test', async t => {
const registry = new MockRegistry({
tap: t,
registry: 'https://registry.npmjs.org/',
})
const manifest = registry.manifest({ name: '@npmcli/npx-test' })
manifest.versions['1.0.0'].bin = { 'npx-test': 'index.js' }
const { npm } = await loadMockNpm(t, {
config: {
audit: false,
yes: false,
},
prefixDir: {
'npm-exec-test': {
'package.json': JSON.stringify(manifest),
'index.js': `#!/usr/bin/env node
require('fs').writeFileSync('npm-exec-test-success', '')`,
},
},
})
try {
await npm.exec('exec', ['@npmcli/npx-test'])
t.fail('Expected error was not thrown')
} catch (error) {
t.match(
error.message,
'npx canceled due to missing packages and no YES option: ',
'Expected error message thrown'
)
}
})
t.test('packs from git spec', async t => {
const spec = 'test/test#111111aaaaaaaabbbbbbbbccccccdddddddeeeee'
const pkgPath = path.resolve(__dirname, '../../fixtures/git-test.tgz')
const srv = MockRegistry.tnock(t, 'https://codeload.github.com')
srv.get('/test/test/tar.gz/111111aaaaaaaabbbbbbbbccccccdddddddeeeee')
.times(2)
.reply(200, await fs.readFile(pkgPath))
const { npm } = await loadMockNpm(t, {
config: {
audit: false,
yes: true,
},
})
try {
await npm.exec('exec', [spec])
const exists = await fs.stat(path.join(npm.prefix, 'npm-exec-test-success'))
t.ok(exists.isFile(), 'bin ran, creating file')
} catch (err) {
t.fail(err, "shouldn't throw")
}
})

277
deps/npm/test/lib/commands/explain.js vendored Normal file
View File

@ -0,0 +1,277 @@
const t = require('tap')
const { resolve } = require('node:path')
const mockNpm = require('../../fixtures/mock-npm.js')
const mockExplain = async (t, opts) => {
const mock = await mockNpm(t, {
command: 'explain',
mocks: {
// keep the snapshots pared down a bit, since this has its own tests.
'{LIB}/utils/explain-dep.js': {
explainNode: (expl, depth, chalk) => {
const color = chalk.level !== 0
return `${expl.name}@${expl.version} depth=${depth} color=${color}`
},
},
},
...opts,
})
return mock
}
t.test('no args throws usage', async t => {
const { explain } = await mockExplain(t)
await t.rejects(
explain.exec([]),
explain.usage
)
})
t.test('no match throws not found', async t => {
const { explain } = await mockExplain(t)
await t.rejects(
explain.exec(['foo@1.2.3', 'node_modules/baz']),
'No dependencies found matching foo@1.2.3, node_modules/baz'
)
})
t.test('invalid package name throws not found', async t => {
const { explain } = await mockExplain(t)
const badName = ' not a valid package name '
await t.rejects(
explain.exec([`${badName}@1.2.3`]),
`No dependencies found matching ${badName}@1.2.3`
)
})
t.test('explain some nodes', async t => {
const mockNodes = async (t, config = {}) => {
const mock = await mockExplain(t, {
prefixDir: {
node_modules: {
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.2.3',
dependencies: {
bar: '*',
},
}),
},
bar: {
'package.json': JSON.stringify({
name: 'bar',
version: '1.2.3',
}),
},
baz: {
'package.json': JSON.stringify({
name: 'baz',
version: '1.2.3',
dependencies: {
foo: '*',
bar: '2',
},
}),
node_modules: {
bar: {
'package.json': JSON.stringify({
name: 'bar',
version: '2.3.4',
}),
},
extra: {
'package.json': JSON.stringify({
name: 'extra',
version: '99.9999.999999',
description: 'extraneous package',
}),
},
},
},
},
'package.json': JSON.stringify({
dependencies: {
baz: '1',
},
}),
},
config: {
color: 'always',
...config,
},
})
return mock
}
t.test('works with the location', async t => {
const path = 'node_modules/foo'
const { explain, joinedOutput } = await mockNodes(t)
await explain.exec([path])
t.strictSame(joinedOutput(), 'foo@1.2.3 depth=Infinity color=true')
})
t.test('works with a full actual path', async t => {
const { npm, explain, joinedOutput } = await mockNodes(t)
const path = resolve(npm.prefix, 'node_modules/foo')
await explain.exec([path])
t.strictSame(joinedOutput(), 'foo@1.2.3 depth=Infinity color=true')
})
t.test('finds all nodes by name', async t => {
const { explain, joinedOutput } = await mockNodes(t)
await explain.exec(['bar'])
t.strictSame(joinedOutput(),
'bar@1.2.3 depth=Infinity color=true\n\n' +
'bar@2.3.4 depth=Infinity color=true'
)
})
t.test('finds only nodes that match the spec', async t => {
const { explain, joinedOutput } = await mockNodes(t)
await explain.exec(['bar@1'])
t.strictSame(joinedOutput(), 'bar@1.2.3 depth=Infinity color=true')
})
t.test('finds extraneous nodes', async t => {
const { explain, joinedOutput } = await mockNodes(t)
await explain.exec(['extra'])
t.strictSame(joinedOutput(), 'extra@99.9999.999999 depth=Infinity color=true')
})
t.test('json output', async t => {
const { explain, joinedOutput } = await mockNodes(t, { json: true })
await explain.exec(['node_modules/foo'])
t.match(JSON.parse(joinedOutput()), [{
name: 'foo',
version: '1.2.3',
dependents: Array,
}])
})
t.test('report if no nodes found', async t => {
const { explain } = await mockNodes(t)
await t.rejects(
explain.exec(['asdf/foo/bar', 'quux@1.x']),
'No dependencies found matching asdf/foo/bar, quux@1.x'
)
})
})
t.test('workspaces', async t => {
const mockWorkspaces = async (t, exec = [], workspaces = true) => {
const mock = await mockExplain(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'workspaces-project',
version: '1.0.0',
workspaces: ['packages/*'],
dependencies: {
abbrev: '^1.0.0',
},
}),
node_modules: {
a: t.fixture('symlink', '../packages/a'),
b: t.fixture('symlink', '../packages/b'),
c: t.fixture('symlink', '../packages/c'),
once: {
'package.json': JSON.stringify({
name: 'once',
version: '1.0.0',
dependencies: {
wrappy: '2.0.0',
},
}),
},
abbrev: {
'package.json': JSON.stringify({
name: 'abbrev',
version: '1.0.0',
}),
},
wrappy: {
'package.json': JSON.stringify({
name: 'wrappy',
version: '2.0.0',
}),
},
},
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
dependencies: {
once: '1.0.0',
},
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
dependencies: {
abbrev: '^1.0.0',
},
}),
},
c: {
'package.json': JSON.stringify({
name: 'c',
version: '1.0.0',
}),
},
},
},
config: {
...(typeof workspaces === 'boolean' ? { workspaces } : { workspace: workspaces }),
color: 'always',
},
})
await mock.explain.exec(exec)
return mock.joinedOutput()
}
t.test('should explain workspaces deps', async t => {
const OUTPUT = await mockWorkspaces(t, ['wrappy'])
t.strictSame(
OUTPUT,
'wrappy@2.0.0 depth=Infinity color=true'
)
})
t.test('should explain deps when filtering to a single ws', async t => {
const OUTPUT = await mockWorkspaces(t, ['wrappy'], ['a'])
t.strictSame(
OUTPUT,
'wrappy@2.0.0 depth=Infinity color=true'
)
})
t.test('should explain deps of workspaces only', async t => {
const OUTPUT = await mockWorkspaces(t, ['abbrev'])
t.strictSame(
OUTPUT,
'abbrev@1.0.0 depth=Infinity color=true'
)
})
t.test('should throw usage if dep not found within filtered ws', async t => {
await t.rejects(
mockWorkspaces(t, ['abbrev'], ['a']),
'No dependencies found matching abbrev'
)
})
t.test('workspaces disabled', async t => {
await t.rejects(
mockWorkspaces(t, ['once'], false),
'No dependencies found matching once',
'should throw usage if dep not found when excluding ws'
)
})
})

175
deps/npm/test/lib/commands/explore.js vendored Normal file
View File

@ -0,0 +1,175 @@
const t = require('tap')
const mockNpm = require('../../fixtures/mock-npm')
const { cleanCwd } = require('../../fixtures/clean-snapshot')
const mockExplore = async (t, exec, {
PJ_ERROR = null,
RUN_SCRIPT_ERROR = null,
RUN_SCRIPT_EXIT_CODE = 0,
RUN_SCRIPT_SIGNAL = null,
} = {}) => {
let PJ_CALLED = ''
const mockPJ = {
normalize: async path => {
if (PJ_ERROR) {
throw PJ_ERROR
}
PJ_CALLED = cleanCwd(path)
return { content: { some: 'package' } }
},
}
let RUN_SCRIPT_EXEC = null
const mockRunScript = ({ pkg, event }) => {
if (event !== '_explore') {
throw new Error('got wrong event name')
}
RUN_SCRIPT_EXEC = pkg.scripts._explore
if (RUN_SCRIPT_ERROR) {
return Promise.reject(RUN_SCRIPT_ERROR)
}
if (RUN_SCRIPT_EXIT_CODE || RUN_SCRIPT_SIGNAL) {
return Promise.reject(Object.assign(new Error('command failed'), {
code: RUN_SCRIPT_EXIT_CODE,
signal: RUN_SCRIPT_SIGNAL,
}))
}
return Promise.resolve({ code: 0, signal: null })
}
const mock = await mockNpm(t, {
mocks: {
'@npmcli/package-json': mockPJ,
'@npmcli/run-script': mockRunScript,
},
config: {
shell: 'shell-command',
},
})
await mock.npm.exec('explore', exec)
return {
...mock,
PJ_CALLED,
RUN_SCRIPT_EXEC,
output: cleanCwd(mock.joinedOutput()).trim(),
}
}
t.test('basic interactive', async t => {
const {
output,
PJ_CALLED,
RUN_SCRIPT_EXEC,
} = await mockExplore(t, ['pkg'])
t.ok(PJ_CALLED.endsWith('/pkg'))
t.strictSame(RUN_SCRIPT_EXEC, 'shell-command')
t.match(output, /Exploring \{CWD\}\/[\w-_/]+\nType 'exit' or \^D when finished/)
})
t.test('interactive tracks exit code', async t => {
t.test('code', async t => {
const {
output,
PJ_CALLED,
RUN_SCRIPT_EXEC,
} = await mockExplore(t, ['pkg'], { RUN_SCRIPT_EXIT_CODE: 99 })
t.ok(PJ_CALLED.endsWith('/pkg'))
t.strictSame(RUN_SCRIPT_EXEC, 'shell-command')
t.match(output, /Exploring \{CWD\}\/[\w-_/]+\nType 'exit' or \^D when finished/)
t.equal(process.exitCode, 99)
})
t.test('spawn fail', async t => {
const RUN_SCRIPT_ERROR = Object.assign(new Error('glorb'), {
code: 33,
})
await t.rejects(
mockExplore(t, ['pkg'], { RUN_SCRIPT_ERROR }),
{ message: 'glorb', code: 33 }
)
t.equal(process.exitCode, 33)
})
t.test('spawn fail, 0 exit code', async t => {
const RUN_SCRIPT_ERROR = Object.assign(new Error('glorb'), {
code: 0,
})
await t.rejects(
mockExplore(t, ['pkg'], { RUN_SCRIPT_ERROR }),
{ message: 'glorb', code: 0 }
)
t.equal(process.exitCode, 1)
})
t.test('spawn fail, no exit code', async t => {
const RUN_SCRIPT_ERROR = Object.assign(new Error('command failed'), {
code: 'EPROBLEM',
})
await t.rejects(
mockExplore(t, ['pkg'], { RUN_SCRIPT_ERROR }),
{ message: 'command failed', code: 'EPROBLEM' }
)
t.equal(process.exitCode, 1)
})
})
t.test('basic non-interactive', async t => {
const {
output,
PJ_CALLED,
RUN_SCRIPT_EXEC,
} = await mockExplore(t, ['pkg', 'ls'])
t.ok(PJ_CALLED.endsWith('/pkg'))
t.strictSame(RUN_SCRIPT_EXEC, 'ls')
t.strictSame(output, '')
})
t.test('signal fails non-interactive', async t => {
await t.rejects(
mockExplore(t, ['pkg', 'ls'], { RUN_SCRIPT_SIGNAL: 'SIGPROBLEM' }),
{
message: 'command failed',
signal: 'SIGPROBLEM',
}
)
})
t.test('usage if no pkg provided', async t => {
const noPkg = [
[],
['foo/../..'],
['asdf/..'],
['.'],
['..'],
['../..'],
]
for (const args of noPkg) {
t.test(JSON.stringify(args), async t => {
await t.rejects(
mockExplore(t, args),
'Usage:'
)
})
}
})
t.test('pkg not installed', async t => {
const PJ_ERROR = new Error('plurple')
await t.rejects(
mockExplore(t, ['pkg', 'ls'], { PJ_ERROR }),
{ message: 'plurple' }
)
})

View File

@ -0,0 +1,84 @@
const t = require('tap')
const path = require('node:path')
const fs = require('node:fs')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const MockRegistry = require('@npmcli/mock-registry')
const treeWithDupes = {
'package.json': JSON.stringify({
name: 'test-top',
version: '1.0.0',
dependencies: {
'test-dep-a': '*',
'test-dep-b': '*',
},
}),
node_modules: {
'test-dep-a': {
'package.json': JSON.stringify({
name: 'test-dep-a',
version: '1.0.1',
dependencies: { 'test-sub': '*' },
}),
node_modules: {
'test-sub': {
'package.json': JSON.stringify({
name: 'test-sub',
version: '1.0.0',
}),
},
},
},
'test-dep-b': {
'package.json': JSON.stringify({
name: 'test-dep-b',
version: '1.0.0',
dependencies: { 'test-sub': '*' },
}),
node_modules: {
'test-sub': {
'package.json': JSON.stringify({
name: 'test-sub',
version: '1.0.0',
}),
},
},
},
},
}
t.test('should run dedupe in dryRun mode', async (t) => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: treeWithDupes,
config: {
// explicitly set to false so we can be 100% sure it's always true when it
// hits arborist
'dry-run': false,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifestSub = registry.manifest({
name: 'test-sub',
manifests: [{ name: 'test-sub', version: '1.0.0' }],
})
await registry.package({ manifest: manifestSub })
await npm.exec('find-dupes', [])
t.match(joinedOutput(), /added 1 package, and removed 2 packages/)
t.notOk(
fs.existsSync(path.join(npm.prefix, 'node_modules', 'test-sub')),
'test-sub was not hoisted'
)
t.ok(
fs.existsSync(path.join(npm.prefix, 'node_modules', 'test-dep-a', 'node_modules', 'test-sub')),
'test-dep-a/test-sub was not removed'
)
t.ok(
fs.existsSync(path.join(npm.prefix, 'node_modules', 'test-dep-b', 'node_modules', 'test-sub')),
'test-dep-b/test-sub was not removed')
})

824
deps/npm/test/lib/commands/fund.js vendored Normal file
View File

@ -0,0 +1,824 @@
const t = require('tap')
const mockNpm = require('../../fixtures/mock-npm')
const version = '1.0.0'
const funding = {
type: 'individual',
url: 'http://example.com/donate',
}
const maintainerOwnsAllDeps = {
'package.json': JSON.stringify({
name: 'maintainer-owns-all-deps',
version,
funding,
dependencies: {
'dep-foo': '*',
'dep-bar': '*',
},
}),
node_modules: {
'dep-foo': {
'package.json': JSON.stringify({
name: 'dep-foo',
version,
funding,
dependencies: {
'dep-sub-foo': '*',
},
}),
node_modules: {
'dep-sub-foo': {
'package.json': JSON.stringify({
name: 'dep-sub-foo',
version,
funding,
}),
},
},
},
'dep-bar': {
'package.json': JSON.stringify({
name: 'dep-bar',
version,
funding,
}),
},
},
}
const nestedNoFundingPackages = {
'package.json': JSON.stringify({
name: 'nested-no-funding-packages',
version,
dependencies: {
foo: '*',
},
devDependencies: {
lorem: '*',
},
}),
node_modules: {
foo: {
'package.json': JSON.stringify({
name: 'foo',
version,
dependencies: {
bar: '*',
},
}),
node_modules: {
bar: {
'package.json': JSON.stringify({
name: 'bar',
version,
funding,
}),
node_modules: {
'sub-bar': {
'package.json': JSON.stringify({
name: 'sub-bar',
version,
funding: 'https://example.com/sponsor',
}),
},
},
},
},
},
lorem: {
'package.json': JSON.stringify({
name: 'lorem',
version,
funding: {
url: 'https://example.com/lorem',
},
}),
},
},
}
const nestedMultipleFundingPackages = {
'package.json': JSON.stringify({
name: 'nested-multiple-funding-packages',
version,
funding: ['https://one.example.com', 'https://two.example.com'],
dependencies: {
foo: '*',
},
devDependencies: {
bar: '*',
},
}),
node_modules: {
foo: {
'package.json': JSON.stringify({
name: 'foo',
version,
funding: [
'http://example.com',
{ url: 'http://sponsors.example.com/me' },
'http://collective.example.com',
],
}),
},
bar: {
'package.json': JSON.stringify({
name: 'bar',
version,
funding: ['http://collective.example.com', { url: 'http://sponsors.example.com/you' }],
}),
},
},
}
const conflictingFundingPackages = {
'package.json': JSON.stringify({
name: 'conflicting-funding-packages',
version,
dependencies: {
foo: '1.0.0',
},
devDependencies: {
bar: '1.0.0',
},
}),
node_modules: {
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
funding: 'http://example.com/1',
}),
},
bar: {
node_modules: {
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '2.0.0',
funding: 'http://example.com/2',
}),
},
},
'package.json': JSON.stringify({
name: 'bar',
version: '1.0.0',
dependencies: {
foo: '2.0.0',
},
}),
},
},
}
const setup = async (t, { openUrl, ...opts } = {}) => {
const openedUrls = []
const res = await mockNpm(t, {
...opts,
mocks: {
'@npmcli/promise-spawn': { open: openUrl || (async url => openedUrls.push(url)) },
pacote: {
manifest: arg =>
arg.name === 'ntl'
? Promise.resolve({ funding: 'http://example.com/pacote' })
: Promise.reject(new Error('ERROR')),
},
...opts.mocks,
},
})
return {
...res,
openedUrls: () => openedUrls,
fund: (...args) => res.npm.exec('fund', args),
}
}
t.test('fund with no package containing funding', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'no-funding-package',
version: '0.0.0',
}),
},
config: {},
})
await fund()
t.matchSnapshot(joinedOutput(), 'should print empty funding info')
})
t.test('fund in which same maintainer owns all its deps', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: maintainerOwnsAllDeps,
config: {},
})
await fund()
t.matchSnapshot(joinedOutput(), 'should print stack packages together')
})
t.test('fund in which same maintainer owns all its deps, using --json option', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: maintainerOwnsAllDeps,
config: { json: true },
})
await fund()
t.same(
JSON.parse(joinedOutput()),
{
length: 3,
name: 'maintainer-owns-all-deps',
version: '1.0.0',
funding: { type: 'individual', url: 'http://example.com/donate' },
dependencies: {
'dep-bar': {
version: '1.0.0',
funding: { type: 'individual', url: 'http://example.com/donate' },
},
'dep-foo': {
version: '1.0.0',
funding: { type: 'individual', url: 'http://example.com/donate' },
dependencies: {
'dep-sub-foo': {
version: '1.0.0',
funding: { type: 'individual', url: 'http://example.com/donate' },
},
},
},
},
},
'should print stack packages together'
)
})
t.test('fund containing multi-level nested deps with no funding', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: nestedNoFundingPackages,
config: {},
})
await fund()
t.matchSnapshot(joinedOutput(), 'should omit dependencies with no funding declared')
})
t.test('fund containing multi-level nested deps with no funding, using --json option', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: nestedNoFundingPackages,
config: { json: true },
})
await fund()
t.same(
JSON.parse(joinedOutput()),
{
length: 2,
name: 'nested-no-funding-packages',
version: '1.0.0',
dependencies: {
lorem: {
version: '1.0.0',
funding: { url: 'https://example.com/lorem' },
},
bar: {
version: '1.0.0',
funding: { type: 'individual', url: 'http://example.com/donate' },
},
},
},
'should omit dependencies with no funding declared in json output'
)
})
t.test('fund containing multi-level nested deps with no funding, using --json option', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: nestedMultipleFundingPackages,
config: { json: true },
})
await fund()
t.same(
JSON.parse(joinedOutput()),
{
length: 2,
name: 'nested-multiple-funding-packages',
version: '1.0.0',
funding: [
{
url: 'https://one.example.com',
},
{
url: 'https://two.example.com',
},
],
dependencies: {
bar: {
version: '1.0.0',
funding: [
{
url: 'http://collective.example.com',
},
{
url: 'http://sponsors.example.com/you',
},
],
},
foo: {
version: '1.0.0',
funding: [
{
url: 'http://example.com',
},
{
url: 'http://sponsors.example.com/me',
},
{
url: 'http://collective.example.com',
},
],
},
},
},
'should list multiple funding entries in json output'
)
})
t.test('fund does not support global', async t => {
const { fund } = await setup(t, {
config: { global: true },
})
await t.rejects(fund(), { code: 'EFUNDGLOBAL' }, 'should throw EFUNDGLOBAL error')
})
t.test('fund using package argument', async t => {
const { fund, openedUrls, joinedOutput } = await setup(t, {
prefixDir: maintainerOwnsAllDeps,
config: {},
})
await fund('.')
t.equal(joinedOutput(), '')
t.strictSame(openedUrls(), ['http://example.com/donate'], 'should open funding url')
})
t.test('fund does not support global, using --json option', async t => {
const { fund } = await setup(t, {
prefixDir: {},
config: { global: true, json: true },
})
await t.rejects(
fund(),
{ code: 'EFUNDGLOBAL', message: '`npm fund` does not support global packages' },
'should use expected error msg'
)
})
t.test('fund using string shorthand', async t => {
const { fund, openedUrls } = await setup(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'funding-string-shorthand',
version: '0.0.0',
funding: 'https://example.com/sponsor',
}),
},
config: {},
})
await fund('.')
t.strictSame(openedUrls(), ['https://example.com/sponsor'], 'should open string-only url')
})
t.test('fund using nested packages with multiple sources', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: nestedMultipleFundingPackages,
config: {},
})
await fund('.')
t.matchSnapshot(joinedOutput(), 'should prompt with all available URLs')
})
t.test('fund using symlink ref', async t => {
const f = 'http://example.com/a'
const { fund, openedUrls } = await setup(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'using-symlink-ref',
version: '1.0.0',
}),
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
funding: f,
}),
},
node_modules: {
a: t.fixture('symlink', '../a'),
},
},
config: {},
})
// using symlinked ref
await fund('./node_modules/a')
t.strictSame(openedUrls(), [f], 'should retrieve funding url from symlink')
// using target ref
await fund('./a')
t.strictSame(openedUrls(), [f, f], 'should retrieve funding url from symlink target')
})
t.test('fund using data from actual tree', async t => {
const { fund, openedUrls } = await setup(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'using-actual-tree',
version: '1.0.0',
}),
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
funding: 'http://example.com/a',
}),
},
b: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
funding: 'http://example.com/b',
}),
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.1',
funding: 'http://example.com/_AAA',
}),
},
},
},
},
},
config: {},
})
// using symlinked ref
await fund('a')
t.strictSame(
openedUrls(),
['http://example.com/_AAA'],
'should retrieve fund info from actual tree, using greatest version found'
)
})
t.test('fund using nested packages with multiple sources, with a source number', async t => {
const { fund, openedUrls } = await setup(t, {
prefixDir: nestedMultipleFundingPackages,
config: { which: '1' },
})
await fund('.')
t.strictSame(openedUrls(), ['https://one.example.com'], 'should open the numbered URL')
})
t.test('fund using pkg name while having conflicting versions', async t => {
const { fund, openedUrls } = await setup(t, {
prefixDir: conflictingFundingPackages,
config: { which: '1' },
})
await fund('foo')
t.strictSame(openedUrls(), ['http://example.com/2'], 'should open greatest version')
})
t.test('fund using bad which value: index too high', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: nestedMultipleFundingPackages,
config: { which: '100' },
})
await fund('foo')
t.match(joinedOutput(), 'not a valid index')
t.matchSnapshot(joinedOutput(), 'should print message about invalid which')
})
t.test('fund using package argument with no browser, using --json option', async t => {
const { fund, openedUrls, joinedOutput } = await setup(t, {
prefixDir: maintainerOwnsAllDeps,
config: { json: true },
})
await fund('.')
t.equal(joinedOutput(), '', 'no output')
t.same(
openedUrls(),
['http://example.com/donate'],
'should open funding url using json output'
)
})
t.test('fund using package info fetch from registry', async t => {
const { fund, openedUrls } = await setup(t, {
prefixDir: {},
config: {},
})
await fund('ntl')
t.match(
openedUrls(),
/http:\/\/example.com\/pacote/,
'should open funding url that was loaded from registry manifest'
)
})
t.test('fund tries to use package info fetch from registry but registry has nothing', async t => {
const { fund } = await setup(t, {
prefixDir: {},
config: {},
})
await t.rejects(
fund('foo'),
{ code: 'ENOFUND', message: 'No valid funding method available for: foo' },
'should have no valid funding message'
)
})
t.test('fund but target module has no funding info', async t => {
const { fund } = await setup(t, {
prefixDir: nestedNoFundingPackages,
config: {},
})
await t.rejects(
fund('foo'),
{ code: 'ENOFUND', message: 'No valid funding method available for: foo' },
'should have no valid funding message'
)
})
t.test('fund using bad which value', async t => {
const { fund } = await setup(t, {
prefixDir: nestedMultipleFundingPackages,
config: { which: '0' },
})
await t.rejects(
fund('bar'),
{
code: 'EFUNDNUMBER',
message: /must be given a positive integer/,
},
'should have bad which option error message'
)
})
t.test('fund pkg missing version number', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
funding: 'http://example.com/foo',
}),
},
config: {},
})
await fund()
t.matchSnapshot(joinedOutput(), 'should print name only')
})
t.test('fund a package throws on openUrl', async t => {
const { fund } = await setup(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
funding: 'http://npmjs.org',
}),
},
config: {},
openUrl: () => {
throw new Error('ERROR')
},
})
await t.rejects(fund('.'), { message: 'ERROR' }, 'should throw unknown error')
})
t.test('fund a package with type and multiple sources', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
funding: [
{
type: 'Foo',
url: 'http://example.com/foo',
},
{
type: 'Lorem',
url: 'http://example.com/foo-lorem',
},
],
}),
},
config: {},
})
await fund('.')
t.matchSnapshot(joinedOutput(), 'should print prompt select message')
})
t.test('fund colors', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'test-fund-colors',
version: '1.0.0',
dependencies: {
a: '^1.0.0',
b: '^1.0.0',
c: '^1.0.0',
},
}),
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
funding: 'http://example.com/a',
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
funding: 'http://example.com/b',
dependencies: {
d: '^1.0.0',
e: '^1.0.0',
},
}),
},
c: {
'package.json': JSON.stringify({
name: 'c',
version: '1.0.0',
funding: 'http://example.com/b',
}),
},
d: {
'package.json': JSON.stringify({
name: 'd',
version: '1.0.0',
funding: 'http://example.com/d',
}),
},
e: {
'package.json': JSON.stringify({
name: 'e',
version: '1.0.0',
funding: 'http://example.com/e',
}),
},
},
},
config: { color: 'always' },
})
await fund()
t.matchSnapshot(joinedOutput(), 'should print output with color info')
})
t.test('sub dep with fund info and a parent with no funding info', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'test-multiple-funding-sources',
version: '1.0.0',
dependencies: {
a: '^1.0.0',
b: '^1.0.0',
},
}),
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
dependencies: {
c: '^1.0.0',
},
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
funding: 'http://example.com/b',
}),
},
c: {
'package.json': JSON.stringify({
name: 'c',
version: '1.0.0',
funding: ['http://example.com/c', 'http://example.com/c-other'],
}),
},
},
},
config: {},
})
await fund()
t.matchSnapshot(joinedOutput(), 'should nest sub dep as child of root')
})
t.test('workspaces', async t => {
const wsPrefixDir = {
'package.json': JSON.stringify({
name: 'workspaces-support',
version: '1.0.0',
workspaces: ['packages/*'],
dependencies: {
d: '^1.0.0',
},
}),
node_modules: {
a: t.fixture('symlink', '../packages/a'),
b: t.fixture('symlink', '../packages/b'),
c: {
'package.json': JSON.stringify({
name: 'c',
version: '1.0.0',
funding: ['http://example.com/c', 'http://example.com/c-other'],
}),
},
d: {
'package.json': JSON.stringify({
name: 'd',
version: '1.0.0',
funding: 'http://example.com/d',
}),
},
},
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
funding: 'https://example.com/a',
dependencies: {
c: '^1.0.0',
},
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
funding: 'http://example.com/b',
dependencies: {
d: '^1.0.0',
},
}),
},
},
}
t.test('filter funding info by a specific workspace name', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: wsPrefixDir,
config: {
workspace: 'a',
},
})
await fund()
t.matchSnapshot(joinedOutput(), 'should display only filtered workspace name and its deps')
})
t.test('filter funding info by a specific workspace path', async t => {
const { fund, joinedOutput } = await setup(t, {
prefixDir: wsPrefixDir,
config: {
workspace: './packages/a',
},
})
await fund()
t.matchSnapshot(joinedOutput(), 'should display only filtered workspace name and its deps')
})
})

16
deps/npm/test/lib/commands/get.js vendored Normal file
View File

@ -0,0 +1,16 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
t.test('should retrieve values from config', async t => {
const name = 'editor'
const value = 'vigor'
const { joinedOutput, npm } = await loadMockNpm(t, {
config: { [name]: value },
})
await npm.exec('get', [name])
t.equal(
joinedOutput(),
value,
'outputs config item'
)
})

View File

@ -0,0 +1,88 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const docsFixtures = {
dir1: {
'npm-exec.md': 'the exec command\nhelp has multiple lines of exec help\none of them references exec',
},
dir2: {
'npm-something.md': 'another\ncommand you run\nthat\nreferences exec\nand has multiple lines\nwith no matches\nthat will be ignored\nand another line\nthat does have exec as well',
'npm-run-script.md': 'the scripted run-script command runs scripts\nand has lines\nsome of which dont match the string run\nor script\nscript',
'npm-install.md': 'does a thing in a script\nif a thing does not exist in a thing you run\nto install it and run it maybe in a script',
},
dir3: {
'npm-help.md': 'will run the `help-search` command if you need to run it to help you search',
'npm-help-search.md': 'is the help search command\nthat you get if you run help-search',
'npm-useless.md': 'exec\nexec',
'npm-more-useless.md': 'exec exec',
'npm-extra-useless.md': 'exec\nexec\nexec',
},
}
const execHelpSearch = async (t, exec = [], opts) => {
const { npm, ...rest } = await loadMockNpm(t, {
npm: ({ other }) => ({ npmRoot: other }),
// docs/content is hardcoded into the glob path in the command
otherDirs: {
docs: {
content: docsFixtures,
},
},
...opts,
})
await npm.exec('help-search', exec)
return { npm, output: rest.joinedOutput(), ...rest }
}
t.test('npm help-search', async t => {
const { output } = await execHelpSearch(t, ['exec'])
t.match(output, /Top hits for "exec"/, 'outputs results')
})
t.test('npm help-search multiple terms', async t => {
const { output } = await execHelpSearch(t, ['run', 'script'])
t.match(output, /Top hits for/, 'outputs results')
t.match(output, /run:\d+ script:\d+/, 'shows hit counts for both terms')
})
t.test('npm help-search long output', async t => {
const { output } = await execHelpSearch(t, ['exec'], {
config: {
long: true,
},
})
t.match(output, /has multiple lines of exec help/, 'outputs detailed results')
})
t.test('npm help-search long output with color', async t => {
const { output } = await execHelpSearch(t, ['help-search'], {
config: {
long: true,
color: 'always',
},
})
const chalk = await import('chalk').then(v => v.default)
const highlightedText = chalk.blue('help-search')
t.equal(
output.split('\n').some(line => line.includes(highlightedText)),
true,
'returned highlighted search terms'
)
})
t.test('npm help-search no args', async t => {
await t.rejects(execHelpSearch(t), /npm help-search/, 'outputs usage')
})
t.test('npm help-search no matches', async t => {
const { output } = await execHelpSearch(t, ['asdfasdf'])
t.match(output, /No matches/)
})

227
deps/npm/test/lib/commands/help.js vendored Normal file
View File

@ -0,0 +1,227 @@
const t = require('tap')
const localeCompare = require('@isaacs/string-locale-compare')('en')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const { cleanCwd } = require('../../fixtures/clean-snapshot')
const genManPages = (obj) => {
const man = {}
const resPages = new Set()
for (const [section, pages] of Object.entries(obj)) {
const num = parseInt(section, 10)
man[`man${num}`] = {}
const sectionPages = []
for (const name of pages) {
man[`man${num}`][`${name}.${section}`] = `.TH "${name.toUpperCase()}" "${num}"`
sectionPages.push(name.replace(/^npm-/, ''))
}
// return a sorted list of uniq pages in order to test completion
for (const p of sectionPages.sort(localeCompare)) {
resPages.add(p)
}
}
// man directory name is hardcoded in the command
return { fixtures: { man }, pages: [...resPages.values()] }
}
const mockHelp = async (t, {
man = {
5: ['npmrc', 'install', 'package-json'],
1: ['whoami', 'install', 'star', 'unstar', 'uninstall', 'unpublish'].map(p => `npm-${p}`),
7: ['disputes', 'config'],
},
browser = false,
woman = false,
exec: execArgs = null,
spawnErr,
...opts
} = {}) => {
const config = {
// always set viewer to test the same on all platforms
viewer: browser ? 'browser' : woman ? 'woman' : 'man',
...opts.config,
}
let args = null
const mockSpawn = async (...a) => {
args = a
if (spawnErr) {
throw spawnErr
}
}
mockSpawn.open = async (url) => args = [cleanCwd(decodeURI(url))]
const manPages = genManPages(man)
const { npm, ...rest } = await loadMockNpm(t, {
npm: ({ other }) => ({ npmRoot: other }),
mocks: { '@npmcli/promise-spawn': mockSpawn },
otherDirs: { ...manPages.fixtures },
config,
command: 'help',
exec: execArgs,
...opts,
})
return {
npm,
manPages: manPages.pages,
getArgs: () => args,
...rest,
}
}
t.test('npm help', async t => {
const { help, joinedOutput } = await mockHelp(t)
await help.exec()
t.match(joinedOutput(), 'npm <command>', 'showed npm usage')
})
t.test('npm help completion', async t => {
const { help, manPages } = await mockHelp(t)
const noArgs = await help.completion({ conf: { argv: { remain: [] } } })
t.strictSame(noArgs, ['help', ...manPages], 'outputs available help pages')
const threeArgs = await help.completion({ conf: { argv: { remain: ['one', 'two', 'three'] } } })
t.strictSame(threeArgs, [], 'outputs no results when more than 2 args are provided')
})
t.test('npm help multiple args calls search', async t => {
const { joinedOutput } = await mockHelp(t, { exec: ['run', 'script'] })
t.match(joinedOutput(), 'No matches in help for: run script', 'calls help-search')
})
t.test('npm help no matches calls search', async t => {
const { joinedOutput } = await mockHelp(t, { exec: ['asdfasdf'] })
t.match(joinedOutput(), 'No matches in help for: asdfasdf', 'passed the args to help-search')
})
t.test('npm help whoami', async t => {
const { getArgs } = await mockHelp(t, { exec: ['whoami'] })
const [spawnBin, spawnArgs] = getArgs()
t.equal(spawnBin, 'man', 'calls man by default')
t.equal(spawnArgs.length, 1)
t.match(spawnArgs[0], /npm-whoami\.1$/)
})
t.test('npm help 1 install', async t => {
const { getArgs } = await mockHelp(t, {
exec: ['1', 'install'],
browser: true,
})
const [url] = getArgs()
t.match(url, /commands\/npm-install.html$/, 'attempts to open the correct url')
t.ok(url.startsWith('file:///'), 'opens with the correct uri schema')
})
t.test('npm help 5 install', async t => {
const { getArgs } = await mockHelp(t, {
exec: ['5', 'install'],
browser: true,
})
const [url] = getArgs()
t.match(url, /configuring-npm\/install.html$/, 'attempts to open the correct url')
})
t.test('npm help 7 config', async t => {
const { getArgs } = await mockHelp(t, {
exec: ['7', 'config'],
browser: true,
})
const [url] = getArgs()
t.match(url, /using-npm\/config.html$/, 'attempts to open the correct url')
})
t.test('npm help package.json redirects to package-json', async t => {
const { getArgs } = await mockHelp(t, {
exec: ['package.json'],
})
const [spawnBin, spawnArgs] = getArgs()
t.equal(spawnBin, 'man', 'calls man by default')
t.equal(spawnArgs.length, 1)
t.match(spawnArgs[0], /package-json\.5$/)
})
t.test('npm help ?(un)star', async t => {
const { getArgs } = await mockHelp(t, {
exec: ['?(un)star'],
woman: true,
})
const [spawnBin, spawnArgs] = getArgs()
t.equal(spawnBin, 'emacsclient', 'maps woman to emacs correctly')
t.equal(spawnArgs.length, 2)
t.match(spawnArgs[1], /^\(woman-find-file '/)
t.match(spawnArgs[1], /npm-star.1'\)$/)
})
t.test('npm help un*', async t => {
const { getArgs } = await mockHelp(t, {
exec: ['un*'],
})
const [spawnBin, spawnArgs] = getArgs()
t.equal(spawnBin, 'man', 'calls man by default')
t.equal(spawnArgs.length, 1)
t.match(spawnArgs[0], /npm-uninstall\.1$/)
})
t.test('npm help - prefers lowest numbered npm prefixed help pages', async t => {
const { getArgs } = await mockHelp(t, {
man: {
6: ['npm-install'],
1: ['npm-install'],
5: ['install'],
7: ['npm-install'],
},
exec: ['install'],
})
const [spawnBin, spawnArgs] = getArgs()
t.equal(spawnBin, 'man', 'calls man by default')
t.equal(spawnArgs.length, 1)
t.match(spawnArgs[0], /npm-install\.1$/)
})
t.test('npm help - works in the presence of strange man pages', async t => {
const { getArgs } = await mockHelp(t, {
man: {
'1strange': ['config'],
5: ['config'],
'6ssl': ['config'],
},
exec: ['config'],
})
const [spawnBin, spawnArgs] = getArgs()
t.equal(spawnBin, 'man', 'calls man by default')
t.equal(spawnArgs.length, 1)
t.match(spawnArgs[0], /config\.5$/)
})
t.test('rejects with code', async t => {
const { help } = await mockHelp(t, {
spawnErr: Object.assign(new Error('errrrr'), { code: 'SPAWN_ERR' }),
})
await t.rejects(help.exec(['whoami']), /help process exited with code: SPAWN_ERR/)
})
t.test('rejects with no code', async t => {
const { help } = await mockHelp(t, {
spawnErr: new Error('errrrr'),
})
await t.rejects(help.exec(['whoami']), /errrrr/)
})

461
deps/npm/test/lib/commands/init.js vendored Normal file
View File

@ -0,0 +1,461 @@
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')
})
})

View File

@ -0,0 +1,65 @@
const t = require('tap')
const InstallCITest = require('../../../lib/commands/install-ci-test.js')
let ciArgs = null
let ciCalled = false
let testArgs = null
let testCalled = false
let ciError = null
const installCITest = new InstallCITest({
exec: (cmd, args) => {
if (cmd === 'ci') {
ciArgs = args
ciCalled = true
}
if (ciError) {
throw ciError
}
if (cmd === 'test') {
testArgs = args
testCalled = true
}
},
config: {
validate: () => {},
get: (key) => {
if (key === 'location') {
return 'project'
}
},
isDefault: () => {},
},
})
t.test('the install-ci-test command', t => {
t.afterEach(() => {
ciArgs = null
ciCalled = false
testArgs = null
testCalled = false
ciError = null
})
t.test('ci and test', async t => {
await installCITest.exec(['extra'])
t.equal(ciCalled, true)
t.equal(testCalled, true)
t.match(ciArgs, ['extra'])
t.match(testArgs, [])
})
t.test('ci fails', async t => {
ciError = new Error('test fail')
await t.rejects(
installCITest.exec(['extra']),
'test fail'
)
t.equal(ciCalled, true)
t.equal(testCalled, false)
t.match(ciArgs, ['extra'])
})
t.end()
})

View File

@ -0,0 +1,65 @@
const t = require('tap')
const InstallTest = require('../../../lib/commands/install-test.js')
let installArgs = null
let installCalled = false
let testArgs = null
let testCalled = false
let installError = null
const installTest = new InstallTest({
exec: (cmd, args) => {
if (cmd === 'install') {
installArgs = args
installCalled = true
}
if (installError) {
throw installError
}
if (cmd === 'test') {
testArgs = args
testCalled = true
}
},
config: {
validate: () => {},
get: (key) => {
if (key === 'location') {
return 'project'
}
},
isDefault: () => {},
},
})
t.test('the install-test command', t => {
t.afterEach(() => {
installArgs = null
installCalled = false
testArgs = null
testCalled = false
installError = null
})
t.test('install and test', async t => {
await installTest.exec(['extra'])
t.equal(installCalled, true)
t.equal(testCalled, true)
t.match(installArgs, ['extra'])
t.match(testArgs, [])
})
t.test('install fails', async t => {
installError = new Error('test fail')
await t.rejects(
installTest.exec(['extra']),
'test fail'
)
t.equal(installCalled, true)
t.equal(testCalled, false)
t.match(installArgs, ['extra'])
})
t.end()
})

719
deps/npm/test/lib/commands/install.js vendored Normal file
View File

@ -0,0 +1,719 @@
const tspawk = require('../../fixtures/tspawk')
const {
cleanCwd,
cleanTime,
cleanDate,
cleanPackumentCache,
} = require('../../fixtures/clean-snapshot.js')
const path = require('node:path')
const t = require('tap')
t.cleanSnapshot = (str) => cleanPackumentCache(cleanDate(cleanTime(cleanCwd(str))))
const {
loadNpmWithRegistry: loadMockNpm,
workspaceMock,
} = require('../../fixtures/mock-npm')
// tspawk calls preventUnmatched which assures that no scripts run if we don't mock any
const spawk = tspawk(t)
const abbrev = {
'package.json': '{"name": "abbrev", "version": "1.0.0"}',
test: 'test file',
}
const packageJson = {
name: '@npmcli/test-package',
version: '1.0.0',
dependencies: {
abbrev: '^1.0.0',
},
}
t.test('exec commands', async t => {
await t.test('with args does not run lifecycle scripts', async t => {
const { npm, registry } = await loadMockNpm(t, {
config: {
audit: false,
},
prefixDir: {
'package.json': JSON.stringify({
...packageJson,
scripts: {
install: 'echo install',
},
}),
abbrev,
},
})
const manifest = registry.manifest({ name: 'abbrev' })
await registry.package({ manifest })
await registry.tarball({
manifest: manifest.versions['1.0.0'],
tarball: path.join(npm.prefix, 'abbrev'),
})
await npm.exec('install', ['abbrev'])
})
await t.test('without args runs lifecycle scripts', async t => {
const lifecycleScripts = [
'preinstall',
'install',
'postinstall',
'prepublish',
'preprepare',
'prepare',
'postprepare',
]
const scripts = {}
for (const script of lifecycleScripts) {
spawk.spawn(/.*/, a => {
runOrder.push(script)
return a.includes(`${script} lifecycle script`)
})
scripts[script] = `${script} lifecycle script`
}
const { npm, registry } = await loadMockNpm(t, {
config: {
audit: false,
},
prefixDir: {
'package.json': JSON.stringify({
...packageJson,
scripts,
}),
abbrev,
},
})
const runOrder = []
const manifest = registry.manifest({ name: 'abbrev' })
await registry.package({ manifest })
await registry.tarball({
manifest: manifest.versions['1.0.0'],
tarball: path.join(npm.prefix, 'abbrev'),
})
await npm.exec('install')
t.strictSame(lifecycleScripts, runOrder, 'all script ran in the correct order')
})
await t.test('should ignore scripts with --ignore-scripts', async t => {
const { npm, registry } = await loadMockNpm(t, {
config: {
'ignore-scripts': true,
audit: false,
},
prefixDir: {
'package.json': JSON.stringify({
...packageJson,
scripts: {
install: 'echo install',
},
}),
abbrev,
},
})
const manifest = registry.manifest({ name: 'abbrev' })
await registry.package({ manifest })
await registry.tarball({
manifest: manifest.versions['1.0.0'],
tarball: path.join(npm.prefix, 'abbrev'),
})
await npm.exec('install')
})
await t.test('should not install invalid global package name', async t => {
const { npm } = await loadMockNpm(t, {
config: {
global: true,
},
})
await t.rejects(
npm.exec('install', ['']),
/Usage:/,
'should not install invalid package name'
)
})
await t.test('npm i -g npm engines check success', async t => {
const { npm, registry } = await loadMockNpm(t, {
prefixDir: {
npm: {
'package.json': JSON.stringify({ name: 'npm', version: '1.0.0' }),
'index.js': 'console.log("this is npm")',
},
},
config: { global: true },
})
const manifest = registry.manifest({
name: 'npm',
packuments: [{ version: '1.0.0', engines: { node: '>1' } }],
})
await registry.package({ manifest, times: 2 })
await registry.tarball({
manifest: manifest.versions['1.0.0'],
tarball: path.join(npm.localPrefix, 'npm'),
})
await npm.exec('install', ['npm'])
t.ok('No exceptions happen')
})
await t.test('npm i -g npm engines check failure', async t => {
const { npm, registry } = await loadMockNpm(t, {
prefixDir: {
npm: {
'package.json': JSON.stringify({ name: 'npm', version: '1.0.0' }),
'index.js': 'console.log("this is the npm we are installing")',
},
},
config: { global: true },
})
const manifest = registry.manifest({
name: 'npm',
packuments: [{ version: '1.0.0', engines: { node: '~1' } }],
})
await registry.package({ manifest })
await t.rejects(
npm.exec('install', ['npm']),
{
message: 'Unsupported engine',
pkgid: 'npm@1.0.0',
current: {
node: process.version,
npm: '1.0.0',
},
required: {
node: '~1',
},
code: 'EBADENGINE',
}
)
})
await t.test('npm i -g npm engines check failure forced override', async t => {
const { npm, registry } = await loadMockNpm(t, {
prefixDir: {
npm: {
'package.json': JSON.stringify({ name: 'npm', version: '1.0.0' }),
'index.js': 'console.log("this is npm")',
},
},
config: { global: true, force: true },
})
const manifest = registry.manifest({
name: 'npm',
packuments: [{ version: '1.0.0', engines: { node: '~1' } }],
})
await registry.package({ manifest, times: 2 })
await registry.tarball({
manifest: manifest.versions['1.0.0'],
tarball: path.join(npm.localPrefix, 'npm'),
})
await npm.exec('install', ['npm'])
t.ok('No exceptions happen')
})
})
t.test('completion', async t => {
const mockComp = async (t, { noChdir } = {}) => loadMockNpm(t, {
command: 'install',
prefixDir: {
arborist: {
'package.json': '{}',
},
'arborist.txt': 'just a file',
'other-dir': { a: 'a' },
},
...(noChdir ? { chdir: false } : {}),
})
await t.test('completion to folder - has a match', async t => {
const { install } = await mockComp(t)
const res = await install.completion({ partialWord: './ar' })
t.strictSame(res, ['arborist'], 'package dir match')
})
await t.test('completion to folder - invalid dir', async t => {
const { install } = await mockComp(t, { noChdir: true })
const res = await install.completion({ partialWord: '/does/not/exist' })
t.strictSame(res, [], 'invalid dir: no matching')
})
await t.test('completion to folder - no matches', async t => {
const { install } = await mockComp(t)
const res = await install.completion({ partialWord: './pa' })
t.strictSame(res, [], 'no name match')
})
await t.test('completion to folder - match is not a package', async t => {
const { install } = await mockComp(t)
const res = await install.completion({ partialWord: './othe' })
t.strictSame(res, [], 'no name match')
})
await t.test('completion to url', async t => {
const { install } = await mockComp(t)
const res = await install.completion({ partialWord: 'http://path/to/url' })
t.strictSame(res, [])
})
await t.test('no /', async t => {
const { install } = await mockComp(t)
const res = await install.completion({ partialWord: 'toto' })
t.notOk(res)
})
await t.test('only /', async t => {
const { install } = await mockComp(t)
const res = await install.completion({ partialWord: '/' })
t.strictSame(res, [])
})
})
t.test('should install in workspace with unhoisted module', async t => {
const { npm, registry, assert } = await loadMockNpm(t, {
prefixDir: workspaceMock(t, {
clean: true,
workspaces: {
'workspace-a': {
'abbrev@1.1.0': { hoist: true },
},
'workspace-b': {
'abbrev@1.1.1': { hoist: false },
},
},
}),
})
await registry.setup({
'abbrev@1.1.0': path.join(npm.prefix, 'tarballs/abbrev@1.1.0'),
'abbrev@1.1.1': path.join(npm.prefix, 'tarballs/abbrev@1.1.1'),
})
registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
assert.packageMissing('node_modules/abbrev@1.1.0')
assert.packageMissing('workspace-b/node_modules/abbrev@1.1.1')
await npm.exec('install', [])
assert.packageInstalled('node_modules/abbrev@1.1.0')
assert.packageInstalled('workspace-b/node_modules/abbrev@1.1.1')
})
t.test('should install in workspace with hoisted modules', async t => {
const prefixDir = workspaceMock(t, {
clean: true,
workspaces: {
'workspace-a': {
'abbrev@1.1.0': { hoist: true },
},
'workspace-b': {
'lodash@1.1.1': { hoist: true },
},
},
})
const { npm, registry, assert } = await loadMockNpm(t, { prefixDir })
await registry.setup({
'abbrev@1.1.0': path.join(npm.prefix, 'tarballs/abbrev@1.1.0'),
'lodash@1.1.1': path.join(npm.prefix, 'tarballs/lodash@1.1.1'),
})
registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
assert.packageMissing('node_modules/abbrev@1.1.0')
assert.packageMissing('node_modules/lodash@1.1.1')
await npm.exec('install', [])
assert.packageInstalled('node_modules/abbrev@1.1.0')
assert.packageInstalled('node_modules/lodash@1.1.1')
})
t.test('should install unhoisted module with --workspace flag', async t => {
const { npm, registry, assert } = await loadMockNpm(t, {
config: {
workspace: 'workspace-b',
},
prefixDir: workspaceMock(t, {
clean: true,
workspaces: {
'workspace-a': {
'abbrev@1.1.0': { hoist: true },
},
'workspace-b': {
'abbrev@1.1.1': { hoist: false },
},
},
}),
})
await registry.setup({
'abbrev@1.1.1': path.join(npm.prefix, 'tarballs/abbrev@1.1.1'),
})
registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
assert.packageMissing('node_modules/abbrev@1.1.0')
assert.packageMissing('workspace-b/node_modules/abbrev@1.1.1')
await npm.exec('install', [])
assert.packageMissing('node_modules/abbrev@1.1.0')
assert.packageInstalled('workspace-b/node_modules/abbrev@1.1.1')
})
t.test('should install hoisted module with --workspace flag', async t => {
const { npm, registry, assert } = await loadMockNpm(t, {
config: {
workspace: 'workspace-b',
},
prefixDir: workspaceMock(t, {
clean: true,
workspaces: {
'workspace-a': {
'abbrev@1.1.0': { hoist: true },
},
'workspace-b': {
'lodash@1.1.1': { hoist: true },
},
},
}),
})
await registry.setup({
'lodash@1.1.1': path.join(npm.prefix, 'tarballs/lodash@1.1.1'),
})
registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
assert.packageMissing('node_modules/abbrev@1.1.0')
assert.packageMissing('node_modules/lodash@1.1.1')
await npm.exec('install', [])
assert.packageMissing('node_modules/abbrev@1.1.0')
assert.packageInstalled('node_modules/lodash@1.1.1')
})
t.test('should show install keeps dirty --workspace flag', async t => {
const { npm, registry, assert } = await loadMockNpm(t, {
config: {
workspace: 'workspace-b',
},
prefixDir: workspaceMock(t, {
workspaces: {
'workspace-a': {
'abbrev@1.1.0': { clean: false, hoist: true },
},
'workspace-b': {
'lodash@1.1.1': { clean: true, hoist: true },
},
},
}),
})
await registry.setup({
'lodash@1.1.1': path.join(npm.prefix, 'tarballs/lodash@1.1.1'),
})
registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
assert.packageDirty('node_modules/abbrev@1.1.0')
assert.packageMissing('node_modules/lodash@1.1.1')
await npm.exec('install', [])
assert.packageDirty('node_modules/abbrev@1.1.0')
assert.packageInstalled('node_modules/lodash@1.1.1')
})
t.test('devEngines', async t => {
const mockArguments = {
globals: {
'process.platform': 'linux',
'process.arch': 'x86',
'process.version': 'v1337.0.0',
},
mocks: {
'{ROOT}/package.json': { version: '42.0.0' },
},
}
t.test('should utilize devEngines success case', async t => {
const { npm, joinedFullOutput } = await loadMockNpm(t, {
...mockArguments,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-package',
version: '1.0.0',
devEngines: {
runtime: {
name: 'node',
},
},
}),
},
})
await npm.exec('install', [])
const output = joinedFullOutput()
t.matchSnapshot(output)
t.ok(!output.includes('EBADDEVENGINES'))
})
t.test('should utilize devEngines failure case', async t => {
const { npm, joinedFullOutput } = await loadMockNpm(t, {
...mockArguments,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-package',
version: '1.0.0',
devEngines: {
runtime: {
name: 'nondescript',
},
},
}),
},
})
await t.rejects(
npm.exec('install', [])
)
const output = joinedFullOutput()
t.matchSnapshot(output)
t.ok(output.includes('error EBADDEVENGINES'))
})
t.test('should utilize devEngines failure force case', async t => {
const { npm, joinedFullOutput } = await loadMockNpm(t, {
...mockArguments,
config: {
force: true,
},
prefixDir: {
'package.json': JSON.stringify({
name: 'test-package',
version: '1.0.0',
devEngines: {
runtime: {
name: 'nondescript',
},
},
}),
},
})
await npm.exec('install', [])
const output = joinedFullOutput()
t.matchSnapshot(output)
t.ok(output.includes('warn EBADDEVENGINES'))
})
t.test('should utilize devEngines 2x warning case', async t => {
const { npm, joinedFullOutput } = await loadMockNpm(t, {
...mockArguments,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-package',
version: '1.0.0',
devEngines: {
runtime: {
name: 'nondescript',
onFail: 'warn',
},
cpu: {
name: 'risv',
onFail: 'warn',
},
},
}),
},
})
await npm.exec('install', [])
const output = joinedFullOutput()
t.matchSnapshot(output)
t.ok(output.includes('warn EBADDEVENGINES'))
})
t.test('should utilize devEngines 2x error case', async t => {
const { npm, joinedFullOutput } = await loadMockNpm(t, {
...mockArguments,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-package',
version: '1.0.0',
devEngines: {
runtime: {
name: 'nondescript',
onFail: 'error',
},
cpu: {
name: 'risv',
onFail: 'error',
},
},
}),
},
})
await t.rejects(
npm.exec('install', [])
)
const output = joinedFullOutput()
t.matchSnapshot(output)
t.ok(output.includes('error EBADDEVENGINES'))
})
t.test('should utilize devEngines failure and warning case', async t => {
const { npm, joinedFullOutput } = await loadMockNpm(t, {
...mockArguments,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-package',
version: '1.0.0',
devEngines: {
runtime: {
name: 'nondescript',
},
cpu: {
name: 'risv',
onFail: 'warn',
},
},
}),
},
})
await t.rejects(
npm.exec('install', [])
)
const output = joinedFullOutput()
t.matchSnapshot(output)
t.ok(output.includes('EBADDEVENGINES'))
})
t.test('should show devEngines has no effect on package install', async t => {
const { npm, joinedFullOutput } = await loadMockNpm(t, {
...mockArguments,
prefixDir: {
alpha: {
'package.json': JSON.stringify({
name: 'alpha',
devEngines: { runtime: { name: 'node', version: '1.0.0' } },
}),
'index.js': 'console.log("this is alpha index")',
},
'package.json': JSON.stringify({
name: 'project',
}),
},
})
await npm.exec('install', ['./alpha'])
const output = joinedFullOutput()
t.matchSnapshot(output)
t.ok(!output.includes('EBADDEVENGINES'))
})
t.test('should show devEngines has no effect on dev package install', async t => {
const { npm, joinedFullOutput } = await loadMockNpm(t, {
...mockArguments,
prefixDir: {
alpha: {
'package.json': JSON.stringify({
name: 'alpha',
devEngines: { runtime: { name: 'node', version: '1.0.0' } },
}),
'index.js': 'console.log("this is alpha index")',
},
'package.json': JSON.stringify({
name: 'project',
}),
},
config: {
'save-dev': true,
},
})
await npm.exec('install', ['./alpha'])
const output = joinedFullOutput()
t.matchSnapshot(output)
t.ok(!output.includes('EBADDEVENGINES'))
})
t.test('should show devEngines doesnt break engines', async t => {
const { npm, joinedFullOutput } = await loadMockNpm(t, {
...mockArguments,
prefixDir: {
alpha: {
'package.json': JSON.stringify({
name: 'alpha',
devEngines: { runtime: { name: 'node', version: '1.0.0' } },
engines: { node: '1.0.0' },
}),
'index.js': 'console.log("this is alpha index")',
},
'package.json': JSON.stringify({
name: 'project',
}),
},
config: { global: true },
})
await npm.exec('install', ['./alpha'])
const output = joinedFullOutput()
t.matchSnapshot(output)
t.ok(output.includes('warn EBADENGINE'))
})
t.test('should not utilize engines in root if devEngines is provided', async t => {
const { npm, joinedFullOutput } = await loadMockNpm(t, {
...mockArguments,
prefixDir: {
'package.json': JSON.stringify({
name: 'alpha',
engines: {
node: '0.0.1',
},
devEngines: {
runtime: {
name: 'node',
version: '0.0.1',
onFail: 'warn',
},
},
}),
'index.js': 'console.log("this is alpha index")',
},
})
await npm.exec('install')
const output = joinedFullOutput()
t.matchSnapshot(output)
t.ok(!output.includes('EBADENGINE'))
t.ok(output.includes('warn EBADDEVENGINES'))
})
t.test('should utilize engines in root if devEngines is not provided', async t => {
const { npm, joinedFullOutput } = await loadMockNpm(t, {
...mockArguments,
prefixDir: {
'package.json': JSON.stringify({
name: 'alpha',
engines: {
node: '0.0.1',
},
}),
'index.js': 'console.log("this is alpha index")',
},
})
await npm.exec('install')
const output = joinedFullOutput()
t.matchSnapshot(output)
t.ok(output.includes('EBADENGINE'))
t.ok(!output.includes('EBADDEVENGINES'))
})
t.test('should show devEngines has no effect on global package install', async t => {
const { npm, joinedFullOutput } = await loadMockNpm(t, {
...mockArguments,
prefixDir: {
'package.json': JSON.stringify({
name: 'alpha',
bin: {
alpha: 'index.js',
},
devEngines: {
runtime: {
name: 'node',
version: '0.0.1',
},
},
}),
'index.js': 'console.log("this is alpha index")',
},
config: {
global: true,
},
})
await npm.exec('install', ['.'])
const output = joinedFullOutput()
t.matchSnapshot(output)
t.ok(!output.includes('EBADENGINE'))
t.ok(!output.includes('EBADDEVENGINES'))
})
})

525
deps/npm/test/lib/commands/link.js vendored Normal file
View File

@ -0,0 +1,525 @@
const t = require('tap')
const { resolve, join } = require('node:path')
const fs = require('node:fs')
const Arborist = require('@npmcli/arborist')
const { cleanCwd } = require('../../fixtures/clean-snapshot.js')
const mockNpm = require('../../fixtures/mock-npm')
t.cleanSnapshot = (str) => cleanCwd(str)
const mockLink = async (t, { globalPrefixDir, ...opts } = {}) => {
const mock = await mockNpm(t, {
...opts,
command: 'link',
globalPrefixDir,
mocks: {
...opts.mocks,
'{LIB}/utils/reify-output.js': async () => {},
},
})
const printLinks = async ({ global = false } = {}) => {
let res = ''
const arb = new Arborist(global ? {
path: resolve(mock.npm.globalDir, '..'),
global: true,
} : { path: mock.prefix })
const tree = await arb.loadActual()
const linkedItems = [...tree.inventory.values()]
.sort((a, b) => a.pkgid.localeCompare(b.pkgid, 'en'))
for (const item of linkedItems) {
if (item.isLink) {
res += `${item.path} -> ${item.target.path}\n`
}
}
return res
}
return {
...mock,
printLinks,
}
}
t.test('link to globalDir when in current working dir of pkg and no args', async t => {
const { link, printLinks } = await mockLink(t, {
globalPrefixDir: {
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
},
},
prefixDir: {
'package.json': JSON.stringify({
name: 'test-pkg-link',
version: '1.0.0',
}),
},
})
await link.exec()
t.matchSnapshot(await printLinks({ global: true }), 'should create a global link to current pkg')
})
t.test('link ws to globalDir when workspace specified and no args', async t => {
const { link, printLinks } = await mockLink(t, {
globalPrefixDir: {
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
},
},
prefixDir: {
'package.json': JSON.stringify({
name: 'test-pkg-link',
version: '1.0.0',
workspaces: ['packages/*'],
}),
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
},
},
config: { workspace: 'a' },
})
await link.exec()
t.matchSnapshot(await printLinks({ global: true }), 'should create a global link to current pkg')
})
t.test('link global linked pkg to local nm when using args', async t => {
const { link, printLinks } = await mockLink(t, {
globalPrefixDir: {
node_modules: {
'@myscope': {
foo: {
'package.json': JSON.stringify({
name: '@myscope/foo',
version: '1.0.0',
}),
},
bar: {
'package.json': JSON.stringify({
name: '@myscope/bar',
version: '1.0.0',
}),
},
linked: t.fixture('symlink', '../../../other/scoped-linked'),
},
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
}),
},
'test-pkg-link': t.fixture('symlink', '../../other/test-pkg-link'),
},
},
otherDirs: {
'test-pkg-link': {
'package.json': JSON.stringify({
name: 'test-pkg-link',
version: '1.0.0',
}),
},
'link-me-too': {
'package.json': JSON.stringify({
name: 'link-me-too',
version: '1.0.0',
}),
},
'scoped-linked': {
'package.json': JSON.stringify({
name: '@myscope/linked',
version: '1.0.0',
}),
},
},
prefixDir: {
'package.json': JSON.stringify({
name: 'my-project',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
},
}),
node_modules: {
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
}),
},
},
},
})
// installs examples for:
// - test-pkg-link: pkg linked to globalDir from local fs
// - @myscope/linked: scoped pkg linked to globalDir from local fs
// - @myscope/bar: prev installed scoped package available in globalDir
// - a: prev installed package available in globalDir
// - file:./link-me-too: pkg that needs to be reified in globalDir first
await link.exec([
'test-pkg-link',
'@myscope/linked',
'@myscope/bar',
'a',
'file:../other/link-me-too',
])
t.matchSnapshot(await printLinks(), 'should create a local symlink to global pkg')
})
t.test('link global linked pkg to local workspace using args', async t => {
const { link, printLinks } = await mockLink(t, {
globalPrefixDir: {
node_modules: {
'@myscope': {
foo: {
'package.json': JSON.stringify({
name: '@myscope/foo',
version: '1.0.0',
}),
},
bar: {
'package.json': JSON.stringify({
name: '@myscope/bar',
version: '1.0.0',
}),
},
linked: t.fixture('symlink', '../../../other/scoped-linked'),
},
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
}),
},
'test-pkg-link': t.fixture('symlink', '../../other/test-pkg-link'),
},
},
otherDirs: {
'test-pkg-link': {
'package.json': JSON.stringify({
name: 'test-pkg-link',
version: '1.0.0',
}),
},
'link-me-too': {
'package.json': JSON.stringify({
name: 'link-me-too',
version: '1.0.0',
}),
},
'scoped-linked': {
'package.json': JSON.stringify({
name: '@myscope/linked',
version: '1.0.0',
}),
},
},
prefixDir: {
'package.json': JSON.stringify({
name: 'my-project',
version: '1.0.0',
workspaces: ['packages/*'],
}),
packages: {
x: {
'package.json': JSON.stringify({
name: 'x',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
},
}),
},
},
node_modules: {
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
}),
},
},
},
config: { workspace: 'x' },
})
// installs examples for:
// - test-pkg-link: pkg linked to globalDir from local fs
// - @myscope/linked: scoped pkg linked to globalDir from local fs
// - @myscope/bar: prev installed scoped package available in globalDir
// - a: prev installed package available in globalDir
// - file:./link-me-too: pkg that needs to be reified in globalDir first
await link.exec([
'test-pkg-link',
'@myscope/linked',
'@myscope/bar',
'a',
'file:../other/link-me-too',
])
t.matchSnapshot(await printLinks(), 'should create a local symlink to global pkg')
})
t.test('link pkg already in global space', async t => {
const { npm, link, printLinks, prefix } = await mockLink(t, {
globalPrefixDir: {
node_modules: {
'@myscope': {
linked: t.fixture('symlink', '../../../other/scoped-linked'),
},
},
},
otherDirs: {
'scoped-linked': {
'package.json': JSON.stringify({
name: '@myscope/linked',
version: '1.0.0',
}),
},
},
prefixDir: {
'package.json': JSON.stringify({
name: 'my-project',
version: '1.0.0',
}),
},
})
// XXX: how to convert this to a config that gets passed in?
npm.config.find = () => 'default'
await link.exec(['@myscope/linked'])
t.equal(
require(resolve(prefix, 'package.json')).dependencies,
undefined,
'should not save to package.json upon linking'
)
t.matchSnapshot(await printLinks(), 'should create a local symlink to global pkg')
})
t.test('link pkg already in global space when prefix is a symlink', async t => {
const { npm, link, printLinks, prefix } = await mockLink(t, {
globalPrefixDir: t.fixture('symlink', './other/real-global-prefix'),
otherDirs: {
// mockNpm does this automatically but only for globalPrefixDir so here we
// need to do it manually since we are making a symlink somewhere else
'real-global-prefix': mockNpm.setGlobalNodeModules({
node_modules: {
'@myscope': {
linked: t.fixture('symlink', '../../../scoped-linked'),
},
},
}),
'scoped-linked': {
'package.json': JSON.stringify({
name: '@myscope/linked',
version: '1.0.0',
}),
},
},
prefixDir: {
'package.json': JSON.stringify({
name: 'my-project',
version: '1.0.0',
}),
},
})
npm.config.find = () => 'default'
await link.exec(['@myscope/linked'])
t.equal(
require(resolve(prefix, 'package.json')).dependencies,
undefined,
'should not save to package.json upon linking'
)
t.matchSnapshot(await printLinks(), 'should create a local symlink to global pkg')
})
t.test('should not save link to package file', async t => {
const { link, prefix } = await mockLink(t, {
globalPrefixDir: {
node_modules: {
'@myscope': {
linked: t.fixture('symlink', '../../../other/scoped-linked'),
},
},
},
otherDirs: {
'scoped-linked': {
'package.json': JSON.stringify({
name: '@myscope/linked',
version: '1.0.0',
}),
},
},
prefixDir: {
'package.json': JSON.stringify({
name: 'my-project',
version: '1.0.0',
}),
},
config: { save: false },
})
await link.exec(['@myscope/linked'])
t.match(
require(resolve(prefix, 'package.json')).dependencies,
undefined,
'should not save to package.json upon linking'
)
})
t.test('should not prune dependencies when linking packages', async t => {
const { link, prefix } = await mockLink(t, {
globalPrefixDir: {
node_modules: {
linked: t.fixture('symlink', '../../other/linked'),
},
},
otherDirs: {
linked: {
'package.json': JSON.stringify({
name: 'linked',
version: '1.0.0',
}),
},
},
prefixDir: {
node_modules: {
foo: {
'package.json': JSON.stringify({ name: 'foo', version: '1.0.0' }),
},
},
'package.json': JSON.stringify({
name: 'my-project',
version: '1.0.0',
}),
},
})
await link.exec(['linked'])
t.ok(
fs.statSync(resolve(prefix, 'node_modules/foo')),
'should not prune any extraneous dep when running npm link'
)
})
t.test('completion', async t => {
const { link } = await mockLink(t, {
globalPrefixDir: {
node_modules: {
foo: {},
bar: {},
lorem: {},
ipsum: {},
},
},
})
const words = await link.completion({})
t.same(
words,
['bar', 'foo', 'ipsum', 'lorem'],
'should list all package names available in globalDir'
)
})
t.test('--global option', async t => {
const { link } = await mockLink(t, {
config: { global: true },
})
await t.rejects(
link.exec([]),
/link should never be --global/,
'should throw an useful error'
)
})
t.test('hash character in working directory path', async t => {
const { link, printLinks } = await mockLink(t, {
globalPrefixDir: {
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
},
},
otherDirs: {
'i_like_#_in_my_paths': {
'test-pkg-link': {
'package.json': JSON.stringify({
name: 'test-pkg-link',
version: '1.0.0',
}),
},
},
},
chdir: ({ other }) => join(other, 'i_like_#_in_my_paths', 'test-pkg-link'),
})
await link.exec([])
t.matchSnapshot(await printLinks({ global: true }),
'should create a global link to current pkg, even within path with hash')
})
t.test('test linked installed as symlinks', async t => {
const { link, prefix, printLinks } = await mockLink(t, {
otherDirs: {
mylink: {
'package.json': JSON.stringify({
name: 'mylink',
version: '1.0.0',
}),
},
},
})
await link.exec([
join('file:../other/mylink'),
])
t.ok(fs.lstatSync(join(prefix, 'node_modules', 'mylink')).isSymbolicLink(),
'linked path should by symbolic link'
)
t.matchSnapshot(await printLinks(), 'linked package should not be installed')
})

34
deps/npm/test/lib/commands/ll.js vendored Normal file
View File

@ -0,0 +1,34 @@
const t = require('tap')
const tmock = require('../../fixtures/tmock')
t.test('ll', t => {
t.plan(3)
class LS {
constructor (npm) {
this.npm = npm
}
async exec (args) {
t.same(args, ['pkg'], 'should forward args')
}
}
const LL = tmock(t, '{LIB}/commands/ll.js', {
'{LIB}/commands/ls.js': LS,
})
const ll = new LL({
config: {
set: (key, value) => {
t.equal(key, 'long', 'should set long config value')
t.equal(value, true, 'should set a truthy value')
},
},
})
ll.exec(['pkg'], err => {
if (err) {
throw err
}
})
})

159
deps/npm/test/lib/commands/login.js vendored Normal file
View File

@ -0,0 +1,159 @@
const t = require('tap')
const fs = require('node:fs')
const path = require('node:path')
const stream = require('node:stream')
const ini = require('ini')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const mockGlobals = require('@npmcli/mock-globals')
const MockRegistry = require('@npmcli/mock-registry')
const mockLogin = async (t, { stdin: stdinLines, registry: registryUrl, ...options } = {}) => {
let stdin
if (stdinLines) {
stdin = new stream.PassThrough()
for (const l of stdinLines) {
stdin.write(l + '\n')
}
mockGlobals(t, {
'process.stdin': stdin,
'process.stdout': new stream.PassThrough(), // to quiet readline
}, { replace: true })
}
const mock = await loadMockNpm(t, {
...options,
command: 'login',
})
const registry = new MockRegistry({
tap: t,
registry: registryUrl ?? mock.npm.config.get('registry'),
})
return {
registry,
stdin,
rc: () => ini.parse(fs.readFileSync(path.join(mock.home, '.npmrc'), 'utf8')),
...mock,
}
}
t.test('usage', async t => {
const { login } = await loadMockNpm(t, { command: 'login' })
t.match(login.usage, 'login', 'usage has command name in it')
})
t.test('legacy', t => {
t.test('basic login', async t => {
const { npm, registry, login, rc } = await mockLogin(t, {
stdin: ['test-user', 'test-password'],
config: { 'auth-type': 'legacy' },
homeDir: {
'.npmrc': [
'//registry.npmjs.org/:_authToken=user',
'//registry.npmjs.org/:always-auth=user',
'//registry.npmjs.org/:email=test-email-old@npmjs.org',
].join('\n'),
},
})
registry.couchlogin({
username: 'test-user',
password: 'test-password',
token: 'npm_test-token',
})
await login.exec([])
t.same(npm.config.get('//registry.npmjs.org/:_authToken'), 'npm_test-token')
t.same(rc(), {
'//registry.npmjs.org/:_authToken': 'npm_test-token',
email: 'test-email-old@npmjs.org',
}, 'should only have token and un-nerfed old email')
})
t.test('scoped login default registry', async t => {
const { npm, registry, login, rc } = await mockLogin(t, {
stdin: ['test-user', 'test-password'],
config: {
'auth-type': 'legacy',
scope: '@npmcli',
},
})
registry.couchlogin({
username: 'test-user',
password: 'test-password',
token: 'npm_test-token',
})
await login.exec([])
t.same(npm.config.get('//registry.npmjs.org/:_authToken'), 'npm_test-token')
t.same(npm.config.get('@npmcli:registry'), 'https://registry.npmjs.org/')
t.same(rc(), {
'//registry.npmjs.org/:_authToken': 'npm_test-token',
'@npmcli:registry': 'https://registry.npmjs.org/',
}, 'should only have token and scope:registry')
})
t.test('scoped login scoped registry', async t => {
const { npm, registry, login, rc } = await mockLogin(t, {
stdin: ['test-user', 'test-password'],
registry: 'https://diff-registry.npmjs.org',
config: {
'auth-type': 'legacy',
scope: '@npmcli',
},
homeDir: {
'.npmrc': '@npmcli:registry=https://diff-registry.npmjs.org',
},
})
registry.couchlogin({
username: 'test-user',
password: 'test-password',
token: 'npm_test-token',
})
await login.exec([])
t.same(npm.config.get('//diff-registry.npmjs.org/:_authToken'), 'npm_test-token')
t.same(npm.config.get('@npmcli:registry'), 'https://diff-registry.npmjs.org')
t.same(rc(), {
'@npmcli:registry': 'https://diff-registry.npmjs.org',
'//diff-registry.npmjs.org/:_authToken': 'npm_test-token',
}, 'should only have token and scope:registry')
})
t.end()
})
t.test('web', t => {
t.test('basic login', async t => {
const { npm, registry, login, rc } = await mockLogin(t, {
config: { 'auth-type': 'web' },
})
registry.weblogin({ token: 'npm_test-token' })
await login.exec([])
t.same(npm.config.get('//registry.npmjs.org/:_authToken'), 'npm_test-token')
t.same(rc(), {
'//registry.npmjs.org/:_authToken': 'npm_test-token',
})
})
t.test('server error', async t => {
const { registry, login } = await mockLogin(t, {
config: { 'auth-type': 'web' },
})
registry.nock.post(registry.fullPath('/-/v1/login'))
.reply(503, {})
await t.rejects(
login.exec([]),
{ message: /503/ }
)
})
t.test('fallback', async t => {
const { npm, registry, login } = await mockLogin(t, {
stdin: ['test-user', 'test-password'],
config: { 'auth-type': 'web' },
})
registry.nock.post(registry.fullPath('/-/v1/login'))
.reply(404, {})
registry.couchlogin({
username: 'test-user',
password: 'test-password',
token: 'npm_test-token',
})
await login.exec([])
t.same(npm.config.get('//registry.npmjs.org/:_authToken'), 'npm_test-token')
})
t.end()
})

154
deps/npm/test/lib/commands/logout.js vendored Normal file
View File

@ -0,0 +1,154 @@
const t = require('tap')
const fs = require('node:fs/promises')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const MockRegistry = require('@npmcli/mock-registry')
const { join } = require('node:path')
t.test('token logout - user config', async t => {
const { npm, home, logs } = await loadMockNpm(t, {
homeDir: {
'.npmrc': [
'//registry.npmjs.org/:_authToken=@foo/',
'other-config=true',
].join('\n'),
},
})
const mockRegistry = new MockRegistry({ tap: t, registry: 'https://registry.npmjs.org/' })
mockRegistry.logout('@foo/')
await npm.exec('logout', [])
t.equal(
logs.verbose.byTitle('logout')[0],
'logout clearing token for https://registry.npmjs.org/',
'should log message with correct registry'
)
const userRc = await fs.readFile(join(home, '.npmrc'), 'utf-8')
t.equal(userRc.trim(), 'other-config=true')
})
t.test('token scoped logout - user config', async t => {
const { npm, home, logs } = await loadMockNpm(t, {
config: {
scope: '@myscope',
},
homeDir: {
'.npmrc': [
'//diff-registry.npmjs.com/:_authToken=@bar/',
'//registry.npmjs.org/:_authToken=@foo/',
'@myscope:registry=https://diff-registry.npmjs.com/',
].join('\n'),
},
})
const mockRegistry = new MockRegistry({ tap: t, registry: 'https://diff-registry.npmjs.com/' })
mockRegistry.logout('@bar/')
await npm.exec('logout', [])
t.equal(
logs.verbose.byTitle('logout')[0],
'logout clearing token for https://diff-registry.npmjs.com/',
'should log message with correct registry'
)
const userRc = await fs.readFile(join(home, '.npmrc'), 'utf-8')
t.equal(userRc.trim(), '//registry.npmjs.org/:_authToken=@foo/')
})
t.test('user/pass logout - user config', async t => {
const { npm, home, logs } = await loadMockNpm(t, {
homeDir: {
'.npmrc': [
'//registry.npmjs.org/:username=foo',
'//registry.npmjs.org/:_password=bar',
'other-config=true',
].join('\n'),
},
})
await npm.exec('logout', [])
t.equal(
logs.verbose.byTitle('logout')[0],
'logout clearing user credentials for https://registry.npmjs.org/',
'should log message with correct registry'
)
const userRc = await fs.readFile(join(home, '.npmrc'), 'utf-8')
t.equal(userRc.trim(), 'other-config=true')
})
t.test('missing credentials', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('logout', []),
{
code: 'ENEEDAUTH',
message: /not logged in to https:\/\/registry.npmjs.org\/, so can't log out!/,
},
'should reject with expected error code'
)
})
t.test('ignore invalid scoped registry config', async t => {
const { npm, home, logs } = await loadMockNpm(t, {
config: { scope: '@myscope' },
homeDir: {
'.npmrc': [
'//registry.npmjs.org/:_authToken=@foo/',
'other-config=true',
].join('\n'),
},
})
const mockRegistry = new MockRegistry({ tap: t, registry: 'https://registry.npmjs.org/' })
mockRegistry.logout('@foo/')
await npm.exec('logout', [])
t.equal(
logs.verbose.byTitle('logout')[0],
'logout clearing token for https://registry.npmjs.org/',
'should log message with correct registry'
)
const userRc = await fs.readFile(join(home, '.npmrc'), 'utf-8')
t.equal(userRc.trim(), 'other-config=true')
})
t.test('token logout - project config', async t => {
const { npm, home, logs, prefix } = await loadMockNpm(t, {
homeDir: {
'.npmrc': [
'//registry.npmjs.org/:_authToken=@foo/',
'other-config=true',
].join('\n'),
},
prefixDir: {
'.npmrc': [
'//registry.npmjs.org/:_authToken=@bar/',
'other-config=true',
].join('\n'),
},
})
const mockRegistry = new MockRegistry({ tap: t, registry: 'https://registry.npmjs.org/' })
mockRegistry.logout('@bar/')
await npm.exec('logout', [])
t.equal(
logs.verbose.byTitle('logout')[0],
'logout clearing token for https://registry.npmjs.org/',
'should log message with correct registry'
)
const userRc = await fs.readFile(join(home, '.npmrc'), 'utf-8')
t.equal(userRc.trim(), [
'//registry.npmjs.org/:_authToken=@foo/',
'other-config=true',
].join('\n'), 'leaves user config alone')
t.equal(
logs.verbose.byTitle('logout')[0],
'logout clearing token for https://registry.npmjs.org/',
'should log message with correct registry'
)
const projectRc = await fs.readFile(join(prefix, '.npmrc'), 'utf-8')
t.equal(projectRc.trim(), 'other-config=true', 'removes project config')
})

5287
deps/npm/test/lib/commands/ls.js vendored Normal file

File diff suppressed because it is too large Load Diff

568
deps/npm/test/lib/commands/org.js vendored Normal file
View File

@ -0,0 +1,568 @@
const t = require('tap')
const mockNpm = require('../../fixtures/mock-npm')
const mockOrg = async (t, { orgSize = 1, orgList = {}, ...npmOpts } = {}) => {
let setArgs = null
let rmArgs = null
let lsArgs = null
const libnpmorg = {
set: async (org, user, role, opts) => {
setArgs = { org, user, role, opts }
return {
org: {
name: org,
size: orgSize,
},
user,
role,
}
},
rm: async (org, user, opts) => {
rmArgs = { org, user, opts }
},
ls: async (org, opts) => {
lsArgs = { org, opts }
return orgList
},
}
const mock = await mockNpm(t, {
...npmOpts,
command: 'org',
mocks: {
libnpmorg,
...npmOpts.mocks,
},
})
return {
...mock,
setArgs: () => setArgs,
rmArgs: () => rmArgs,
lsArgs: () => lsArgs,
}
}
t.test('completion', async t => {
const { org } = await mockOrg(t)
const completion = argv => org.completion({ conf: { argv: { remain: argv } } })
const assertions = [
[
['npm', 'org'],
['set', 'rm', 'ls'],
],
[['npm', 'org', 'ls'], []],
[['npm', 'org', 'add'], []],
[['npm', 'org', 'rm'], []],
[['npm', 'org', 'set'], []],
]
for (const [argv, expected] of assertions) {
t.resolveMatch(completion(argv), expected, `completion for: ${argv.join(', ')}`)
}
t.rejects(
completion(['npm', 'org', 'flurb']),
/flurb not recognized/,
'errors for unknown subcommand'
)
})
t.test('npm org - invalid subcommand', async t => {
const { org } = await mockOrg(t)
await t.rejects(org.exec(['foo']), org.usage)
})
t.test('npm org add', async t => {
const { npm, org, setArgs, outputs } = await mockOrg(t)
await org.exec(['add', 'orgname', 'username'])
t.match(
setArgs(),
{
org: 'orgname',
user: 'username',
role: 'developer',
opts: npm.flatOptions,
},
'received the correct arguments'
)
t.equal(
outputs[0],
'Added username as developer to orgname. You now have 1 member in this org.',
'printed the correct output'
)
})
t.test('npm org add - no org', async t => {
const { org } = await mockOrg(t)
await t.rejects(
org.exec(['add', '', 'username']),
/`orgname` is required/,
'returns the correct error'
)
})
t.test('npm org add - no user', async t => {
const { org } = await mockOrg(t)
await t.rejects(
org.exec(['add', 'orgname', '']),
/`username` is required/,
'returns the correct error'
)
})
t.test('npm org add - invalid role', async t => {
const { org } = await mockOrg(t)
await t.rejects(
org.exec(['add', 'orgname', 'username', 'person']),
/`role` must be one of/,
'returns the correct error'
)
})
t.test('npm org add - more users', async t => {
const orgSize = 5
const { npm, org, outputs, setArgs } = await mockOrg(t, { orgSize })
await org.exec(['add', 'orgname', 'username'])
t.match(
setArgs(),
{
org: 'orgname',
user: 'username',
role: 'developer',
opts: npm.flatOptions,
},
'received the correct arguments'
)
t.equal(
outputs[0],
'Added username as developer to orgname. You now have 5 members in this org.',
'printed the correct output'
)
})
t.test('npm org add - json output', async t => {
const { npm, org, outputs, setArgs } = await mockOrg(t, {
config: { json: true },
})
await org.exec(['add', 'orgname', 'username'])
t.match(
setArgs(),
{
org: 'orgname',
user: 'username',
role: 'developer',
opts: npm.flatOptions,
},
'received the correct arguments'
)
t.strictSame(
JSON.parse(outputs[0]),
{
org: {
name: 'orgname',
size: 1,
},
user: 'username',
role: 'developer',
},
'printed the correct output'
)
})
t.test('npm org add - parseable output', async t => {
const config = { parseable: true }
const { npm, org, outputs, setArgs } = await mockOrg(t, {
config,
})
await org.exec(['add', 'orgname', 'username'])
t.match(
setArgs(),
{
org: 'orgname',
user: 'username',
role: 'developer',
opts: npm.flatOptions,
},
'received the correct arguments'
)
t.strictSame(
outputs.map(line => line.split(/\t/)),
[
['org', 'orgsize', 'user', 'role'],
['orgname', '1', 'username', 'developer'],
],
'printed the correct output'
)
})
t.test('npm org add - silent output', async t => {
const config = { loglevel: 'silent' }
const { npm, org, outputs, setArgs } = await mockOrg(t, {
config,
})
await org.exec(['add', 'orgname', 'username'])
t.match(
setArgs(),
{
org: 'orgname',
user: 'username',
role: 'developer',
opts: npm.flatOptions,
},
'received the correct arguments'
)
t.strictSame(outputs, [], 'prints no output')
})
t.test('npm org rm', async t => {
const { npm, org, outputs, lsArgs, rmArgs } = await mockOrg(t)
await org.exec(['rm', 'orgname', 'username'])
t.match(
rmArgs(),
{
org: 'orgname',
user: 'username',
opts: npm.flatOptions,
},
'libnpmorg.rm received the correct args'
)
t.match(
lsArgs(),
{
org: 'orgname',
opts: npm.flatOptions,
},
'libnpmorg.ls received the correct args'
)
t.equal(
outputs[0],
'Successfully removed username from orgname. You now have 0 members in this org.',
'printed the correct output'
)
})
t.test('npm org rm - no org', async t => {
const { org } = await mockOrg(t)
await t.rejects(
org.exec(['rm', '', 'username']),
/`orgname` is required/,
'threw the correct error'
)
})
t.test('npm org rm - no user', async t => {
const { org } = await mockOrg(t)
await t.rejects(org.exec(['rm', 'orgname']), /`username` is required/, 'threw the correct error')
})
t.test('npm org rm - one user left', async t => {
const orgList = {
one: 'developer',
}
const { npm, org, outputs, lsArgs, rmArgs } = await mockOrg(t, {
orgList,
})
await org.exec(['rm', 'orgname', 'username'])
t.match(
rmArgs(),
{
org: 'orgname',
user: 'username',
opts: npm.flatOptions,
},
'libnpmorg.rm received the correct args'
)
t.match(
lsArgs(),
{
org: 'orgname',
opts: npm.flatOptions,
},
'libnpmorg.ls received the correct args'
)
t.equal(
outputs[0],
'Successfully removed username from orgname. You now have 1 member in this org.',
'printed the correct output'
)
})
t.test('npm org rm - json output', async t => {
const config = { json: true }
const { npm, org, outputs, lsArgs, rmArgs } = await mockOrg(t, {
config,
})
await org.exec(['rm', 'orgname', 'username'])
t.match(
rmArgs(),
{
org: 'orgname',
user: 'username',
opts: npm.flatOptions,
},
'libnpmorg.rm received the correct args'
)
t.match(
lsArgs(),
{
org: 'orgname',
opts: npm.flatOptions,
},
'libnpmorg.ls received the correct args'
)
t.strictSame(
JSON.parse(outputs[0]),
{
user: 'username',
org: 'orgname',
userCount: 0,
deleted: true,
},
'printed the correct output'
)
})
t.test('npm org rm - parseable output', async t => {
const config = { parseable: true }
const { npm, org, outputs, lsArgs, rmArgs } = await mockOrg(t, {
config,
})
await org.exec(['rm', 'orgname', 'username'])
t.match(
rmArgs(),
{
org: 'orgname',
user: 'username',
opts: npm.flatOptions,
},
'libnpmorg.rm received the correct args'
)
t.match(
lsArgs(),
{
org: 'orgname',
opts: npm.flatOptions,
},
'libnpmorg.ls received the correct args'
)
t.strictSame(
outputs.map(line => line.split(/\t/)),
[
['user', 'org', 'userCount', 'deleted'],
['username', 'orgname', '0', 'true'],
],
'printed the correct output'
)
})
t.test('npm org rm - silent output', async t => {
const config = { loglevel: 'silent' }
const { npm, org, outputs, lsArgs, rmArgs } = await mockOrg(t, {
config,
})
await org.exec(['rm', 'orgname', 'username'])
t.match(
rmArgs(),
{
org: 'orgname',
user: 'username',
opts: npm.flatOptions,
},
'libnpmorg.rm received the correct args'
)
t.match(
lsArgs(),
{
org: 'orgname',
opts: npm.flatOptions,
},
'libnpmorg.ls received the correct args'
)
t.strictSame(outputs, [], 'printed no output')
})
t.test('npm org ls', async t => {
const orgList = {
one: 'developer',
two: 'admin',
three: 'owner',
}
const { npm, org, outputs, lsArgs } = await mockOrg(t, {
orgList,
})
await org.exec(['ls', 'orgname'])
t.match(
lsArgs(),
{
org: 'orgname',
opts: npm.flatOptions,
},
'receieved the correct args'
)
t.strictSame(outputs, [
'one - developer',
'three - owner',
'two - admin',
])
})
t.test('npm org ls - user filter', async t => {
const orgList = {
username: 'admin',
missing: 'admin',
}
const { npm, org, outputs, lsArgs } = await mockOrg(t, {
orgList,
})
await org.exec(['ls', 'orgname', 'username'])
t.match(
lsArgs(),
{
org: 'orgname',
opts: npm.flatOptions,
},
'receieved the correct args'
)
t.strictSame(outputs, [
'username - admin',
])
})
t.test('npm org ls - user filter, missing user', async t => {
const orgList = {
missing: 'admin',
}
const { npm, org, outputs, lsArgs } = await mockOrg(t, {
orgList,
})
await org.exec(['ls', 'orgname', 'username'])
t.match(
lsArgs(),
{
org: 'orgname',
opts: npm.flatOptions,
},
'receieved the correct args'
)
t.strictSame(outputs, [])
})
t.test('npm org ls - no org', async t => {
const { org } = await mockOrg(t)
await t.rejects(org.exec(['ls']), /`orgname` is required/, 'throws the correct error')
})
t.test('npm org ls - json output', async t => {
const config = { json: true }
const orgList = {
one: 'developer',
two: 'admin',
three: 'owner',
}
const { npm, org, outputs, lsArgs } = await mockOrg(t, {
orgList,
config,
})
await org.exec(['ls', 'orgname'])
t.match(
lsArgs(),
{
org: 'orgname',
opts: npm.flatOptions,
},
'receieved the correct args'
)
t.strictSame(JSON.parse(outputs[0]), orgList, 'prints the correct output')
})
t.test('npm org ls - parseable output', async t => {
const config = { parseable: true }
const orgList = {
one: 'developer',
two: 'admin',
three: 'owner',
}
const { npm, org, outputs, lsArgs } = await mockOrg(t, {
orgList,
config,
})
await org.exec(['ls', 'orgname'])
t.match(
lsArgs(),
{
org: 'orgname',
opts: npm.flatOptions,
},
'receieved the correct args'
)
t.strictSame(
outputs.map(line => line.split(/\t/)),
[
['user', 'role'],
['one', 'developer'],
['two', 'admin'],
['three', 'owner'],
],
'printed the correct output'
)
})
t.test('npm org ls - silent output', async t => {
const config = { loglevel: 'silent' }
const orgList = {
one: 'developer',
two: 'admin',
three: 'owner',
}
const { npm, org, outputs, lsArgs } = await mockOrg(t, {
orgList,
config,
})
await org.exec(['ls', 'orgname'])
t.match(
lsArgs(),
{
org: 'orgname',
opts: npm.flatOptions,
},
'receieved the correct args'
)
t.strictSame(outputs, [], 'printed no output')
})

664
deps/npm/test/lib/commands/outdated.js vendored Normal file
View File

@ -0,0 +1,664 @@
const t = require('tap')
const MockRegistry = require('@npmcli/mock-registry')
const _mockNpm = require('../../fixtures/mock-npm')
const { cleanCwd } = require('../../fixtures/clean-snapshot')
t.cleanSnapshot = (str) => cleanCwd(str)
const packument = spec => {
const mocks = {
cat: {
name: 'cat',
'dist-tags': {
latest: '1.0.1',
},
versions: {
'1.0.1': {
version: '1.0.1',
dependencies: {
dog: '2.0.0',
},
},
},
},
chai: {
name: 'chai',
'dist-tags': {
latest: '1.0.1',
},
versions: {
'1.0.1': {
version: '1.0.1',
},
},
},
dog: {
name: 'dog',
'dist-tags': {
latest: '2.0.0',
},
versions: {
'1.0.1': {
version: '1.0.1',
},
'2.0.0': {
version: '2.0.0',
},
},
},
theta: {
name: 'theta',
'dist-tags': {
latest: '1.0.1',
},
versions: {
'1.0.1': {
version: '1.0.1',
},
},
},
}
if (spec.name === 'eta') {
throw new Error('There is an error with this package.')
}
if (!mocks[spec.name]) {
const err = new Error()
err.code = 'E404'
throw err
}
return mocks[spec.name]
}
const fixtures = {
global: {
node_modules: {
cat: {
'package.json': JSON.stringify({
name: 'cat',
version: '1.0.0',
}, null, 2),
},
},
},
local: {
'package.json': JSON.stringify({
name: 'delta',
version: '1.0.0',
dependencies: {
cat: '^1.0.0',
dog: '^1.0.0',
theta: '^1.0.0',
},
devDependencies: {
zeta: '^1.0.0',
},
optionalDependencies: {
lorem: '^1.0.0',
},
peerDependencies: {
chai: '^1.0.0',
},
}, null, 2),
node_modules: {
cat: {
'package.json': JSON.stringify({
name: 'cat',
version: '1.0.0',
dependencies: {
dog: '2.0.0',
},
}, null, 2),
node_modules: {
dog: {
'package.json': JSON.stringify({
name: 'dog',
version: '2.0.0',
}, null, 2),
},
},
},
chai: {
'package.json': JSON.stringify({
name: 'chai',
version: '1.0.0',
}, null, 2),
},
dog: {
'package.json': JSON.stringify({
name: 'dog',
version: '1.0.1',
}, null, 2),
},
zeta: {
'package.json': JSON.stringify({
name: 'zeta',
version: '1.0.0',
}, null, 2),
},
},
},
workspaces: {
'package.json': JSON.stringify({
name: 'workspaces-project',
version: '1.0.0',
workspaces: ['packages/*'],
dependencies: {
dog: '^1.0.0',
},
}),
node_modules: {
a: t.fixture('symlink', '../packages/a'),
b: t.fixture('symlink', '../packages/b'),
c: t.fixture('symlink', '../packages/c'),
d: t.fixture('symlink', '../packages/d'),
e: t.fixture('symlink', '../packages/e'),
cat: {
'package.json': JSON.stringify({
name: 'cat',
version: '1.0.0',
dependencies: {
dog: '2.0.0',
},
}),
node_modules: {
dog: {
'package.json': JSON.stringify({
name: 'dog',
version: '2.0.0',
}),
},
},
},
chai: {
'package.json': JSON.stringify({
name: 'chai',
version: '1.0.0',
}),
},
dog: {
'package.json': JSON.stringify({
name: 'dog',
version: '1.0.1',
}),
},
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
dependencies: {
chai: '^1.0.0',
},
}),
},
zeta: {
'package.json': JSON.stringify({
name: 'zeta',
version: '1.0.0',
}),
},
},
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
dependencies: {
b: '^1.0.0',
cat: '^1.0.0',
foo: '^1.0.0',
},
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
dependencies: {
zeta: '^1.0.0',
},
}),
},
c: {
'package.json': JSON.stringify({
name: 'c',
version: '1.0.0',
dependencies: {
theta: '^1.0.0',
},
}),
},
d: {
'package.json': JSON.stringify({
name: 'd',
version: '1.0.0',
dependencies: {
theta: '^1.0.0',
},
}),
},
e: {
'package.json': JSON.stringify({
name: 'e',
version: '1.0.0',
dependencies: {
theta: '^1.0.0',
},
}),
},
},
},
}
const mockNpm = async (t, { prefixDir, ...opts } = {}) => {
const res = await _mockNpm(t, {
command: 'outdated',
mocks: {
pacote: {
packument,
},
},
...opts,
prefixDir,
})
// this is not currently used, but ensures that no requests are
// hitting the registry.
// XXX: the pacote mock should be replaced with mock registry calls
const registry = new MockRegistry({
tap: t,
registry: res.npm.config.get('registry'),
strict: true,
})
return {
...res,
registry,
}
}
t.test('should display outdated deps', async t => {
await t.test('outdated global', async t => {
const { outdated, joinedOutput } = await mockNpm(t, {
globalPrefixDir: fixtures.global,
config: { global: true },
})
await outdated.exec([])
t.equal(process.exitCode, 1)
t.matchSnapshot(joinedOutput())
})
await t.test('outdated', async t => {
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: fixtures.local,
config: {
color: 'always',
},
})
await outdated.exec([])
t.equal(process.exitCode, 1)
t.matchSnapshot(joinedOutput())
})
await t.test('outdated --omit=dev', async t => {
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: fixtures.local,
config: {
omit: ['dev'],
color: 'always',
},
})
await outdated.exec([])
t.equal(process.exitCode, 1)
t.matchSnapshot(joinedOutput())
})
await t.test('outdated --omit=dev --omit=peer', async t => {
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: fixtures.local,
config: {
omit: ['dev', 'peer'],
color: 'always',
},
})
await outdated.exec([])
t.equal(process.exitCode, 1)
t.matchSnapshot(joinedOutput())
})
await t.test('outdated --omit=prod', async t => {
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: fixtures.local,
config: {
omit: ['prod'],
color: 'always',
},
})
await outdated.exec([])
t.equal(process.exitCode, 1)
t.matchSnapshot(joinedOutput())
})
await t.test('outdated --long', async t => {
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: fixtures.local,
config: {
long: true,
},
})
await outdated.exec([])
t.equal(process.exitCode, 1)
t.matchSnapshot(joinedOutput())
})
await t.test('outdated --json', async t => {
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: fixtures.local,
config: {
json: true,
},
})
await outdated.exec([])
t.equal(process.exitCode, 1)
t.matchSnapshot(joinedOutput())
})
await t.test('outdated --json --long', async t => {
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: fixtures.local,
config: {
json: true,
long: true,
},
})
await outdated.exec([])
t.equal(process.exitCode, 1)
t.matchSnapshot(joinedOutput())
})
await t.test('outdated --parseable', async t => {
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: fixtures.local,
config: {
parseable: true,
},
})
await outdated.exec([])
t.equal(process.exitCode, 1)
t.matchSnapshot(joinedOutput())
})
await t.test('outdated --parseable --long', async t => {
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: fixtures.local,
config: {
parseable: true,
long: true,
},
})
await outdated.exec([])
t.equal(process.exitCode, 1)
t.matchSnapshot(joinedOutput())
})
await t.test('outdated --all', async t => {
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: fixtures.local,
config: {
all: true,
},
})
await outdated.exec([])
t.equal(process.exitCode, 1)
t.matchSnapshot(joinedOutput())
})
await t.test('outdated specific dep', async t => {
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: fixtures.local,
})
await outdated.exec(['cat'])
t.equal(process.exitCode, 1)
t.matchSnapshot(joinedOutput())
})
})
t.test('should return if no outdated deps', async t => {
const testDir = {
'package.json': JSON.stringify({
name: 'delta',
version: '1.0.0',
dependencies: {
cat: '^1.0.0',
},
}, null, 2),
node_modules: {
cat: {
'package.json': JSON.stringify({
name: 'cat',
version: '1.0.1',
}, null, 2),
},
},
}
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: testDir,
})
await outdated.exec([])
t.equal(joinedOutput(), '', 'no logs')
})
t.test('throws if error with a dep', async t => {
const testDir = {
'package.json': JSON.stringify({
name: 'delta',
version: '1.0.0',
dependencies: {
eta: '^1.0.0',
},
}, null, 2),
node_modules: {
eta: {
'package.json': JSON.stringify({
name: 'eta',
version: '1.0.1',
}, null, 2),
},
},
}
const { outdated } = await mockNpm(t, {
prefixDir: testDir,
})
await t.rejects(outdated.exec([]), 'There is an error with this package.')
})
t.test('should skip missing non-prod deps', async t => {
const testDir = {
'package.json': JSON.stringify({
name: 'delta',
version: '1.0.0',
devDependencies: {
chai: '^1.0.0',
},
}, null, 2),
node_modules: {},
}
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: testDir,
})
await outdated.exec([])
t.equal(joinedOutput(), '', 'no logs')
})
t.test('should skip invalid pkg ranges', async t => {
const testDir = {
'package.json': JSON.stringify({
name: 'delta',
version: '1.0.0',
dependencies: {
cat: '>=^2',
},
}, null, 2),
node_modules: {
cat: {
'package.json': JSON.stringify({
name: 'cat',
version: '1.0.0',
}, null, 2),
},
},
}
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: testDir,
})
await outdated.exec([])
t.equal(joinedOutput(), '', 'no logs')
})
t.test('should skip git specs', async t => {
const testDir = {
'package.json': JSON.stringify({
name: 'delta',
version: '1.0.0',
dependencies: {
cat: 'github:username/foo',
},
}, null, 2),
node_modules: {
cat: {
'package.json': JSON.stringify({
name: 'cat',
version: '1.0.0',
}, null, 2),
},
},
}
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: testDir,
})
await outdated.exec([])
t.equal(joinedOutput(), '', 'no logs')
})
t.test('workspaces', async t => {
const mockWorkspaces = async (t, { exitCode = 1, ...config } = {}) => {
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: fixtures.workspaces,
config,
})
await outdated.exec([])
t.matchSnapshot(joinedOutput(), 'output')
t.equal(process.exitCode, exitCode ?? undefined)
}
await t.test('should display ws outdated deps human output', t =>
mockWorkspaces(t))
// TODO: This should display dog, but doesn't because arborist filters
// workspace deps even if they're also root deps
// This will be fixed in a future arborist version
await t.test('should display only root outdated when ws disabled', t =>
mockWorkspaces(t, { workspaces: false, exitCode: null }))
await t.test('should display ws outdated deps json output', t =>
mockWorkspaces(t, { json: true }))
await t.test('should display ws outdated deps parseable output', t =>
mockWorkspaces(t, { parseable: true }))
await t.test('should display all dependencies', t =>
mockWorkspaces(t, { all: true }))
await t.test('should highlight ws in dependend by section', t =>
mockWorkspaces(t, { color: 'always' }))
await t.test('should display results filtered by ws', t =>
mockWorkspaces(t, { workspace: 'a' }))
await t.test('should display json results filtered by ws', t =>
mockWorkspaces(t, { json: true, workspace: 'a' }))
await t.test('should display parseable results filtered by ws', t =>
mockWorkspaces(t, { parseable: true, workspace: 'a' }))
await t.test('should display nested deps when filtering by ws and using --all', t =>
mockWorkspaces(t, { all: true, workspace: 'a' }))
await t.test('should display no results if ws has no deps to display', t =>
mockWorkspaces(t, { workspace: 'b', exitCode: null }))
await t.test('should display missing deps when filtering by ws', t =>
mockWorkspaces(t, { workspace: 'c', exitCode: 1 }))
})
t.test('aliases', async t => {
const testDir = {
'package.json': JSON.stringify({
name: 'display-aliases',
version: '1.0.0',
dependencies: {
cat: 'npm:dog@latest',
},
}),
node_modules: {
cat: {
'package.json': JSON.stringify({
name: 'dog',
version: '1.0.0',
}),
},
},
}
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: testDir,
})
await outdated.exec([])
t.matchSnapshot(joinedOutput(), 'should display aliased outdated dep output')
t.equal(process.exitCode, 1)
})
t.test('aliases with version range', async t => {
const testDir = {
'package.json': JSON.stringify({
name: 'display-aliases',
version: '1.0.0',
dependencies: {
cat: 'npm:dog@^1.0.0',
},
}),
node_modules: {
cat: {
'package.json': JSON.stringify({
name: 'dog',
version: '1.0.0',
}),
},
},
}
const { outdated, joinedOutput } = await mockNpm(t, {
prefixDir: testDir,
})
await outdated.exec([])
t.matchSnapshot(
joinedOutput(),
'should display aliased outdated dep output with correct wanted values'
)
t.equal(process.exitCode, 1)
})

682
deps/npm/test/lib/commands/owner.js vendored Normal file
View File

@ -0,0 +1,682 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const MockRegistry = require('@npmcli/mock-registry')
const path = require('node:path')
const npa = require('npm-package-arg')
const packageName = '@npmcli/test-package'
const spec = npa(packageName)
const auth = { '//registry.npmjs.org/:_authToken': 'test-auth-token' }
const maintainers = [
{ email: 'test-user-a@npmjs.org', name: 'test-user-a' },
{ email: 'test-user-b@npmjs.org', name: 'test-user-b' },
]
const workspaceFixture = {
'package.json': JSON.stringify({
name: packageName,
version: '1.2.3-test',
workspaces: ['workspace-a', 'workspace-b', 'workspace-c'],
}),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.2.3-a',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '1.2.3-n',
}),
},
'workspace-c': JSON.stringify({
'package.json': {
name: 'workspace-n',
version: '1.2.3-n',
},
}),
}
function registryPackage (t, registry, name) {
const mockRegistry = new MockRegistry({ tap: t, registry })
const manifest = mockRegistry.manifest({
name,
packuments: [{ maintainers, version: '1.0.0' }],
})
return mockRegistry.package({ manifest })
}
t.test('owner no args', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('owner', []),
{ code: 'EUSAGE' },
'rejects with usage'
)
})
t.test('owner ls no args', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: packageName }),
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers, version: '1.0.0' }],
})
await registry.package({ manifest })
await npm.exec('owner', ['ls'])
t.match(joinedOutput(), maintainers.map(m => `${m.name} <${m.email}>`).join('\n'))
})
t.test('local package.json has no name', async t => {
const { npm } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ hello: 'world' }),
},
})
await t.rejects(
npm.exec('owner', ['ls']),
{ code: 'EUSAGE' }
)
})
t.test('owner ls global', async t => {
const { npm } = await loadMockNpm(t, {
config: { global: true },
})
await t.rejects(
npm.exec('owner', ['ls']),
{ code: 'EUSAGE' },
'rejects with usage'
)
})
t.test('owner ls no args no cwd package', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('owner', ['ls'])
)
})
t.test('owner ls fails to retrieve packument', async t => {
const { npm, logs } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: packageName }),
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.nock.get(`/${spec.escapedName}`).reply(404)
await t.rejects(npm.exec('owner', ['ls']))
t.match(logs.error.byTitle('owner ls'), [`owner ls Couldn't get owner data @npmcli/test-package`])
})
t.test('owner ls <pkg>', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers, version: '1.0.0' }],
})
await registry.package({ manifest })
await npm.exec('owner', ['ls', packageName])
t.match(joinedOutput(), maintainers.map(m => `${m.name} <${m.email}>`).join('\n'))
})
t.test('owner ls <pkg> no maintainers', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
versions: ['1.0.0'],
})
await registry.package({ manifest })
await npm.exec('owner', ['ls', packageName])
t.equal(joinedOutput(), 'no admin found')
})
t.test('owner add <user> <pkg>', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: { ...auth },
})
const username = 'foo'
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers, version: '1.0.0' }],
})
registry.couchuser({ username })
await registry.package({ manifest })
registry.nock.put(`/${spec.escapedName}/-rev/${manifest._rev}`, body => {
t.match(body, {
_id: manifest._id,
_rev: manifest._rev,
maintainers: [
...manifest.maintainers,
{ name: username, email: '' },
],
})
return true
}).reply(200, {})
await npm.exec('owner', ['add', username, packageName])
t.equal(joinedOutput(), `+ ${username} (${packageName})`)
})
t.test('owner add <user> cwd package', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: packageName }),
},
config: { ...auth },
})
const username = 'foo'
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers, version: '1.0.0' }],
})
registry.couchuser({ username })
await registry.package({ manifest })
registry.nock.put(`/${spec.escapedName}/-rev/${manifest._rev}`, body => {
t.match(body, {
_id: manifest._id,
_rev: manifest._rev,
maintainers: [
...manifest.maintainers,
{ name: username, email: '' },
],
})
return true
}).reply(200, {})
await npm.exec('owner', ['add', username])
t.equal(joinedOutput(), `+ ${username} (${packageName})`)
})
t.test('owner add <user> <pkg> already an owner', async t => {
const { npm, joinedOutput, logs } = await loadMockNpm(t, {
config: { ...auth },
})
const username = maintainers[0].name
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers, version: '1.0.0' }],
})
registry.couchuser({ username })
await registry.package({ manifest })
await npm.exec('owner', ['add', username, packageName])
t.equal(joinedOutput(), '')
t.match(
logs.info.byTitle('owner add'),
[`Already a package owner: test-user-a <test-user-a@npmjs.org>`]
)
})
t.test('owner add <user> <pkg> fails to retrieve user', async t => {
const { npm, logs } = await loadMockNpm(t, {
config: { ...auth },
})
const username = 'foo'
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.couchuser({ username, responseCode: 404, body: {} })
await t.rejects(npm.exec('owner', ['add', username, packageName]))
t.match(logs.error.byTitle('owner mutate'), [`Error getting user data for ${username}`])
})
t.test('owner add <user> <pkg> fails to PUT updates', async t => {
const { npm } = await loadMockNpm(t, {
config: { ...auth },
})
const username = 'foo'
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers, version: '1.0.0' }],
})
registry.couchuser({ username })
await registry.package({ manifest })
registry.nock.put(`/${spec.escapedName}/-rev/${manifest._rev}`).reply(404, {})
await t.rejects(
npm.exec('owner', ['add', username, packageName]),
{ code: 'EOWNERMUTATE' }
)
})
t.test('owner add <user> <pkg> no previous maintainers property from server', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: { ...auth },
})
const username = 'foo'
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers: undefined, version: '1.0.0' }],
})
registry.couchuser({ username })
await registry.package({ manifest })
registry.nock.put(`/${spec.escapedName}/-rev/${manifest._rev}`, body => {
t.match(body, {
_id: manifest._id,
_rev: manifest._rev,
maintainers: [{ name: username, email: '' }],
})
return true
}).reply(200, {})
await npm.exec('owner', ['add', username, packageName])
t.equal(joinedOutput(), `+ ${username} (${packageName})`)
})
t.test('owner add no user', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('owner', ['add']),
{ code: 'EUSAGE' }
)
})
t.test('owner add <user> no pkg global', async t => {
const { npm } = await loadMockNpm(t, {
config: { global: true },
})
await t.rejects(
npm.exec('owner', ['add', 'foo']),
{ code: 'EUSAGE' }
)
})
t.test('owner add <user> no cwd package', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('owner', ['add', 'foo']),
{ code: 'EUSAGE' }
)
})
t.test('owner rm <user> <pkg>', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: { ...auth },
})
const username = maintainers[0].name
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers, version: '1.0.0' }],
})
registry.couchuser({ username })
await registry.package({ manifest })
registry.nock.put(`/${spec.escapedName}/-rev/${manifest._rev}`, body => {
t.match(body, {
_id: manifest._id,
_rev: manifest._rev,
maintainers: maintainers.slice(1),
})
return true
}).reply(200, {})
await npm.exec('owner', ['rm', username, packageName])
t.equal(joinedOutput(), `- ${username} (${packageName})`)
})
t.test('owner rm <user> <pkg> not a current owner', async t => {
const { npm, logs } = await loadMockNpm(t, {
config: { ...auth },
})
const username = 'foo'
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers, version: '1.0.0' }],
})
registry.couchuser({ username })
await registry.package({ manifest })
await npm.exec('owner', ['rm', username, packageName])
t.match(logs.info.byTitle('owner rm'), [`Not a package owner: ${username}`])
})
t.test('owner rm <user> cwd package', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: packageName }),
},
config: { ...auth },
})
const username = maintainers[0].name
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers, version: '1.0.0' }],
})
registry.couchuser({ username })
await registry.package({ manifest })
registry.nock.put(`/${spec.escapedName}/-rev/${manifest._rev}`, body => {
t.match(body, {
_id: manifest._id,
_rev: manifest._rev,
maintainers: maintainers.slice(1),
})
return true
}).reply(200, {})
await npm.exec('owner', ['rm', username])
t.equal(joinedOutput(), `- ${username} (${packageName})`)
})
t.test('owner rm <user> only user', async t => {
const { npm } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: packageName }),
},
config: { ...auth },
})
const username = maintainers[0].name
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers: maintainers.slice(0, 1), version: '1.0.0' }],
})
registry.couchuser({ username })
await registry.package({ manifest })
await t.rejects(
npm.exec('owner', ['rm', username]),
{
code: 'EOWNERRM',
message: 'Cannot remove all owners of a package. Add someone else first.',
}
)
})
t.test('owner rm no user', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('owner', ['rm']),
{ code: 'EUSAGE' }
)
})
t.test('owner rm no pkg global', async t => {
const { npm } = await loadMockNpm(t, {
config: { global: true },
})
await t.rejects(
npm.exec('owner', ['rm', 'foo']),
{ code: 'EUSAGE' }
)
})
t.test('owner rm <user> no cwd package', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('owner', ['rm', 'foo']),
{ code: 'EUSAGE' }
)
})
t.test('workspaces', async t => {
t.test('owner no args --workspace', async t => {
const { npm } = await loadMockNpm(t, {
prefixDir: workspaceFixture,
config: {
workspace: 'workspace-a',
},
})
await t.rejects(
npm.exec('owner', []),
{ code: 'EUSAGE' },
'rejects with usage'
)
})
t.test('owner ls implicit workspace', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: workspaceFixture,
chdir: ({ prefix }) => path.join(prefix, 'workspace-a'),
})
await registryPackage(t, npm.config.get('registry'), 'workspace-a')
await npm.exec('owner', ['ls'])
t.match(joinedOutput(), maintainers.map(m => `${m.name} <${m.email}>`).join('\n'))
})
t.test('owner ls explicit workspace', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: workspaceFixture,
config: {
workspace: 'workspace-a',
},
})
await registryPackage(t, npm.config.get('registry'), 'workspace-a')
await npm.exec('owner', ['ls'])
t.match(joinedOutput(), maintainers.map(m => `${m.name} <${m.email}>`).join('\n'))
})
t.test('owner ls <pkg> implicit workspace', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: workspaceFixture,
chdir: ({ prefix }) => path.join(prefix, 'workspace-a'),
})
await registryPackage(t, npm.config.get('registry'), packageName)
await npm.exec('owner', ['ls', packageName])
t.match(joinedOutput(), maintainers.map(m => `${m.name} <${m.email}>`).join('\n'))
})
t.test('owner ls <pkg> explicit workspace', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: workspaceFixture,
config: {
workspace: 'workspace-a',
},
})
await registryPackage(t, npm.config.get('registry'), packageName)
await npm.exec('owner', ['ls', packageName])
t.match(joinedOutput(), maintainers.map(m => `${m.name} <${m.email}>`).join('\n'))
})
t.test('owner add implicit workspace', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: workspaceFixture,
chdir: ({ prefix }) => path.join(prefix, 'workspace-a'),
})
const username = 'foo'
const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
const manifest = registry.manifest({
name: 'workspace-a',
packuments: [{ maintainers, version: '1.0.0' }],
})
await registry.package({ manifest })
registry.couchuser({ username })
registry.nock.put(`/workspace-a/-rev/${manifest._rev}`, body => {
t.match(body, {
_id: manifest._id,
_rev: manifest._rev,
maintainers: [
...manifest.maintainers,
{ name: username, email: '' },
],
})
return true
}).reply(200, {})
await npm.exec('owner', ['add', username])
t.equal(joinedOutput(), `+ ${username} (workspace-a)`)
})
t.test('owner add --workspace', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: workspaceFixture,
config: {
workspace: 'workspace-a',
},
})
const username = 'foo'
const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
const manifest = registry.manifest({
name: 'workspace-a',
packuments: [{ maintainers, version: '1.0.0' }],
})
await registry.package({ manifest })
registry.couchuser({ username })
registry.nock.put(`/workspace-a/-rev/${manifest._rev}`, body => {
t.match(body, {
_id: manifest._id,
_rev: manifest._rev,
maintainers: [
...manifest.maintainers,
{ name: username, email: '' },
],
})
return true
}).reply(200, {})
await npm.exec('owner', ['add', username])
t.equal(joinedOutput(), `+ ${username} (workspace-a)`)
})
t.test('owner rm --workspace', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: workspaceFixture,
chdir: ({ prefix }) => path.join(prefix, 'workspace-a'),
})
const registry = new MockRegistry({ tap: t, registry: npm.config.get('registry') })
const username = maintainers[0].name
const manifest = registry.manifest({
name: 'workspace-a',
packuments: [{ maintainers, version: '1.0.0' }],
})
await registry.package({ manifest })
registry.couchuser({ username })
registry.nock.put(`/workspace-a/-rev/${manifest._rev}`, body => {
t.match(body, {
_id: manifest._id,
_rev: manifest._rev,
maintainers: maintainers.slice(1),
})
return true
}).reply(200, {})
await npm.exec('owner', ['rm', username])
t.equal(joinedOutput(), `- ${username} (workspace-a)`)
})
})
t.test('completion', async t => {
const mockCompletion = (t, opts) => loadMockNpm(t, { command: 'owner', ...opts })
t.test('basic commands', async t => {
const { owner } = await mockCompletion(t)
const testComp = async (argv, expect) => {
const res = await owner.completion({ conf: { argv: { remain: argv } } })
t.strictSame(res, expect, argv.join(' '))
}
await Promise.all([
testComp(['npm', 'foo'], []),
testComp(['npm', 'owner'], ['add', 'rm', 'ls']),
testComp(['npm', 'owner', 'add'], []),
testComp(['npm', 'owner', 'ls'], []),
testComp(['npm', 'owner', 'rm', 'foo'], []),
])
})
t.test('completion npm owner rm', async t => {
const { npm, owner } = await mockCompletion(t, {
prefixDir: { 'package.json': JSON.stringify({ name: packageName }) },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers, version: '1.0.0' }],
})
await registry.package({ manifest })
const res = await owner.completion({ conf: { argv: { remain: ['npm', 'owner', 'rm'] } } })
t.strictSame(res, maintainers.map(m => m.name), 'should return list of current owners')
})
t.test('completion npm owner rm no cwd package', async t => {
const { owner } = await mockCompletion(t)
const res = await owner.completion({ conf: { argv: { remain: ['npm', 'owner', 'rm'] } } })
t.strictSame(res, [], 'should have no owners to autocomplete if not cwd package')
})
t.test('completion npm owner rm global', async t => {
const { owner } = await mockCompletion(t, {
config: { global: true },
})
const res = await owner.completion({ conf: { argv: { remain: ['npm', 'owner', 'rm'] } } })
t.strictSame(res, [], 'should have no owners to autocomplete if global')
})
t.test('completion npm owner rm no owners found', async t => {
const { npm, owner } = await mockCompletion(t, {
prefixDir: { 'package.json': JSON.stringify({ name: packageName }) },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
const manifest = registry.manifest({
name: packageName,
packuments: [{ maintainers: [], version: '1.0.0' }],
})
await registry.package({ manifest })
const res = await owner.completion({ conf: { argv: { remain: ['npm', 'owner', 'rm'] } } })
t.strictSame(res, [], 'should return no owners if not found')
})
})

239
deps/npm/test/lib/commands/pack.js vendored Normal file
View File

@ -0,0 +1,239 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const { cleanZlib } = require('../../fixtures/clean-snapshot')
const path = require('node:path')
const fs = require('node:fs')
t.cleanSnapshot = data => cleanZlib(data)
t.test('should pack current directory with no arguments', async t => {
const { npm, outputs, logs } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'test-package',
version: '1.0.0',
}),
},
})
await npm.exec('pack', [])
const filename = 'test-package-1.0.0.tgz'
t.strictSame(outputs, [filename])
t.matchSnapshot(logs.notice, 'logs pack contents')
t.ok(fs.statSync(path.resolve(npm.prefix, filename)))
})
t.test('follows pack-destination config', async t => {
const { npm, outputs } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'test-package',
version: '1.0.0',
}),
'tar-destination': {},
},
config: ({ prefix }) => ({ 'pack-destination': path.join(prefix, 'tar-destination') }),
})
await npm.exec('pack', [])
const filename = 'test-package-1.0.0.tgz'
t.strictSame(outputs, [filename])
t.ok(fs.statSync(path.resolve(npm.prefix, 'tar-destination', filename)))
})
t.test('should pack given directory for scoped package', async t => {
const { npm, outputs } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: '@npm/test-package',
version: '1.0.0',
}),
},
})
await npm.exec('pack', [])
const filename = 'npm-test-package-1.0.0.tgz'
t.strictSame(outputs, [filename])
t.ok(fs.statSync(path.resolve(npm.prefix, filename)))
})
t.test('should log output as valid json', async t => {
const { npm, outputs, logs } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'test-package',
version: '1.0.0',
}),
},
config: { json: true },
})
await npm.exec('pack', [])
const filename = 'test-package-1.0.0.tgz'
t.matchSnapshot(outputs.map(JSON.parse), 'outputs as json')
t.matchSnapshot(logs.notice, 'logs pack contents')
t.ok(fs.statSync(path.resolve(npm.prefix, filename)))
})
t.test('should log scoped package output as valid json', async t => {
const { npm, outputs, outputErrors, logs } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: '@myscope/test-package',
version: '1.0.0',
scripts: {
prepack: 'echo prepack!',
},
}),
},
config: {
json: true,
progress: false,
},
})
await npm.exec('pack', [])
const filename = 'myscope-test-package-1.0.0.tgz'
t.matchSnapshot(outputs.map(JSON.parse), 'outputs as json')
t.matchSnapshot(outputErrors, 'stderr has banners')
t.matchSnapshot(logs.notice, 'logs pack contents')
t.ok(fs.statSync(path.resolve(npm.prefix, filename)))
})
t.test('dry run', async t => {
const { npm, outputs, logs } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'test-package',
version: '1.0.0',
}),
},
config: { 'dry-run': true },
})
await npm.exec('pack', [])
const filename = 'test-package-1.0.0.tgz'
t.strictSame(outputs, [filename])
t.matchSnapshot(logs.notice, 'logs pack contents')
t.throws(() => fs.statSync(path.resolve(npm.prefix, filename)))
})
t.test('foreground-scripts defaults to true', async t => {
const { npm, outputs, logs } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'test-fg-scripts',
version: '0.0.0',
scripts: {
prepack: 'echo prepack!',
postpack: 'echo postpack!',
},
}
),
},
config: { 'dry-run': true },
})
await npm.exec('pack', [])
const filename = 'test-fg-scripts-0.0.0.tgz'
t.strictSame(
outputs,
[
'\n> test-fg-scripts@0.0.0 prepack\n> echo prepack!\n',
'\n> test-fg-scripts@0.0.0 postpack\n> echo postpack!\n',
filename,
],
'prepack and postpack log to stdout'
)
t.matchSnapshot(logs.notice, 'logs pack contents')
t.throws(() => fs.statSync(path.resolve(npm.prefix, filename)))
})
t.test('foreground-scripts can still be set to false', async t => {
const { npm, outputs, logs } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'test-fg-scripts',
version: '0.0.0',
scripts: {
prepack: 'echo prepack!',
postpack: 'echo postpack!',
},
}
),
},
config: { 'dry-run': true, 'foreground-scripts': false },
})
await npm.exec('pack', [])
const filename = 'test-fg-scripts-0.0.0.tgz'
t.strictSame(outputs, [filename], 'prepack and postpack do not log to stdout')
t.matchSnapshot(logs.notice, 'logs pack contents')
t.throws(() => fs.statSync(path.resolve(npm.prefix, filename)))
})
t.test('invalid packument', async t => {
const { npm, outputs } = await loadMockNpm(t, {
prefixDir: {
'package.json': '{}',
},
})
await t.rejects(
npm.exec('pack', []),
/Invalid package, must have name and version/
)
t.strictSame(outputs, [])
})
t.test('workspaces', async t => {
const loadWorkspaces = (t) => loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify(
{
name: 'workspaces-test',
version: '1.0.0',
workspaces: ['workspace-a', 'workspace-b'],
},
null,
2
),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.0.0',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '1.0.0',
}),
},
},
config: {
workspaces: true,
// TODO: this is a workaround for npm run test-all
// somehow leaking include-workspace-root
'include-workspace-root': false,
},
})
t.test('all workspaces', async t => {
const { npm, outputs } = await loadWorkspaces(t)
await npm.exec('pack', [])
t.strictSame(outputs, ['workspace-a-1.0.0.tgz', 'workspace-b-1.0.0.tgz'])
})
t.test('all workspaces, `.` first arg', async t => {
const { npm, outputs } = await loadWorkspaces(t)
await npm.exec('pack', ['.'])
t.strictSame(outputs, ['workspace-a-1.0.0.tgz', 'workspace-b-1.0.0.tgz'])
})
t.test('one workspace', async t => {
const { npm, outputs } = await loadWorkspaces(t)
await npm.exec('pack', ['workspace-a'])
t.strictSame(outputs, ['workspace-a-1.0.0.tgz'])
})
t.test('specific package', async t => {
const { npm, outputs } = await loadWorkspaces(t)
await npm.exec('pack', [npm.prefix])
t.strictSame(outputs, ['workspaces-test-1.0.0.tgz'])
})
})

94
deps/npm/test/lib/commands/ping.js vendored Normal file
View File

@ -0,0 +1,94 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const MockRegistry = require('@npmcli/mock-registry')
const cacache = require('cacache')
const path = require('node:path')
t.test('no details', async t => {
const { npm, logs, joinedOutput } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.ping()
await npm.exec('ping', [])
t.match(logs.notice, [
'PING https://registry.npmjs.org/',
/PONG [0-9]+ms/,
])
t.equal(joinedOutput(), '')
})
t.test('with details', async t => {
const { npm, logs, joinedOutput } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.ping({ body: { test: true, test2: true } })
await npm.exec('ping', [])
t.match(logs.notice, [
`PING https://registry.npmjs.org/`,
/PONG [0-9]+ms/,
`PONG {\nPONG "test": true,\nPONG "test2": true\nPONG }`,
])
t.match(joinedOutput(), '')
})
t.test('valid json', async t => {
const { npm, logs, joinedOutput } = await loadMockNpm(t, {
config: { json: true },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.ping()
await npm.exec('ping', [])
t.match(logs.notice, [
'PING https://registry.npmjs.org/',
/PONG [0-9]+ms/,
])
t.match(JSON.parse(joinedOutput()), {
registry: npm.config.get('registry'),
time: /[0-9]+/,
details: {},
})
})
t.test('invalid json', async t => {
const { npm, logs, joinedOutput } = await loadMockNpm(t, {
config: { json: true },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.ping({ body: '{not: real"json]' })
await npm.exec('ping', [])
t.match(logs.notice, [
'PING https://registry.npmjs.org/',
/PONG [0-9]+ms/,
])
t.match(JSON.parse(joinedOutput()), {
registry: npm.config.get('registry'),
time: /[0-9]+/,
details: {},
})
})
t.test('fail when registry is unreachable even if request is cached', async t => {
const { npm } = await loadMockNpm(t, {
config: { registry: 'https://ur.npmlocal.npmtest/' },
cacheDir: { _cacache: {} },
})
const url = `${npm.config.get('registry')}-/ping`
const cache = path.join(npm.cache, '_cacache')
await cacache.put(cache,
`make-fetch-happen:request-cache:${url}`,
'{}', { metadata: { url } }
)
t.rejects(npm.exec('ping', []), {
code: 'ENOTFOUND',
},
'throws ENOTFOUND error')
})

761
deps/npm/test/lib/commands/pkg.js vendored Normal file
View File

@ -0,0 +1,761 @@
const { resolve } = require('node:path')
const { readFileSync } = require('node:fs')
const t = require('tap')
const _mockNpm = require('../../fixtures/mock-npm')
const { cleanCwd } = require('../../fixtures/clean-snapshot')
t.cleanSnapshot = (str) => cleanCwd(str)
const mockNpm = async (t, { ...opts } = {}) => {
const res = await _mockNpm(t, {
...opts,
command: 'pkg',
})
const readPackageJson = (dir = '') =>
JSON.parse(readFileSync(resolve(res.prefix, dir, 'package.json'), 'utf8'))
return {
...res,
pkg: (...args) => res.npm.exec('pkg', args),
readPackageJson,
OUTPUT: () => res.joinedOutput(),
}
}
t.test('no args', async t => {
const { pkg } = await mockNpm(t)
await t.rejects(
pkg(),
{ code: 'EUSAGE' },
'should throw usage error'
)
})
t.test('no global mode', async t => {
const { pkg } = await mockNpm(t, {
config: { global: true },
})
await t.rejects(
pkg('get', 'foo'),
{ code: 'EPKGGLOBAL' },
'should throw no global mode error'
)
})
t.test('get no args', async t => {
const { pkg, OUTPUT } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.1.1',
}),
},
})
await pkg('get')
t.strictSame(
JSON.parse(OUTPUT()),
{
name: 'foo',
version: '1.1.1',
},
'should print package.json content'
)
})
t.test('get single arg', async t => {
const { pkg, OUTPUT } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.1.1',
}),
},
})
await pkg('get', 'version')
t.strictSame(
JSON.parse(OUTPUT()),
'1.1.1',
'should print retrieved package.json field'
)
})
t.test('get multiple arg', async t => {
const { pkg, OUTPUT } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.1.1',
}),
},
})
await pkg('get', 'name', 'version')
t.strictSame(
JSON.parse(OUTPUT()),
{
name: 'foo',
version: '1.1.1',
},
'should print retrieved package.json field'
)
})
t.test('get multiple arg with only one arg existing', async t => {
const { pkg, OUTPUT } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
}),
},
})
await pkg('get', 'name', 'version', 'dependencies')
t.strictSame(
JSON.parse(OUTPUT()),
{
name: 'foo',
},
'should print retrieved package.json field'
)
})
t.test('get multiple arg with empty value', async t => {
const { pkg, OUTPUT } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
author: '',
}),
},
})
await pkg('get', 'name', 'author')
t.strictSame(
JSON.parse(OUTPUT()),
{
name: 'foo',
author: '',
},
'should print retrieved package.json field regardless of empty value'
)
})
t.test('get nested arg', async t => {
const { pkg, OUTPUT } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.1.1',
scripts: {
test: 'node test.js',
},
}),
},
})
await pkg('get', 'scripts.test')
t.strictSame(
JSON.parse(OUTPUT()),
'node test.js',
'should print retrieved nested field'
)
})
t.test('get array field', async t => {
const files = [
'index.js',
'cli.js',
]
const { pkg, OUTPUT } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.1.1',
files,
}),
},
})
await pkg('get', 'files')
t.strictSame(
JSON.parse(OUTPUT()),
files,
'should print retrieved array field'
)
})
t.test('get array item', async t => {
const files = [
'index.js',
'cli.js',
]
const { pkg, OUTPUT } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.1.1',
files,
}),
},
})
await pkg('get', 'files[0]')
t.strictSame(
JSON.parse(OUTPUT()),
'index.js',
'should print retrieved array field'
)
})
t.test('get array nested items notation', async t => {
const contributors = [
{
name: 'Ruy',
url: 'http://example.com/ruy',
},
{
name: 'Gar',
url: 'http://example.com/gar',
},
]
const { pkg, OUTPUT } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.1.1',
contributors,
}),
},
})
await pkg('get', 'contributors.name')
t.strictSame(
JSON.parse(OUTPUT()),
{
'contributors[0].name': 'Ruy',
'contributors[1].name': 'Gar',
},
'should print json result containing matching results'
)
})
t.test('set no args', async t => {
const { pkg } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'foo' }),
},
})
await t.rejects(
pkg('set'),
{ code: 'EUSAGE' },
'should throw an error if no args'
)
})
t.test('set missing value', async t => {
const { pkg } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'foo' }),
},
})
await t.rejects(
pkg('set', 'key='),
{ code: 'EUSAGE' },
'should throw an error if missing value'
)
})
t.test('set missing key', async t => {
const { pkg } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'foo' }),
},
})
await t.rejects(
pkg('set', '=value'),
{ code: 'EUSAGE' },
'should throw an error if missing key'
)
})
t.test('set single field', async t => {
const json = {
name: 'foo',
version: '1.1.1',
}
const { pkg, readPackageJson } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify(json),
},
})
await pkg('set', 'description=Awesome stuff')
t.strictSame(
readPackageJson(),
{
...json,
description: 'Awesome stuff',
},
'should add single field to package.json'
)
})
t.test('push to array syntax', async t => {
const json = {
name: 'foo',
version: '1.1.1',
keywords: [
'foo',
],
}
const { pkg, readPackageJson } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify(json),
},
})
await pkg('set', 'keywords[]=bar', 'keywords[]=baz')
t.strictSame(
readPackageJson(),
{
...json,
keywords: [
'foo',
'bar',
'baz',
],
},
'should append to arrays using empty bracket syntax'
)
})
t.test('set multiple fields', async t => {
const json = {
name: 'foo',
version: '1.1.1',
}
const { pkg, readPackageJson } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify(json),
},
})
await pkg('set', 'bin.foo=foo.js', 'scripts.test=node test.js')
t.strictSame(
readPackageJson(),
{
...json,
bin: {
foo: 'foo.js',
},
scripts: {
test: 'node test.js',
},
},
'should add single field to package.json'
)
})
t.test('set = separate value', async t => {
const json = {
name: 'foo',
version: '1.1.1',
}
const { pkg, readPackageJson } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify(json),
},
})
await pkg('set', 'tap[test-env][0]=LC_ALL=sk')
t.strictSame(
readPackageJson(),
{
...json,
tap: {
'test-env': [
'LC_ALL=sk',
],
},
},
'should add single field to package.json'
)
})
t.test('set --json', async t => {
const { pkg, readPackageJson } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.1.1',
}),
},
config: { json: true },
})
await pkg('set', 'private=true')
t.strictSame(
readPackageJson(),
{
name: 'foo',
version: '1.1.1',
private: true,
},
'should add boolean field to package.json'
)
await pkg('set', 'tap.timeout=60')
t.strictSame(
readPackageJson(),
{
name: 'foo',
version: '1.1.1',
private: true,
tap: {
timeout: 60,
},
},
'should add number field to package.json'
)
await pkg('set', 'foo={ "bar": { "baz": "BAZ" } }')
t.strictSame(
readPackageJson(),
{
name: 'foo',
version: '1.1.1',
private: true,
tap: {
timeout: 60,
},
foo: {
bar: {
baz: 'BAZ',
},
},
},
'should add object field to package.json'
)
await pkg('set', 'workspaces=["packages/*"]')
t.strictSame(
readPackageJson(),
{
name: 'foo',
version: '1.1.1',
private: true,
workspaces: [
'packages/*',
],
tap: {
timeout: 60,
},
foo: {
bar: {
baz: 'BAZ',
},
},
},
'should add object field to package.json'
)
await pkg('set', 'description="awesome"')
t.strictSame(
readPackageJson(),
{
name: 'foo',
version: '1.1.1',
description: 'awesome',
private: true,
workspaces: [
'packages/*',
],
tap: {
timeout: 60,
},
foo: {
bar: {
baz: 'BAZ',
},
},
},
'should add object field to package.json'
)
})
t.test('delete no args', async t => {
const { pkg } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'foo' }),
},
})
await t.rejects(
pkg('delete'),
{ code: 'EUSAGE' },
'should throw an error if deleting no args'
)
})
t.test('delete invalid key', async t => {
const { pkg } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'foo' }),
},
})
await t.rejects(
pkg('delete', ''),
{ code: 'EUSAGE' },
'should throw an error if deleting invalid args'
)
})
t.test('delete single field', async t => {
const { pkg, readPackageJson } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
}),
},
})
await pkg('delete', 'version')
t.strictSame(
readPackageJson(),
{
name: 'foo',
},
'should delete single field from package.json'
)
})
t.test('delete multiple field', async t => {
const { pkg, readPackageJson } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
description: 'awesome',
}),
},
})
await pkg('delete', 'version', 'description')
t.strictSame(
readPackageJson(),
{
name: 'foo',
},
'should delete multiple fields from package.json'
)
})
t.test('delete nested field', async t => {
const { pkg, readPackageJson } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
info: {
foo: {
bar: [
{
baz: 'deleteme',
},
],
},
},
}),
},
})
await pkg('delete', 'info.foo.bar[0].baz')
t.strictSame(
readPackageJson(),
{
name: 'foo',
version: '1.0.0',
info: {
foo: {
bar: [
{},
],
},
},
},
'should delete nested fields from package.json'
)
})
t.test('workspaces', async t => {
const mockWorkspaces = (t) => mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
workspaces: [
'packages/*',
],
}),
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.2.3',
}),
},
},
},
config: { workspaces: true },
})
t.test('get', async t => {
const { pkg, OUTPUT } = await mockWorkspaces(t)
await pkg('get', 'name', 'version')
t.strictSame(
JSON.parse(OUTPUT()),
{
a: {
name: 'a',
version: '1.0.0',
},
b: {
name: 'b',
version: '1.2.3',
},
},
'should return expected result for configured workspaces'
)
})
t.test('set', async t => {
const { pkg, readPackageJson } = await mockWorkspaces(t)
await pkg('set', 'funding=http://example.com')
t.strictSame(
readPackageJson('packages/a'),
{
name: 'a',
version: '1.0.0',
funding: 'http://example.com',
},
'should add field to workspace a'
)
t.strictSame(
readPackageJson('packages/b'),
{
name: 'b',
version: '1.2.3',
funding: 'http://example.com',
},
'should add field to workspace b'
)
await pkg('delete', 'version')
t.strictSame(
readPackageJson('packages/a'),
{
name: 'a',
funding: 'http://example.com',
},
'should delete version field from workspace a'
)
t.strictSame(
readPackageJson('packages/b'),
{
name: 'b',
funding: 'http://example.com',
},
'should delete version field from workspace b'
)
})
})
t.test('single workspace', async t => {
const mockWorkspace = (t) => mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
workspaces: [
'packages/*',
],
}),
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.2.3',
}),
},
},
},
config: { workspace: ['packages/a'] },
})
t.test('multiple args', async t => {
const { pkg, OUTPUT } = await mockWorkspace(t)
await pkg('get', 'name', 'version')
t.strictSame(
JSON.parse(OUTPUT()),
{ a: { name: 'a', version: '1.0.0' } },
'should only return info for one workspace'
)
})
t.test('single arg', async t => {
const { pkg, OUTPUT } = await mockWorkspace(t)
await pkg('get', 'version')
t.strictSame(
JSON.parse(OUTPUT()),
{ a: '1.0.0' },
'should only return info for one workspace'
)
})
})
t.test('fix', async t => {
const { pkg, readPackageJson } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo ',
version: 'v1.1.1',
}),
},
})
await pkg('fix')
t.strictSame(
readPackageJson(),
{ name: 'foo', version: '1.1.1' },
'fixes package.json issues'
)
})

12
deps/npm/test/lib/commands/prefix.js vendored Normal file
View File

@ -0,0 +1,12 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
t.test('prefix', async t => {
const { joinedOutput, npm } = await loadMockNpm(t)
await npm.exec('prefix', [])
t.equal(
joinedOutput(),
npm.prefix,
'outputs npm.prefix'
)
})

1103
deps/npm/test/lib/commands/profile.js vendored Normal file

File diff suppressed because it is too large Load Diff

21
deps/npm/test/lib/commands/prune.js vendored Normal file
View File

@ -0,0 +1,21 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
t.test('should prune using Arborist', async (t) => {
t.plan(4)
const { npm } = await loadMockNpm(t, {
mocks: {
'@npmcli/arborist': function (args) {
t.ok(args, 'gets options object')
t.ok(args.path, 'gets path option')
this.prune = () => {
t.ok(true, 'prune is called')
}
},
'{LIB}/utils/reify-finish.js': (arb) => {
t.ok(arb, 'gets arborist tree')
},
},
})
await npm.exec('prune', [])
})

990
deps/npm/test/lib/commands/publish.js vendored Normal file
View File

@ -0,0 +1,990 @@
const t = require('tap')
const { loadNpmWithRegistry } = require('../../fixtures/mock-npm')
const { cleanZlib } = require('../../fixtures/clean-snapshot')
const pacote = require('pacote')
const Arborist = require('@npmcli/arborist')
const path = require('node:path')
const fs = require('node:fs')
const pkg = '@npmcli/test-package'
const token = 'test-auth-token'
const auth = { '//registry.npmjs.org/:_authToken': token }
const alternateRegistry = 'https://other.registry.npmjs.org'
const basic = Buffer.from('test-user:test-password').toString('base64')
const pkgJson = {
name: pkg,
description: 'npm test package',
version: '1.0.0',
}
t.cleanSnapshot = data => cleanZlib(data)
t.test('respects publishConfig.registry, runs appropriate scripts', async t => {
const packageJson = {
...pkgJson,
scripts: {
prepublishOnly: 'touch scripts-prepublishonly',
prepublish: 'touch scripts-prepublish', // should NOT run this one
publish: 'touch scripts-publish',
postpublish: 'touch scripts-postpublish',
},
publishConfig: {
other: 'not defined',
registry: alternateRegistry,
},
}
const { npm, joinedOutput, logs, prefix, registry } = await loadNpmWithRegistry(t, {
config: {
loglevel: 'warn',
[`${alternateRegistry.slice(6)}/:_authToken`]: 'test-other-token',
},
prefixDir: {
'package.json': JSON.stringify(packageJson, null, 2),
},
registry: alternateRegistry,
authorization: 'test-other-token',
})
registry.publish(pkg, { packageJson })
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'new package version')
t.equal(fs.existsSync(path.join(prefix, 'scripts-prepublishonly')), true, 'ran prepublishOnly')
t.equal(fs.existsSync(path.join(prefix, 'scripts-prepublish')), false, 'did not run prepublish')
t.equal(fs.existsSync(path.join(prefix, 'scripts-publish')), true, 'ran publish')
t.equal(fs.existsSync(path.join(prefix, 'scripts-postpublish')), true, 'ran postpublish')
t.same(logs.warn, ['Unknown publishConfig config "other". This will stop working in the next major version of npm.'])
})
t.test('re-loads publishConfig.registry if added during script process', async t => {
const initPackageJson = {
...pkgJson,
scripts: {
prepare: 'cp new.json package.json',
},
}
const packageJson = {
...initPackageJson,
publishConfig: { registry: alternateRegistry },
}
const { joinedOutput, npm, registry } = await loadNpmWithRegistry(t, {
config: {
[`${alternateRegistry.slice(6)}/:_authToken`]: 'test-other-token',
// Keep output from leaking into tap logs for readability
'foreground-scripts': false,
},
prefixDir: {
'package.json': JSON.stringify(initPackageJson, null, 2),
'new.json': JSON.stringify(packageJson, null, 2),
},
registry: alternateRegistry,
authorization: 'test-other-token',
})
registry.publish(pkg, { packageJson })
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'new package version')
})
t.test('prioritize CLI flags over publishConfig', async t => {
const initPackageJson = {
...pkgJson,
scripts: {
prepare: 'cp new.json package.json',
},
}
const packageJson = {
...initPackageJson,
publishConfig: { registry: alternateRegistry },
}
const { joinedOutput, npm, registry } = await loadNpmWithRegistry(t, {
config: {
[`${alternateRegistry.slice(6)}/:_authToken`]: 'test-other-token',
// Keep output from leaking into tap logs for readability
'foreground-scripts': false,
},
prefixDir: {
'package.json': JSON.stringify(initPackageJson, null, 2),
'new.json': JSON.stringify(packageJson, null, 2),
},
argv: ['--registry', alternateRegistry],
registryUrl: alternateRegistry,
authorization: 'test-other-token',
})
registry.publish(pkg, { packageJson })
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'new package version')
})
t.test('json', async t => {
const { joinedOutput, npm, logs, registry } = await loadNpmWithRegistry(t, {
config: {
json: true,
...auth,
},
prefixDir: {
'package.json': JSON.stringify(pkgJson, null, 2),
},
authorization: token,
})
registry.publish(pkg)
await npm.exec('publish', [])
t.matchSnapshot(logs.notice)
t.matchSnapshot(joinedOutput(), 'new package json')
})
t.test('dry-run', async t => {
const { joinedOutput, npm, logs, registry } = await loadNpmWithRegistry(t, {
config: {
'dry-run': true,
...auth,
},
prefixDir: {
'package.json': JSON.stringify(pkgJson, null, 2),
},
authorization: token,
})
registry.publish(pkg, { noPut: true })
await npm.exec('publish', [])
t.equal(joinedOutput(), `+ ${pkg}@1.0.0`)
t.matchSnapshot(logs.notice)
})
t.test('foreground-scripts defaults to true', async t => {
const { outputs, npm, logs, registry } = await loadNpmWithRegistry(t, {
config: {
'dry-run': true,
...auth,
},
prefixDir: {
'package.json': JSON.stringify({
name: 'test-fg-scripts',
version: '0.0.0',
scripts: {
prepack: 'echo prepack!',
postpack: 'echo postpack!',
},
}
),
},
})
registry.publish('test-fg-scripts', { noPut: true })
await npm.exec('publish', [])
t.matchSnapshot(logs.notice)
t.strictSame(
outputs,
[
'\n> test-fg-scripts@0.0.0 prepack\n> echo prepack!\n',
'\n> test-fg-scripts@0.0.0 postpack\n> echo postpack!\n',
`+ test-fg-scripts@0.0.0`,
],
'prepack and postpack log to stdout')
})
t.test('foreground-scripts can still be set to false', async t => {
const { outputs, npm, logs, registry } = await loadNpmWithRegistry(t, {
config: {
'dry-run': true,
'foreground-scripts': false,
...auth,
},
prefixDir: {
'package.json': JSON.stringify({
name: 'test-fg-scripts',
version: '0.0.0',
scripts: {
prepack: 'echo prepack!',
postpack: 'echo postpack!',
},
}
),
},
})
registry.publish('test-fg-scripts', { noPut: true })
await npm.exec('publish', [])
t.matchSnapshot(logs.notice)
t.strictSame(
outputs,
[`+ test-fg-scripts@0.0.0`],
'prepack and postpack do not log to stdout')
})
t.test('shows usage with wrong set of arguments', async t => {
const { publish } = await loadNpmWithRegistry(t, { command: 'publish' })
await t.rejects(publish.exec(['a', 'b', 'c']), publish.usage)
})
t.test('throws when invalid tag is semver', async t => {
const { npm } = await loadNpmWithRegistry(t, {
config: {
tag: '0.0.13',
},
prefixDir: {
'package.json': JSON.stringify(pkgJson, null, 2),
},
})
await t.rejects(
npm.exec('publish', []),
{ message: 'Tag name must not be a valid SemVer range: 0.0.13' }
)
})
t.test('throws when invalid tag when not url encodable', async t => {
const { npm } = await loadNpmWithRegistry(t, {
config: {
tag: '@test',
},
prefixDir: {
'package.json': JSON.stringify(pkgJson, null, 2),
},
})
await t.rejects(
npm.exec('publish', []),
{
message: `Invalid tag name "@test" of package "${pkg}@@test": Tags may not have any characters that encodeURIComponent encodes.`,
}
)
})
t.test('tarball', async t => {
const { npm, joinedOutput, logs, home, registry } = await loadNpmWithRegistry(t, {
config: {
'fetch-retries': 0,
...auth,
},
homeDir: {
'package.json': JSON.stringify({
name: 'test-tar-package',
description: 'this was from a tarball',
version: '1.0.0',
}, null, 2),
'index.js': 'console.log("hello world"}',
},
authorization: token,
})
const tarball = await pacote.tarball(home, { Arborist })
const tarFilename = path.join(home, 'tarball.tgz')
fs.writeFileSync(tarFilename, tarball)
registry.publish('test-tar-package')
await npm.exec('publish', [tarFilename])
t.matchSnapshot(logs.notice)
t.matchSnapshot(joinedOutput(), 'new package json')
})
t.test('no auth default registry', async t => {
const { npm } = await loadNpmWithRegistry(t, {
prefixDir: {
'package.json': JSON.stringify(pkgJson, null, 2),
},
})
await t.rejects(
npm.exec('publish', []),
{
message: 'This command requires you to be logged in to https://registry.npmjs.org/',
code: 'ENEEDAUTH',
}
)
})
t.test('no auth dry-run', async t => {
const { npm, joinedOutput, logs, registry } = await loadNpmWithRegistry(t, {
config: {
'dry-run': true,
},
prefixDir: {
'package.json': JSON.stringify(pkgJson, null, 2),
},
})
registry.publish(pkg, { noPut: true })
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput())
t.matchSnapshot(logs.warn, 'warns about auth being needed')
})
t.test('no auth for configured registry', async t => {
const { npm } = await loadNpmWithRegistry(t, {
config: {
registry: alternateRegistry,
...auth,
},
prefixDir: {
'package.json': JSON.stringify(pkgJson, null, 2),
},
})
await t.rejects(
npm.exec('publish', []),
{
message: `This command requires you to be logged in to ${alternateRegistry}`,
code: 'ENEEDAUTH',
}
)
})
t.test('no auth for scope configured registry', async t => {
const { npm } = await loadNpmWithRegistry(t, {
config: {
scope: '@npm',
registry: alternateRegistry,
...auth,
},
prefixDir: {
'package.json': JSON.stringify({
name: '@npm/test-package',
version: '1.0.0',
}, null, 2),
},
})
await t.rejects(
npm.exec('publish', []),
{
message: `This command requires you to be logged in to ${alternateRegistry}`,
code: 'ENEEDAUTH',
}
)
})
t.test('has token auth for scope configured registry', async t => {
const { npm, joinedOutput, registry } = await loadNpmWithRegistry(t, {
config: {
scope: '@npm',
registry: alternateRegistry,
[`${alternateRegistry.slice(6)}/:_authToken`]: 'test-scope-token',
},
prefixDir: {
'package.json': JSON.stringify({
name: '@npm/test-package',
version: '1.0.0',
}, null, 2),
},
registry: alternateRegistry,
authorization: 'test-scope-token',
})
registry.publish('@npm/test-package')
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'new package version')
})
t.test('has mTLS auth for scope configured registry', async t => {
const { npm, joinedOutput, registry } = await loadNpmWithRegistry(t, {
config: {
scope: '@npm',
registry: alternateRegistry,
[`${alternateRegistry.slice(6)}/:certfile`]: '/some.cert',
[`${alternateRegistry.slice(6)}/:keyfile`]: '/some.key',
},
prefixDir: {
'package.json': JSON.stringify({
name: '@npm/test-package',
version: '1.0.0',
}, null, 2),
},
registry: alternateRegistry,
})
registry.publish('@npm/test-package')
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'new package version')
})
t.test('workspaces', t => {
const dir = {
'package.json': JSON.stringify(
{
...pkgJson,
workspaces: ['workspace-a', 'workspace-b', 'workspace-c', 'workspace-p'],
}, null, 2),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.2.3-a',
repository: 'http://repo.workspace-a/',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '1.2.3-n',
repository: 'https://github.com/npm/workspace-b',
}),
},
'workspace-c': {
'package.json': JSON.stringify({
name: 'workspace-n',
version: '1.2.3-n',
}),
},
'workspace-p': {
'package.json': JSON.stringify({
name: 'workspace-p',
version: '1.2.3-p',
private: true,
}),
},
}
t.test('all workspaces - no color', async t => {
const { npm, joinedOutput, logs, registry } = await loadNpmWithRegistry(t, {
config: {
tag: 'latest',
color: false,
...auth,
workspaces: true,
},
prefixDir: dir,
authorization: token,
})
;['workspace-a', 'workspace-b', 'workspace-n'].forEach(name => {
registry.publish(name)
})
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'all public workspaces')
t.matchSnapshot(logs.warn, 'warns about skipped private workspace')
})
t.test('all workspaces - color', async t => {
const { npm, joinedOutput, logs, registry } = await loadNpmWithRegistry(t, {
config: {
...auth,
tag: 'latest',
color: 'always',
workspaces: true,
},
prefixDir: dir,
authorization: token,
})
;['workspace-a', 'workspace-b', 'workspace-n'].forEach(name => {
registry.publish(name)
})
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'all public workspaces')
t.matchSnapshot(logs.warn, 'warns about skipped private workspace in color')
})
t.test('one workspace - success', async t => {
const { npm, joinedOutput, registry } = await loadNpmWithRegistry(t, {
config: {
...auth,
tag: 'latest',
workspace: ['workspace-a'],
},
prefixDir: dir,
authorization: token,
})
;['workspace-a'].forEach(name => {
registry.publish(name)
})
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'single workspace')
})
t.test('one workspace - failure', async t => {
const { npm, registry } = await loadNpmWithRegistry(t, {
config: {
...auth,
tag: 'latest',
workspace: ['workspace-a'],
},
prefixDir: dir,
authorization: token,
})
registry.publish('workspace-a', { putCode: 404 })
await t.rejects(npm.exec('publish', []), { code: 'E404' })
})
t.test('all workspaces - some marked private', async t => {
const testDir = {
'package.json': JSON.stringify(
{
...pkgJson,
workspaces: ['workspace-a', 'workspace-p'],
}, null, 2),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.2.3-a',
}),
},
'workspace-p': {
'package.json': JSON.stringify({
name: '@scoped/workspace-p',
private: true,
version: '1.2.3-p-scoped',
}),
},
}
const { npm, joinedOutput, registry } = await loadNpmWithRegistry(t, {
config: {
...auth,
tag: 'latest',
workspaces: true,
},
prefixDir: testDir,
authorization: token,
})
registry.publish('workspace-a')
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'one marked private')
})
t.test('invalid workspace', async t => {
const { npm } = await loadNpmWithRegistry(t, {
config: {
...auth,
tag: 'latest',
workspace: ['workspace-x'],
},
prefixDir: dir,
})
await t.rejects(
npm.exec('publish', []),
{ message: 'No workspaces found:\n --workspace=workspace-x' }
)
})
t.test('json', async t => {
const { npm, joinedOutput, registry } = await loadNpmWithRegistry(t, {
config: {
...auth,
tag: 'latest',
workspaces: true,
json: true,
},
prefixDir: dir,
authorization: token,
})
;['workspace-a', 'workspace-b', 'workspace-n'].forEach(name => {
registry.publish(name)
})
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'all workspaces in json')
})
t.test('differet package spec', async t => {
const testDir = {
'package.json': JSON.stringify(
{
...pkgJson,
workspaces: ['workspace-a'],
}, null, 2),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.2.3-a',
}),
},
'dir/pkg': {
'package.json': JSON.stringify({
name: 'pkg',
version: '1.2.3',
}),
},
}
const { npm, joinedOutput, registry } = await loadNpmWithRegistry(t, {
config: {
...auth,
tag: 'latest',
},
prefixDir: testDir,
chdir: ({ prefix }) => path.resolve(prefix, './workspace-a'),
authorization: token,
})
registry.publish('pkg')
await npm.exec('publish', ['../dir/pkg'])
t.matchSnapshot(joinedOutput(), 'publish different package spec')
})
t.end()
})
t.test('ignore-scripts', async t => {
const { npm, joinedOutput, prefix, registry } = await loadNpmWithRegistry(t, {
config: {
...auth,
'ignore-scripts': true,
},
prefixDir: {
'package.json': JSON.stringify({
...pkgJson,
scripts: {
prepublishOnly: 'touch scripts-prepublishonly',
prepublish: 'touch scripts-prepublish', // should NOT run this one
publish: 'touch scripts-publish',
postpublish: 'touch scripts-postpublish',
},
}, null, 2),
},
authorization: token,
})
registry.publish(pkg)
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'new package version')
t.equal(
fs.existsSync(path.join(prefix, 'scripts-prepublishonly')),
false,
'did not run prepublishOnly'
)
t.equal(
fs.existsSync(path.join(prefix, 'scripts-prepublish')),
false,
'did not run prepublish'
)
t.equal(
fs.existsSync(path.join(prefix, 'scripts-publish')),
false,
'did not run publish'
)
t.equal(
fs.existsSync(path.join(prefix, 'scripts-postpublish')),
false,
'did not run postpublish'
)
})
t.test('_auth config default registry', async t => {
const { npm, joinedOutput, registry } = await loadNpmWithRegistry(t, {
config: {
'//registry.npmjs.org/:_auth': basic,
},
prefixDir: {
'package.json': JSON.stringify(pkgJson),
},
basic,
})
registry.publish(pkg)
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'new package version')
})
t.test('bare _auth and registry config', async t => {
const { npm, joinedOutput, registry } = await loadNpmWithRegistry(t, {
config: {
registry: alternateRegistry,
'//other.registry.npmjs.org/:_auth': basic,
},
prefixDir: {
'package.json': JSON.stringify({
name: '@npm/test-package',
version: '1.0.0',
}, null, 2),
},
registry: alternateRegistry,
basic,
})
registry.publish('@npm/test-package')
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'new package version')
})
t.test('bare _auth config scoped registry', async t => {
const { npm } = await loadNpmWithRegistry(t, {
config: {
scope: '@npm',
registry: alternateRegistry,
_auth: basic,
},
prefixDir: {
'package.json': JSON.stringify({
name: '@npm/test-package',
version: '1.0.0',
}, null, 2),
},
})
await t.rejects(
npm.exec('publish', []),
{ message: `This command requires you to be logged in to ${alternateRegistry}` }
)
})
t.test('scoped _auth config scoped registry', async t => {
const { npm, joinedOutput, registry } = await loadNpmWithRegistry(t, {
config: {
scope: '@npm',
registry: alternateRegistry,
[`${alternateRegistry.slice(6)}/:_auth`]: basic,
},
prefixDir: {
'package.json': JSON.stringify({
name: '@npm/test-package',
version: '1.0.0',
}, null, 2),
},
registry: alternateRegistry,
basic,
})
registry.publish('@npm/test-package')
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'new package version')
})
t.test('restricted access', async t => {
const packageJson = {
name: '@npm/test-package',
version: '1.0.0',
}
const { npm, joinedOutput, logs, registry } = await loadNpmWithRegistry(t, {
config: {
...auth,
access: 'restricted',
},
prefixDir: {
'package.json': JSON.stringify(packageJson, null, 2),
},
authorization: token,
})
registry.publish('@npm/test-package', { packageJson, access: 'restricted' })
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'new package version')
t.matchSnapshot(logs.notice)
})
t.test('public access', async t => {
const { npm, joinedOutput, logs, registry } = await loadNpmWithRegistry(t, {
config: {
...auth,
access: 'public',
},
prefixDir: {
'package.json': JSON.stringify({
name: '@npm/test-package',
version: '1.0.0',
}, null, 2),
},
authorization: token,
})
registry.publish('@npm/test-package', { access: 'public' })
await npm.exec('publish', [])
t.matchSnapshot(joinedOutput(), 'new package version')
t.matchSnapshot(logs.notice)
})
t.test('manifest', async t => {
// https://github.com/npm/cli/pull/6470#issuecomment-1571234863
// snapshot test that was generated against v9.6.7 originally to ensure our
// own manifest does not change unexpectedly when publishing. this test
// asserts a bunch of keys are there that will change often and then snapshots
// the rest of the manifest.
const root = path.resolve(__dirname, '../../..')
const npmPkg = require(path.join(root, 'package.json'))
t.cleanSnapshot = (s) => s.replace(new RegExp(npmPkg.version, 'g'), '{VERSION}')
let manifest = null
const { npm, registry } = await loadNpmWithRegistry(t, {
config: {
...auth,
tag: 'latest',
'foreground-scripts': false,
},
chdir: () => root,
mocks: {
libnpmpublish: {
publish: (m) => manifest = m,
},
},
})
registry.publish('npm', { noPut: true })
await npm.exec('publish', [])
const okKeys = [
'contributors',
'bundleDependencies',
'dependencies',
'devDependencies',
'templateOSS',
'scripts',
'tap',
'readme',
'engines',
'workspaces',
]
for (const k of okKeys) {
t.ok(manifest[k], k)
delete manifest[k]
}
delete manifest.gitHead
manifest.man.sort()
t.matchSnapshot(manifest, 'manifest')
})
t.test('prerelease dist tag', (t) => {
t.test('aborts when prerelease and no tag', async t => {
const { npm } = await loadNpmWithRegistry(t, {
config: {
loglevel: 'silent',
[`${alternateRegistry.slice(6)}/:_authToken`]: 'test-other-token',
},
prefixDir: {
'package.json': JSON.stringify({
...pkgJson,
version: '1.0.0-0',
publishConfig: { registry: alternateRegistry },
}, null, 2),
},
})
await t.rejects(async () => {
await npm.exec('publish', [])
}, new Error('You must specify a tag using --tag when publishing a prerelease version'))
})
t.test('does not abort when prerelease and authored tag latest', async t => {
const packageJson = {
...pkgJson,
version: '1.0.0-0',
publishConfig: { registry: alternateRegistry },
}
const { npm, registry } = await loadNpmWithRegistry(t, {
config: {
loglevel: 'silent',
tag: 'latest',
[`${alternateRegistry.slice(6)}/:_authToken`]: 'test-other-token',
},
prefixDir: {
'package.json': JSON.stringify(packageJson, null, 2),
},
registry: alternateRegistry,
authorization: 'test-other-token',
})
registry.publish(pkg, { packageJson })
await npm.exec('publish', [])
})
t.test('does not abort when prerelease and force', async t => {
const packageJson = {
...pkgJson,
version: '1.0.0-0',
publishConfig: { registry: alternateRegistry },
}
const { npm, registry } = await loadNpmWithRegistry(t, {
config: {
loglevel: 'silent',
force: true,
[`${alternateRegistry.slice(6)}/:_authToken`]: 'test-other-token',
},
prefixDir: {
'package.json': JSON.stringify(packageJson, null, 2),
},
registry: alternateRegistry,
authorization: 'test-other-token',
})
registry.publish(pkg, { noGet: true, packageJson })
await npm.exec('publish', [])
})
t.end()
})
t.test('semver highest dist tag', async t => {
const init = ({ version, pkgExtra = {} }) => ({
config: {
loglevel: 'silent',
...auth,
},
prefixDir: {
'package.json': JSON.stringify({
...pkgJson,
...pkgExtra,
version,
}, null, 2),
},
authorization: token,
})
const packuments = [
// this needs more than one item in it to cover the sort logic
{ version: '50.0.0' },
{ version: '100.0.0' },
{ version: '102.0.0', deprecated: 'oops' },
{ version: '105.0.0-pre' },
]
await t.test('PREVENTS publish when highest version is HIGHER than publishing version', async t => {
const version = '99.0.0'
const { npm, registry } = await loadNpmWithRegistry(t, init({ version }))
registry.publish(pkg, { noPut: true, packuments })
await t.rejects(async () => {
await npm.exec('publish', [])
}, new Error('Cannot implicitly apply the "latest" tag because previously published version 100.0.0 is higher than the new version 99.0.0. You must specify a tag using --tag.'))
})
await t.test('ALLOWS publish when highest is HIGHER than publishing version and flag', async t => {
const version = '99.0.0'
const { npm, registry } = await loadNpmWithRegistry(t, {
...init({ version }),
argv: ['--tag', 'latest'],
})
registry.publish(pkg, { packuments })
await npm.exec('publish', [])
})
await t.test('ALLOWS publish when highest versions are LOWER than publishing version', async t => {
const version = '101.0.0'
const { npm, registry } = await loadNpmWithRegistry(t, init({ version }))
registry.publish(pkg, { packuments })
await npm.exec('publish', [])
})
await t.test('ALLOWS publish when packument has empty versions (for coverage)', async t => {
const version = '1.0.0'
const { npm, registry } = await loadNpmWithRegistry(t, init({ version }))
registry.publish(pkg, { manifest: { versions: { } } })
await npm.exec('publish', [])
})
await t.test('ALLOWS publish when packument has empty manifest (for coverage)', async t => {
const version = '1.0.0'
const { npm, registry } = await loadNpmWithRegistry(t, init({ version }))
registry.publish(pkg, { manifest: {} })
await npm.exec('publish', [])
})
await t.test('ALLOWS publish when highest version is HIGHER than publishing version with publishConfig', async t => {
const version = '99.0.0'
const { npm, registry } = await loadNpmWithRegistry(t, init({
version,
pkgExtra: {
publishConfig: {
tag: 'next',
},
},
}))
registry.publish(pkg, { packuments })
await npm.exec('publish', [])
})
await t.test('PREVENTS publish when latest version is SAME AS publishing version', async t => {
const version = '100.0.0'
const { npm, registry } = await loadNpmWithRegistry(t, init({ version }))
registry.publish(pkg, { noPut: true, packuments })
await t.rejects(async () => {
await npm.exec('publish', [])
}, new Error('You cannot publish over the previously published versions: 100.0.0.'))
})
await t.test('PREVENTS publish when publishing version EXISTS ALREADY in the registry', async t => {
const version = '50.0.0'
const { npm, registry } = await loadNpmWithRegistry(t, init({ version }))
registry.publish(pkg, { noPut: true, packuments })
await t.rejects(async () => {
await npm.exec('publish', [])
}, new Error('You cannot publish over the previously published versions: 50.0.0.'))
})
await t.test('ALLOWS publish when latest is HIGHER than publishing version and flag --force', async t => {
const version = '99.0.0'
const { npm, registry } = await loadNpmWithRegistry(t, {
...init({ version }),
argv: ['--force'],
})
registry.publish(pkg, { noGet: true, packuments })
await npm.exec('publish', [])
})
})

344
deps/npm/test/lib/commands/query.js vendored Normal file
View File

@ -0,0 +1,344 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const { cleanCwd } = require('../../fixtures/clean-snapshot.js')
t.cleanSnapshot = (str) => cleanCwd(str)
t.test('simple query', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
node_modules: {
a: {
name: 'a',
version: '1.0.0',
},
b: {
name: 'b',
version: '^2.0.0',
},
},
'package.json': JSON.stringify({
name: 'project',
dependencies: {
a: '^1.0.0',
b: '^1.0.0',
},
peerDependencies: {
c: '1.0.0',
},
}),
},
})
await npm.exec('query', [':root, :root > *'])
t.matchSnapshot(joinedOutput(), 'should return root object and direct children')
})
t.test('recursive tree', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
node_modules: {
a: {
name: 'a',
version: '1.0.0',
},
b: {
name: 'b',
version: '^2.0.0',
dependencies: {
a: '1.0.0',
},
},
},
'package.json': JSON.stringify({
name: 'project',
dependencies: {
a: '^1.0.0',
b: '^1.0.0',
},
}),
},
})
await npm.exec('query', ['*'])
t.matchSnapshot(joinedOutput(), 'should return everything in the tree, accounting for recursion')
})
t.test('workspace query', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
workspace: ['c'],
},
prefixDir: {
node_modules: {
a: {
name: 'a',
version: '1.0.0',
},
b: {
name: 'b',
version: '^2.0.0',
},
c: t.fixture('symlink', '../c'),
},
c: {
'package.json': JSON.stringify({
name: 'c',
version: '1.0.0',
}),
},
'package.json': JSON.stringify({
name: 'project',
workspaces: ['c'],
dependencies: {
a: '^1.0.0',
b: '^1.0.0',
},
}),
},
})
await npm.exec('query', [':scope'])
t.matchSnapshot(joinedOutput(), 'should return workspace object')
})
t.test('include-workspace-root', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
'include-workspace-root': true,
workspace: ['c'],
},
prefixDir: {
node_modules: {
a: {
name: 'a',
version: '1.0.0',
},
b: {
name: 'b',
version: '^2.0.0',
},
c: t.fixture('symlink', '../c'),
},
c: {
'package.json': JSON.stringify({
name: 'c',
version: '1.0.0',
}),
},
'package.json': JSON.stringify({
name: 'project',
workspaces: ['c'],
dependencies: {
a: '^1.0.0',
b: '^1.0.0',
},
}),
},
})
await npm.exec('query', [':scope'])
t.matchSnapshot(joinedOutput(), 'should return workspace object and root object')
})
t.test('linked node', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
node_modules: {
a: t.fixture('symlink', '../a'),
},
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
'package.json': JSON.stringify({
name: 'project',
dependencies: {
a: 'file:./a',
},
}),
},
})
await npm.exec('query', ['[name=a]'])
t.matchSnapshot(joinedOutput(), 'should return linked node res')
})
t.test('global', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
global: true,
},
globalPrefixDir: {
node_modules: {
lorem: {
'package.json': JSON.stringify({
name: 'lorem',
version: '2.0.0',
}),
},
},
},
})
await npm.exec('query', ['[name=lorem]'])
t.matchSnapshot(joinedOutput(), 'should return global package')
})
t.test('package-lock-only', t => {
t.test('no package lock', async t => {
const { npm } = await loadMockNpm(t, {
config: {
'package-lock-only': true,
},
prefixDir: {
'package.json': JSON.stringify({
name: 'project',
dependencies: {
a: '^1.0.0',
},
}),
},
})
await t.rejects(npm.exec('query', [':root, :root > *']), { code: 'EUSAGE' })
})
t.test('with package lock', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
'package-lock-only': true,
},
prefixDir: {
'package.json': JSON.stringify({
name: 'project',
dependencies: {
a: '^1.0.0',
},
}),
'package-lock.json': JSON.stringify({
name: 'project',
lockfileVersion: 3,
requires: true,
packages: {
'': {
dependencies: {
a: '^1.0.0',
},
},
'node_modules/a': {
version: '1.2.3',
resolved: 'https://dummy.npmjs.org/a/-/a-1.2.3.tgz',
integrity: 'sha512-dummy',
engines: {
node: '>=14.17',
},
},
},
}),
},
})
await npm.exec('query', ['*'])
t.matchSnapshot(joinedOutput(), 'should return valid response with only lock info')
})
t.end()
})
t.test('expect entries', t => {
const { exitCode } = process
t.afterEach(() => process.exitCode = exitCode)
const prefixDir = {
node_modules: {
a: { name: 'a', version: '1.0.0' },
},
'package.json': JSON.stringify({
name: 'project',
dependencies: { a: '^1.0.0' },
}),
}
t.test('false, has entries', async t => {
const { logs, npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-results', false)
await npm.exec('query', ['#a'])
t.not(joinedOutput(), '[]', 'has entries')
t.same(logs.warn.byTitle('query'), ['query Expected no results, got 1'])
t.ok(process.exitCode, 'exits with code')
})
t.test('false, no entries', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-results', false)
await npm.exec('query', ['#b'])
t.equal(joinedOutput(), '[]', 'does not have entries')
t.notOk(process.exitCode, 'exits without code')
})
t.test('true, has entries', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-results', true)
await npm.exec('query', ['#a'])
t.not(joinedOutput(), '[]', 'has entries')
t.notOk(process.exitCode, 'exits without code')
})
t.test('true, no entries', async t => {
const { logs, npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-results', true)
await npm.exec('query', ['#b'])
t.equal(joinedOutput(), '[]', 'does not have entries')
t.same(logs.warn.byTitle('query'), ['query Expected results, got 0'])
t.ok(process.exitCode, 'exits with code')
})
t.test('count, matches', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-result-count', 1)
await npm.exec('query', ['#a'])
t.not(joinedOutput(), '[]', 'has entries')
t.notOk(process.exitCode, 'exits without code')
})
t.test('count 1, does not match', async t => {
const { logs, npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-result-count', 1)
await npm.exec('query', ['#b'])
t.equal(joinedOutput(), '[]', 'does not have entries')
t.same(logs.warn.byTitle('query'), ['query Expected 1 result, got 0'])
t.ok(process.exitCode, 'exits with code')
})
t.test('count 3, does not match', async t => {
const { logs, npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-result-count', 3)
await npm.exec('query', ['#b'])
t.equal(joinedOutput(), '[]', 'does not have entries')
t.same(logs.warn.byTitle('query'), ['query Expected 3 results, got 0'])
t.ok(process.exitCode, 'exits with code')
})
t.end()
})
t.test('missing', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
node_modules: {
a: {
name: 'a',
version: '1.0.0',
},
},
'package.json': JSON.stringify({
name: 'project',
dependencies: {
a: '^1.0.0',
b: '^1.0.0',
},
}),
},
})
await npm.exec('query', [':missing'])
t.matchSnapshot(joinedOutput(), 'should return missing node')
})

207
deps/npm/test/lib/commands/rebuild.js vendored Normal file
View File

@ -0,0 +1,207 @@
const t = require('tap')
const fs = require('node:fs')
const { resolve } = require('node:path')
const setupMockNpm = require('../../fixtures/mock-npm')
t.test('no args', async t => {
const { npm, joinedOutput, prefix: path } = await setupMockNpm(t, {
prefixDir: {
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
bin: 'cwd',
scripts: {
preinstall: "node -e \"require('fs').writeFileSync('cwd', '')\"",
},
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
bin: 'cwd',
scripts: {
preinstall: "node -e \"require('fs').writeFileSync('cwd', '')\"",
},
}),
},
},
},
})
const aBuildFile = resolve(path, 'node_modules/a/cwd')
const bBuildFile = resolve(path, 'node_modules/b/cwd')
const aBinFile = resolve(path, 'node_modules/.bin/a')
const bBinFile = resolve(path, 'node_modules/.bin/b')
t.throws(() => fs.statSync(aBuildFile))
t.throws(() => fs.statSync(bBuildFile))
t.throws(() => fs.statSync(aBinFile))
t.throws(() => fs.statSync(bBinFile))
await npm.exec('rebuild', [])
t.ok(() => fs.statSync(aBuildFile))
t.ok(() => fs.statSync(bBuildFile))
t.ok(() => fs.statSync(aBinFile))
t.ok(() => fs.statSync(bBinFile))
t.equal(
joinedOutput(),
'rebuilt dependencies successfully',
'should output success msg'
)
})
t.test('filter by pkg name', async t => {
const { npm, prefix: path } = await setupMockNpm(t, {
prefixDir: {
node_modules: {
a: {
'index.js': '',
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
bin: 'index.js',
}),
},
b: {
'index.js': '',
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
bin: 'index.js',
}),
},
},
},
})
const aBinFile = resolve(path, 'node_modules/.bin/a')
const bBinFile = resolve(path, 'node_modules/.bin/b')
t.throws(() => fs.statSync(aBinFile))
t.throws(() => fs.statSync(bBinFile))
await npm.exec('rebuild', ['b'])
t.throws(() => fs.statSync(aBinFile), 'should not link a bin')
t.ok(() => fs.statSync(bBinFile), 'should link filtered pkg bin')
})
t.test('filter by pkg@<range>', async t => {
const { npm, prefix: path } = await setupMockNpm(t, {
prefixDir: {
node_modules: {
a: {
'index.js': '',
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
bin: 'index.js',
}),
node_modules: {
b: {
'index.js': '',
'package.json': JSON.stringify({
name: 'b',
version: '2.0.0',
bin: 'index.js',
}),
},
},
},
b: {
'index.js': '',
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
bin: 'index.js',
}),
},
},
},
})
const bBinFile = resolve(path, 'node_modules/.bin/b')
const nestedBinFile = resolve(path, 'node_modules/a/node_modules/.bin/b')
await npm.exec('rebuild', ['b@2'])
t.throws(() => fs.statSync(bBinFile), 'should not link b bin')
t.ok(() => fs.statSync(nestedBinFile), 'should link filtered pkg bin')
})
t.test('filter by directory', async t => {
const { npm, prefix: path } = await setupMockNpm(t, {
prefixDir: {
node_modules: {
a: {
'index.js': '',
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
bin: 'index.js',
}),
},
b: {
'index.js': '',
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
bin: 'index.js',
}),
},
},
},
})
const aBinFile = resolve(path, 'node_modules/.bin/a')
const bBinFile = resolve(path, 'node_modules/.bin/b')
t.throws(() => fs.statSync(aBinFile))
t.throws(() => fs.statSync(bBinFile))
await npm.exec('rebuild', ['file:node_modules/b'])
t.throws(() => fs.statSync(aBinFile), 'should not link a bin')
t.ok(() => fs.statSync(bBinFile), 'should link filtered pkg bin')
})
t.test('filter must be a semver version/range, or directory', async t => {
const { npm } = await setupMockNpm(t)
await t.rejects(
npm.exec('rebuild', ['git+ssh://github.com/npm/arborist']),
/`npm rebuild` only supports SemVer version\/range specifiers/,
'should throw type error'
)
})
t.test('global prefix', async t => {
const { npm, globalPrefix, joinedOutput } = await setupMockNpm(t, {
config: {
global: true,
},
globalPrefixDir: {
node_modules: {
a: {
'index.js': '',
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
bin: 'index.js',
}),
},
},
},
})
await npm.exec('rebuild', [])
t.ok(() => fs.statSync(resolve(globalPrefix, 'lib/node_modules/.bin/a')))
t.equal(
joinedOutput(),
'rebuilt dependencies successfully',
'should output success msg'
)
})

322
deps/npm/test/lib/commands/repo.js vendored Normal file
View File

@ -0,0 +1,322 @@
const t = require('tap')
const mockNpm = require('../../fixtures/mock-npm.js')
const { sep } = require('node:path')
const fixture = {
'package.json': JSON.stringify({
name: 'thispkg',
version: '1.2.3',
repository: 'https://example.com/thispkg.git',
}),
norepo: {
'package.json': JSON.stringify({
name: 'norepo',
version: '1.2.3',
}),
},
'repoobbj-nourl': {
'package.json': JSON.stringify({
name: 'repoobj-nourl',
repository: { no: 'url' },
}),
},
hostedgit: {
'package.json': JSON.stringify({
repository: 'git://github.com/foo/hostedgit',
}),
},
hostedgitat: {
'package.json': JSON.stringify({
repository: 'git@github.com:foo/hostedgitat',
}),
},
hostedssh: {
'package.json': JSON.stringify({
repository: 'ssh://git@github.com/foo/hostedssh',
}),
},
hostedgitssh: {
'package.json': JSON.stringify({
repository: 'git+ssh://git@github.com/foo/hostedgitssh',
}),
},
hostedgithttp: {
'package.json': JSON.stringify({
repository: 'git+http://github.com/foo/hostedgithttp',
}),
},
hostedgithttps: {
'package.json': JSON.stringify({
repository: 'git+https://github.com/foo/hostedgithttps',
}),
},
hostedgitobj: {
'package.json': JSON.stringify({
repository: { url: 'git://github.com/foo/hostedgitobj' },
}),
},
hostedgitatobj: {
'package.json': JSON.stringify({
repository: { url: 'git@github.com:foo/hostedgitatobj' },
}),
},
hostedsshobj: {
'package.json': JSON.stringify({
repository: { url: 'ssh://git@github.com/foo/hostedsshobj' },
}),
},
hostedgitsshobj: {
'package.json': JSON.stringify({
repository: { url: 'git+ssh://git@github.com/foo/hostedgitsshobj' },
}),
},
hostedgithttpobj: {
'package.json': JSON.stringify({
repository: { url: 'git+http://github.com/foo/hostedgithttpobj' },
}),
},
hostedgithttpsobj: {
'package.json': JSON.stringify({
repository: { url: 'git+https://github.com/foo/hostedgithttpsobj' },
}),
},
unhostedgit: {
'package.json': JSON.stringify({
repository: 'git://gothib.com/foo/unhostedgit',
}),
},
unhostedgitat: {
'package.json': JSON.stringify({
repository: 'git@gothib.com:foo/unhostedgitat',
}),
},
unhostedssh: {
'package.json': JSON.stringify({
repository: 'ssh://git@gothib.com/foo/unhostedssh',
}),
},
unhostedgitssh: {
'package.json': JSON.stringify({
repository: 'git+ssh://git@gothib.com/foo/unhostedgitssh',
}),
},
unhostedgithttp: {
'package.json': JSON.stringify({
repository: 'git+http://gothib.com/foo/unhostedgithttp',
}),
},
unhostedgithttps: {
'package.json': JSON.stringify({
repository: 'git+https://gothib.com/foo/unhostedgithttps',
}),
},
unhostedgitobj: {
'package.json': JSON.stringify({
repository: { url: 'git://gothib.com/foo/unhostedgitobj' },
}),
},
unhostedgitatobj: {
'package.json': JSON.stringify({
repository: { url: 'git@gothib.com:foo/unhostedgitatobj' },
}),
},
unhostedsshobj: {
'package.json': JSON.stringify({
repository: { url: 'ssh://git@gothib.com/foo/unhostedsshobj' },
}),
},
unhostedgitsshobj: {
'package.json': JSON.stringify({
repository: { url: 'git+ssh://git@gothib.com/foo/unhostedgitsshobj' },
}),
},
unhostedgithttpobj: {
'package.json': JSON.stringify({
repository: { url: 'git+http://gothib.com/foo/unhostedgithttpobj' },
}),
},
unhostedgithttpsobj: {
'package.json': JSON.stringify({
repository: { url: 'git+https://gothib.com/foo/unhostedgithttpsobj' },
}),
},
directory: {
'package.json': JSON.stringify({
repository: {
type: 'git',
url: 'git+https://github.com/foo/test-repo-with-directory.git',
directory: 'some/directory',
},
}),
},
}
const workspaceFixture = {
'package.json': JSON.stringify({
name: 'workspaces-test',
version: '1.2.3-test',
workspaces: ['workspace-a', 'workspace-b', 'workspace-c'],
repository: 'https://github.com/npm/workspaces-test',
}),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.2.3-a',
repository: 'http://repo.workspace-a/',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '1.2.3-n',
repository: 'https://github.com/npm/workspace-b',
}),
},
'workspace-c': JSON.stringify({
'package.json': {
name: 'workspace-n',
version: '1.2.3-n',
},
}),
}
const loadMockNpm = async (t, prefixDir, config = {}) => {
// keep a tally of which urls got opened
const opened = {}
const mock = await mockNpm(t, {
command: 'repo',
mocks: {
'{LIB}/utils/open-url.js': {
openUrl: async (_, url) => {
opened[url] = opened[url] || 0
opened[url]++
},
},
},
config,
prefixDir,
})
return {
...mock,
opened,
}
}
t.test('open repo urls', async t => {
const { repo, opened } = await loadMockNpm(t, fixture)
const expect = {
hostedgit: 'https://github.com/foo/hostedgit',
hostedgitat: 'https://github.com/foo/hostedgitat',
hostedssh: 'https://github.com/foo/hostedssh',
hostedgitssh: 'https://github.com/foo/hostedgitssh',
hostedgithttp: 'https://github.com/foo/hostedgithttp',
hostedgithttps: 'https://github.com/foo/hostedgithttps',
hostedgitobj: 'https://github.com/foo/hostedgitobj',
hostedgitatobj: 'https://github.com/foo/hostedgitatobj',
hostedsshobj: 'https://github.com/foo/hostedsshobj',
hostedgitsshobj: 'https://github.com/foo/hostedgitsshobj',
hostedgithttpobj: 'https://github.com/foo/hostedgithttpobj',
hostedgithttpsobj: 'https://github.com/foo/hostedgithttpsobj',
unhostedgit: 'https://gothib.com/foo/unhostedgit',
unhostedssh: 'https://gothib.com/foo/unhostedssh',
unhostedgitssh: 'https://gothib.com/foo/unhostedgitssh',
unhostedgithttp: 'http://gothib.com/foo/unhostedgithttp',
unhostedgithttps: 'https://gothib.com/foo/unhostedgithttps',
unhostedgitobj: 'https://gothib.com/foo/unhostedgitobj',
unhostedsshobj: 'https://gothib.com/foo/unhostedsshobj',
unhostedgitsshobj: 'https://gothib.com/foo/unhostedgitsshobj',
unhostedgithttpobj: 'http://gothib.com/foo/unhostedgithttpobj',
unhostedgithttpsobj: 'https://gothib.com/foo/unhostedgithttpsobj',
directory: 'https://github.com/foo/test-repo-with-directory/tree/HEAD/some/directory',
'.': 'https://example.com/thispkg',
}
for (const [pkg, url] of Object.entries(expect)) {
await repo.exec([['.', pkg].join(sep)])
t.equal(opened[url], 1, `opened ${url}`)
}
})
t.test('fail if cannot figure out repo url', async t => {
const { repo } = await loadMockNpm(t, fixture)
const cases = [
'norepo',
'repoobbj-nourl',
'unhostedgitat',
'unhostedgitatobj',
]
for (const pkg of cases) {
await t.rejects(
repo.exec([['.', pkg].join(sep)]),
{ pkgid: pkg }
)
}
})
t.test('open default package if none specified', async t => {
const { repo, opened } = await loadMockNpm(t, fixture)
await repo.exec([])
t.equal(opened['https://example.com/thispkg'], 1, 'opened expected url', { opened })
})
t.test('workspaces', async t => {
const mockWorkspaces = (t, config) => loadMockNpm(t, workspaceFixture, config)
t.test('include workspace root', async (t) => {
const { opened, repo } = await mockWorkspaces(t, {
workspaces: true,
'include-workspace-root': true,
})
await repo.exec([])
t.match({
'https://github.com/npm/workspaces-test': 1,
'https://repo.workspace-a/': 1, // Gets translated to https!
'https://github.com/npm/workspace-b': 1,
}, opened, 'opened two valid repo urls')
})
t.test('all workspaces', async (t) => {
const { opened, repo } = await mockWorkspaces(t, {
workspaces: true,
})
await repo.exec([])
t.match({
'https://repo.workspace-a/': 1, // Gets translated to https!
'https://github.com/npm/workspace-b': 1,
}, opened, 'opened two valid repo urls')
})
t.test('one workspace', async (t) => {
const { opened, repo } = await mockWorkspaces(t, {
workspace: ['workspace-a'],
})
await repo.exec([])
t.match({
'https://repo.workspace-a/': 1,
}, opened, 'opened one requested repo urls')
})
t.test('invalid workspace', async (t) => {
const { opened, repo } = await mockWorkspaces(t, {
workspace: ['workspace-x'],
})
await t.rejects(
repo.exec([]),
/workspace-x/
)
t.match({}, opened, 'opened no repo urls')
})
t.test('package arg and workspace', async (t) => {
const { opened, repo } = await mockWorkspaces(t, {
workspace: ['workspace-x'],
})
await repo.exec(['.'])
t.match({
'https://github.com/npm/workspaces-test': 1,
}, opened, 'opened url for package arg, not workspace')
})
})

33
deps/npm/test/lib/commands/restart.js vendored Normal file
View File

@ -0,0 +1,33 @@
const t = require('tap')
const tspawk = require('../../fixtures/tspawk')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const spawk = tspawk(t)
const isCmdRe = /(?:^|\\)cmd(?:\.exe)?$/i
t.test('should run restart script from package.json', async t => {
const { npm } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
scripts: {
restart: 'node ./test-restart.js',
},
}),
},
config: {
loglevel: 'silent',
'script-shell': process.platform === 'win32' ? process.env.COMSPEC : 'sh',
},
})
const scriptShell = npm.config.get('script-shell')
const scriptArgs = isCmdRe.test(scriptShell)
? ['/d', '/s', '/c', 'node ./test-restart.js foo']
: ['-c', 'node ./test-restart.js foo']
const script = spawk.spawn(scriptShell, scriptArgs)
await npm.exec('restart', ['foo'])
t.ok(script.called, 'script ran')
})

12
deps/npm/test/lib/commands/root.js vendored Normal file
View File

@ -0,0 +1,12 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
t.test('prefix', async (t) => {
const { joinedOutput, npm } = await loadMockNpm(t)
await npm.exec('root', [])
t.equal(
joinedOutput(),
npm.dir,
'outputs npm.dir'
)
})

762
deps/npm/test/lib/commands/run-script.js vendored Normal file
View File

@ -0,0 +1,762 @@
const t = require('tap')
const { resolve } = require('node:path')
const realRunScript = require('@npmcli/run-script')
const mockNpm = require('../../fixtures/mock-npm')
const { cleanCwd } = require('../../fixtures/clean-snapshot')
const path = require('node:path')
const mockRs = async (t, { windows = false, runScript, ...opts } = {}) => {
let RUN_SCRIPTS = []
t.afterEach(() => RUN_SCRIPTS = [])
const mock = await mockNpm(t, {
...opts,
command: 'run-script',
mocks: {
'@npmcli/run-script': Object.assign(
async rs => {
if (runScript) {
await runScript(rs)
}
RUN_SCRIPTS.push(rs)
},
realRunScript
),
'{LIB}/utils/is-windows.js': { isWindowsShell: windows },
},
})
return {
...mock,
RUN_SCRIPTS: () => RUN_SCRIPTS,
runScript: mock['run-script'],
cleanLogs: () => mock.logs.error.map(cleanCwd).join('\n'),
}
}
t.test('completion', async t => {
const completion = async (t, remain, pkg, isFish = false) => {
const { runScript } = await mockRs(t,
pkg ? { prefixDir: { 'package.json': JSON.stringify(pkg) } } : {}
)
return runScript.completion({ conf: { argv: { remain } }, isFish })
}
t.test('already have a script name', async t => {
const res = await completion(t, ['npm', 'run', 'x'])
t.equal(res, undefined)
})
t.test('no package.json', async t => {
const res = await completion(t, ['npm', 'run'])
t.strictSame(res, [])
})
t.test('has package.json, no scripts', async t => {
const res = await completion(t, ['npm', 'run'], {})
t.strictSame(res, [])
})
t.test('has package.json, with scripts', async t => {
const res = await completion(t, ['npm', 'run'], {
scripts: { hello: 'echo hello', world: 'echo world' },
})
t.strictSame(res, ['hello', 'world'])
})
t.test('fish shell', async t => {
const res = await completion(t, ['npm', 'run'], {
scripts: { hello: 'echo hello', world: 'echo world' },
}, true)
t.strictSame(res, ['hello\techo hello', 'world\techo world'])
})
})
t.test('fail if no package.json', async t => {
const { runScript } = await mockRs(t)
await t.rejects(runScript.exec([]), { code: 'ENOENT' })
await t.rejects(runScript.exec(['test']), { code: 'ENOENT' })
})
t.test('default env, start, and restart scripts', async t => {
const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
prefixDir: {
'package.json': JSON.stringify({ name: 'x', version: '1.2.3' }),
'server.js': 'console.log("hello, world")',
},
})
t.test('start', async t => {
await runScript.exec(['start'])
t.match(RUN_SCRIPTS(), [
{
path: npm.localPrefix,
args: [],
scriptShell: undefined,
stdio: 'inherit',
pkg: { name: 'x', version: '1.2.3', _id: 'x@1.2.3', scripts: {} },
event: 'start',
},
])
})
t.test('env', async t => {
await runScript.exec(['env'])
t.match(RUN_SCRIPTS(), [
{
path: npm.localPrefix,
args: [],
scriptShell: undefined,
stdio: 'inherit',
pkg: {
name: 'x',
version: '1.2.3',
_id: 'x@1.2.3',
scripts: {
env: 'env',
},
},
event: 'env',
},
])
})
t.test('restart', async t => {
await runScript.exec(['restart'])
t.match(RUN_SCRIPTS(), [
{
path: npm.localPrefix,
args: [],
scriptShell: undefined,
stdio: 'inherit',
pkg: {
name: 'x',
version: '1.2.3',
_id: 'x@1.2.3',
scripts: {
restart: 'npm stop --if-present && npm start',
},
},
event: 'restart',
},
])
})
})
t.test('default windows env', async t => {
const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
windows: true,
prefixDir: {
'package.json': JSON.stringify({ name: 'x', version: '1.2.3' }),
'server.js': 'console.log("hello, world")',
},
})
await runScript.exec(['env'])
t.match(RUN_SCRIPTS(), [
{
path: npm.localPrefix,
args: [],
scriptShell: undefined,
stdio: 'inherit',
pkg: {
name: 'x',
version: '1.2.3',
_id: 'x@1.2.3',
scripts: {
env: 'SET',
},
},
event: 'env',
},
])
})
t.test('non-default env script', async t => {
const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
scripts: {
env: 'hello',
},
}),
},
})
t.test('env', async t => {
await runScript.exec(['env'])
t.match(RUN_SCRIPTS(), [
{
path: npm.localPrefix,
args: [],
scriptShell: undefined,
stdio: 'inherit',
pkg: {
name: 'x',
version: '1.2.3',
_id: 'x@1.2.3',
scripts: {
env: 'hello',
},
},
event: 'env',
},
])
})
})
t.test('non-default env script windows', async t => {
const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
windows: true,
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
scripts: {
env: 'hello',
},
}),
},
})
await runScript.exec(['env'])
t.match(RUN_SCRIPTS(), [
{
path: npm.localPrefix,
args: [],
scriptShell: undefined,
stdio: 'inherit',
pkg: {
name: 'x',
version: '1.2.3',
_id: 'x@1.2.3',
scripts: {
env: 'hello',
},
},
event: 'env',
},
])
})
t.test('try to run missing script', async t => {
t.test('errors', async t => {
const { runScript } = await mockRs(t, {
prefixDir: {
'package.json': JSON.stringify({
scripts: { hello: 'world' },
bin: { goodnight: 'moon' },
}),
},
})
t.test('no suggestions', async t => {
await t.rejects(runScript.exec(['notevenclose']), 'Missing script: "notevenclose"')
})
t.test('script suggestions', async t => {
await t.rejects(runScript.exec(['helo']), /Missing script: "helo"/)
await t.rejects(runScript.exec(['helo']), /npm run hello/)
})
t.test('bin suggestions', async t => {
await t.rejects(runScript.exec(['goodneght']), /Missing script: "goodneght"/)
await t.rejects(runScript.exec(['goodneght']), /npm exec goodnight/)
})
})
t.test('with --if-present', async t => {
const { runScript, RUN_SCRIPTS } = await mockRs(t, {
config: { 'if-present': true },
prefixDir: {
'package.json': JSON.stringify({
scripts: { hello: 'world' },
bin: { goodnight: 'moon' },
}),
},
})
await runScript.exec(['goodbye'])
t.strictSame(RUN_SCRIPTS(), [], 'did not try to run anything')
})
})
t.test('run pre/post hooks', async t => {
const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
scripts: {
preenv: 'echo before the env',
postenv: 'echo after the env',
},
}),
},
})
await runScript.exec(['env'])
t.match(RUN_SCRIPTS(), [
{ event: 'preenv' },
{
path: npm.localPrefix,
args: [],
scriptShell: undefined,
stdio: 'inherit',
pkg: {
name: 'x',
version: '1.2.3',
_id: 'x@1.2.3',
scripts: {
env: 'env',
},
},
event: 'env',
},
{ event: 'postenv' },
])
})
t.test('skip pre/post hooks when using ignoreScripts', async t => {
const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
scripts: {
preenv: 'echo before the env',
postenv: 'echo after the env',
},
}),
},
config: { 'ignore-scripts': true },
})
await runScript.exec(['env'])
t.same(RUN_SCRIPTS(), [
{
path: npm.localPrefix,
args: [],
scriptShell: undefined,
stdio: 'inherit',
pkg: {
name: 'x',
version: '1.2.3',
_id: 'x@1.2.3',
scripts: {
preenv: 'echo before the env',
postenv: 'echo after the env',
env: 'env',
},
},
nodeGyp: npm.config.get('node-gyp'),
event: 'env',
},
])
})
t.test('run silent', async t => {
const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
scripts: {
preenv: 'echo before the env',
postenv: 'echo after the env',
},
}),
},
config: { silent: true },
})
await runScript.exec(['env'])
t.match(RUN_SCRIPTS(), [
{
event: 'preenv',
stdio: 'inherit',
},
{
path: npm.localPrefix,
args: [],
scriptShell: undefined,
stdio: 'inherit',
pkg: {
name: 'x',
version: '1.2.3',
_id: 'x@1.2.3',
scripts: {
env: 'env',
},
},
event: 'env',
},
{
event: 'postenv',
stdio: 'inherit',
},
])
})
t.test('list scripts', async t => {
const scripts = {
test: 'exit 2',
start: 'node server.js',
stop: 'node kill-server.js',
preenv: 'echo before the env',
postenv: 'echo after the env',
}
const mockList = async (t, config = {}) => {
const mock = await mockRs(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
scripts,
}),
},
config,
})
await mock.runScript.exec([])
return mock.joinedOutput()
}
t.test('no args', async t => {
const output = await mockList(t)
t.matchSnapshot(output, 'basic report')
})
t.test('silent', async t => {
const output = await mockList(t, { silent: true })
t.strictSame(output, '')
})
t.test('warn json', async t => {
const output = await mockList(t, { json: true })
t.matchSnapshot(output, 'json report')
})
t.test('parseable', async t => {
const output = await mockList(t, { parseable: true })
t.matchSnapshot(output)
})
})
t.test('list scripts when no scripts', async t => {
const { runScript, joinedOutput } = await mockRs(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
}),
},
})
await runScript.exec([])
t.strictSame(joinedOutput(), '', 'nothing to report')
})
t.test('list scripts, only commands', async t => {
const { runScript, joinedOutput } = await mockRs(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
scripts: { preversion: 'echo doing the version dance' },
}),
},
})
await runScript.exec([])
t.matchSnapshot(joinedOutput())
})
t.test('list scripts, only non-commands', async t => {
const { runScript, joinedOutput } = await mockRs(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
scripts: { glorp: 'echo doing the glerp glop' },
}),
},
})
await runScript.exec([])
t.matchSnapshot(joinedOutput())
})
t.test('node-gyp config', async t => {
const { runScript, RUN_SCRIPTS, npm } = await mockRs(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
}),
},
config: { 'node-gyp': '/test/node-gyp.js' },
})
await runScript.exec(['env'])
t.match(RUN_SCRIPTS(), [
{
nodeGyp: npm.config.get('node-gyp'),
},
])
})
t.test('workspaces', async t => {
const mockWorkspaces = async (t, {
runScript,
prefixDir,
workspaces = true,
exec = [],
chdir = ({ prefix }) => prefix,
...config
} = {}) => {
const mock = await mockRs(t, {
prefixDir: prefixDir || {
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
scripts: { glorp: 'echo a doing the glerp glop' },
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '2.0.0',
scripts: { glorp: 'echo b doing the glerp glop' },
}),
},
c: {
'package.json': JSON.stringify({
name: 'c',
version: '1.0.0',
scripts: {
test: 'exit 0',
posttest: 'echo posttest',
lorem: 'echo c lorem',
},
}),
},
d: {
'package.json': JSON.stringify({
name: 'd',
version: '1.0.0',
scripts: {
test: 'exit 0',
posttest: 'echo posttest',
},
}),
},
e: {
'package.json': JSON.stringify({
name: 'e',
scripts: { test: 'exit 0', start: 'echo start something' },
}),
},
noscripts: {
'package.json': JSON.stringify({
name: 'noscripts',
version: '1.0.0',
}),
},
},
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
workspaces: ['packages/*'],
}),
},
config: {
...Array.isArray(workspaces) ? { workspace: workspaces } : { workspaces },
...config,
},
chdir,
runScript,
})
if (exec) {
await mock.runScript.exec(exec)
}
return mock
}
t.test('completion', async t => {
t.test('in root dir', async t => {
const { runScript } = await mockWorkspaces(t)
const res = await runScript.completion({ conf: { argv: { remain: ['npm', 'run'] } } })
t.strictSame(res, [])
})
t.test('in workspace dir', async t => {
const { runScript } = await mockWorkspaces(t, {
chdir: ({ prefix }) => path.join(prefix, 'packages/c'),
})
const res = await runScript.completion({ conf: { argv: { remain: ['npm', 'run'] } } })
t.strictSame(res, ['test', 'posttest', 'lorem'])
})
})
t.test('list all scripts', async t => {
const { joinedOutput } = await mockWorkspaces(t)
t.matchSnapshot(joinedOutput())
})
t.test('list regular scripts, filtered by name', async t => {
const { joinedOutput } = await mockWorkspaces(t, { workspaces: ['a', 'b'] })
t.matchSnapshot(joinedOutput())
})
t.test('list regular scripts, filtered by path', async t => {
const { joinedOutput } = await mockWorkspaces(t, { workspaces: ['./packages/a'] })
t.matchSnapshot(joinedOutput())
})
t.test('list regular scripts, filtered by parent folder', async t => {
const { joinedOutput } = await mockWorkspaces(t, { workspaces: ['./packages'] })
t.matchSnapshot(joinedOutput())
})
t.test('list all scripts with colors', async t => {
const { joinedOutput } = await mockWorkspaces(t, { color: 'always' })
t.matchSnapshot(joinedOutput())
})
t.test('list all scripts --json', async t => {
const { joinedOutput } = await mockWorkspaces(t, { json: true })
t.matchSnapshot(joinedOutput())
})
t.test('list all scripts --parseable', async t => {
const { joinedOutput } = await mockWorkspaces(t, { parseable: true })
t.matchSnapshot(joinedOutput())
})
t.test('list no scripts --loglevel=silent', async t => {
const { joinedOutput } = await mockWorkspaces(t, { silent: true })
t.strictSame(joinedOutput(), '')
})
t.test('run scripts across all workspaces', async t => {
const { npm, RUN_SCRIPTS } = await mockWorkspaces(t, { exec: ['test'] })
t.match(RUN_SCRIPTS(), [
{
path: resolve(npm.localPrefix, 'packages/c'),
pkg: { name: 'c', version: '1.0.0' },
event: 'test',
},
{
path: resolve(npm.localPrefix, 'packages/c'),
pkg: { name: 'c', version: '1.0.0' },
event: 'posttest',
},
{
path: resolve(npm.localPrefix, 'packages/d'),
pkg: { name: 'd', version: '1.0.0' },
event: 'test',
},
{
path: resolve(npm.localPrefix, 'packages/d'),
pkg: { name: 'd', version: '1.0.0' },
event: 'posttest',
},
{
path: resolve(npm.localPrefix, 'packages/e'),
pkg: { name: 'e' },
event: 'test',
},
])
})
t.test('missing scripts in all workspaces', async t => {
const { runScript, RUN_SCRIPTS, cleanLogs } = await mockWorkspaces(t, { exec: null })
await runScript.exec(['missing-script'])
t.match(RUN_SCRIPTS(), [])
t.matchSnapshot(
cleanLogs(),
'should log error msgs for each workspace script'
)
})
t.test('missing scripts in some workspaces', async t => {
const { RUN_SCRIPTS, cleanLogs } = await mockWorkspaces(t, {
exec: ['test'],
workspaces: ['a', 'b', 'c', 'd'],
})
t.match(RUN_SCRIPTS(), [])
t.matchSnapshot(
cleanLogs(),
'should log error msgs for each workspace script'
)
})
t.test('no workspaces when filtering by user args', async t => {
await t.rejects(
mockWorkspaces(t, { workspaces: ['foo', 'bar'] }),
'No workspaces found:\n --workspace=foo --workspace=bar',
'should throw error msg'
)
})
t.test('no workspaces', async t => {
await t.rejects(
mockWorkspaces(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
}),
},
}),
/No workspaces found!/,
'should throw error msg'
)
})
t.test('single failed workspace run', async t => {
const { cleanLogs } = await mockWorkspaces(t, {
runScript: () => {
throw new Error('err')
},
exec: ['test'],
workspaces: ['c'],
})
t.matchSnapshot(
cleanLogs(),
'should log error msgs for each workspace script'
)
})
t.test('failed workspace run with succeeded runs', async t => {
const { cleanLogs, RUN_SCRIPTS, prefix } = await mockWorkspaces(t, {
runScript: (opts) => {
if (opts.pkg.name === 'a') {
throw new Error('ERR')
}
},
exec: ['glorp'],
workspaces: ['a', 'b'],
})
t.matchSnapshot(
cleanLogs(),
'should log error msgs for each workspace script'
)
t.match(RUN_SCRIPTS(), [
{
path: resolve(prefix, 'packages/b'),
pkg: { name: 'b', version: '2.0.0' },
event: 'glorp',
},
])
})
})

594
deps/npm/test/lib/commands/sbom.js vendored Normal file
View File

@ -0,0 +1,594 @@
const t = require('tap')
const mockNpm = require('../../fixtures/mock-npm.js')
const FAKE_TIMESTAMP = '2020-01-01T00:00:00.000Z'
const FAKE_UUID = '00000000-0000-0000-0000-000000000000'
t.cleanSnapshot = s => {
let sbom
try {
sbom = JSON.parse(s)
} catch (e) {
return s
}
// Clean dynamic values from snapshots. SPDX and CycloneDX have different
// formats for these values, so we need to do it separately.
if (sbom.SPDXID) {
sbom.documentNamespace = `http://spdx.org/spdxdocs/test-npm-sbom-1.0.0-${FAKE_UUID}`
if (sbom.creationInfo) {
sbom.creationInfo.created = FAKE_TIMESTAMP
sbom.creationInfo.creators = ['Tool: npm/cli-10.0.0']
}
} else {
sbom.serialNumber = `urn:uuid:${FAKE_UUID}`
if (sbom.metadata) {
sbom.metadata.timestamp = FAKE_TIMESTAMP
sbom.metadata.tools[0].version = '10.0.0'
}
}
return JSON.stringify(sbom, null, 2)
}
const simpleNmFixture = {
node_modules: {
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
dependencies: {
dog: '^1.0.0',
},
}),
node_modules: {
dog: {
'package.json': JSON.stringify({
name: 'dog',
version: '1.0.0',
}),
},
},
},
chai: {
'package.json': JSON.stringify({
name: 'chai',
version: '1.0.0',
}),
},
},
}
const mockSbom = async (t, { mocks, config, ...opts } = {}) => {
const mock = await mockNpm(t, {
...opts,
config: {
...config,
},
command: 'sbom',
mocks: {
'node:path': {
...require('node:path'),
sep: '/',
},
...mocks,
},
})
return {
...mock,
result: () => mock.joinedOutput(),
}
}
t.test('sbom', async t => {
t.test('basic sbom - spdx', async t => {
const config = {
'sbom-format': 'spdx',
}
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-sbom',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
chai: '^1.0.0',
},
}),
...simpleNmFixture,
},
})
await sbom.exec([])
t.matchSnapshot(result())
})
t.test('basic sbom - cyclonedx', async t => {
const config = {
'sbom-format': 'cyclonedx',
'sbom-type': 'application',
}
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-sbom',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
chai: '^1.0.0',
},
}),
...simpleNmFixture,
},
})
await sbom.exec([])
t.matchSnapshot(result())
})
t.test('--omit dev', async t => {
const config = {
'sbom-format': 'spdx',
omit: ['dev'],
}
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-sbom',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
},
devDependencies: {
chai: '^1.0.0',
},
}),
...simpleNmFixture,
},
})
await sbom.exec([])
t.matchSnapshot(result())
})
t.test('--omit optional', async t => {
const config = {
'sbom-format': 'spdx',
omit: ['optional'],
}
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-sbom',
version: '1.0.0',
dependencies: {
chai: '^1.0.0',
},
optionalDependencies: {
foo: '^1.0.0',
},
}),
...simpleNmFixture,
},
})
await sbom.exec([])
t.matchSnapshot(result())
})
t.test('--omit peer', async t => {
const config = {
'sbom-format': 'spdx',
omit: ['peer'],
}
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-sbom',
version: '1.0.0',
dependencies: {
chai: '^1.0.0',
},
peerDependencies: {
foo: '^1.0.0',
},
}),
...simpleNmFixture,
},
})
await sbom.exec([])
t.matchSnapshot(result())
})
const dupDepsNmFixture = {
node_modules: {
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
dependencies: {
chai: '^1.0.0',
},
}),
node_modules: {
chai: {
'package.json': JSON.stringify({
name: 'chai',
version: '1.0.0',
}),
},
},
},
bar: {
'package.json': JSON.stringify({
name: 'bar',
version: '1.0.0',
dependencies: {
chai: '^1.0.0',
},
}),
node_modules: {
chai: {
'package.json': JSON.stringify({
name: 'chai',
version: '1.0.0',
}),
},
},
},
chai: {
'package.json': JSON.stringify({
name: 'chai',
version: '2.0.0',
}),
},
},
}
t.test('duplicate deps - spdx', async t => {
const config = {
'sbom-format': 'spdx',
}
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-sbom',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
bar: '^1.0.0',
chai: '^2.0.0',
},
}),
...dupDepsNmFixture,
},
})
await sbom.exec([])
t.matchSnapshot(result())
})
t.test('duplicate deps - cyclonedx', async t => {
const config = {
'sbom-format': 'cyclonedx',
}
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-sbom',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
bar: '^1.0.0',
chai: '^2.0.0',
},
}),
...dupDepsNmFixture,
},
})
await sbom.exec([])
t.matchSnapshot(result())
})
t.test('missing format', async t => {
const config = {}
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-sbom',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
chai: '^1.0.0',
},
}),
...simpleNmFixture,
},
})
await t.rejects(sbom.exec([]), {
code: 'EUSAGE',
message: 'Must specify --sbom-format flag with one of: cyclonedx, spdx.',
},
'should throw error')
t.matchSnapshot(result())
})
t.test('invalid dep', async t => {
const config = {
'sbom-format': 'spdx',
}
const { sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-ls',
version: '1.0.0',
dependencies: {
foo: '^2.0.0',
},
}),
...simpleNmFixture,
},
})
await t.rejects(
sbom.exec([]),
{ code: 'ESBOMPROBLEMS', message: /invalid: foo@1.0.0/ },
'should list dep problems'
)
})
t.test('missing dep', async t => {
const config = {
'sbom-format': 'spdx',
}
const { sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-ls',
version: '1.0.0',
dependencies: {
ipsum: '^1.0.0',
},
}),
...simpleNmFixture,
},
})
await t.rejects(
sbom.exec([]),
{ code: 'ESBOMPROBLEMS', message: /missing: ipsum@\^1.0.0/ },
'should list dep problems'
)
})
t.test('missing (optional) dep', async t => {
const config = {
'sbom-format': 'spdx',
}
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-ls',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
chai: '^1.0.0',
},
optionalDependencies: {
ipsum: '^1.0.0',
},
}),
...simpleNmFixture,
},
})
await sbom.exec([])
t.matchSnapshot(result())
})
t.test('extraneous dep', async t => {
const config = {
'sbom-format': 'spdx',
}
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-ls',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
},
}),
...simpleNmFixture,
},
})
await sbom.exec([])
t.matchSnapshot(result())
})
t.test('lock file only', async t => {
const config = {
'sbom-format': 'spdx',
'package-lock-only': true,
}
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-ls',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
chai: '^1.0.0',
},
}),
'package-lock.json': JSON.stringify({
dependencies: {
foo: {
version: '1.0.0',
requires: {
dog: '^1.0.0',
},
},
dog: {
version: '1.0.0',
},
chai: {
version: '1.0.0',
},
},
}),
},
})
await sbom.exec([])
t.matchSnapshot(result())
})
t.test('lock file only - missing lock file', async t => {
const config = {
'sbom-format': 'spdx',
'package-lock-only': true,
}
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'test-npm-ls',
version: '1.0.0',
dependencies: {
foo: '^1.0.0',
chai: '^1.0.0',
},
}),
},
})
await t.rejects(sbom.exec([]), {
code: 'EUSAGE',
message: 'A package lock or shrinkwrap file is required in package-lock-only mode',
},
'should throw error')
t.matchSnapshot(result())
})
t.test('loading a tree containing workspaces', async t => {
const mockWorkspaces = async (t, exec = [], config = {}) => {
const { result, sbom } = await mockSbom(t, {
config,
prefixDir: {
'package.json': JSON.stringify({
name: 'workspaces-tree',
version: '1.0.0',
workspaces: ['./a', './b', './d', './group/*'],
dependencies: { pacote: '1.0.0' },
}),
node_modules: {
a: t.fixture('symlink', '../a'),
b: t.fixture('symlink', '../b'),
c: {
'package.json': JSON.stringify({
name: 'c',
version: '1.0.0',
}),
},
d: t.fixture('symlink', '../d'),
e: t.fixture('symlink', '../group/e'),
f: t.fixture('symlink', '../group/f'),
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.1.1',
dependencies: {
bar: '^1.0.0',
},
}),
},
bar: {
'package.json': JSON.stringify({ name: 'bar', version: '1.0.0' }),
},
baz: {
'package.json': JSON.stringify({ name: 'baz', version: '1.0.0' }),
},
pacote: {
'package.json': JSON.stringify({ name: 'pacote', version: '1.0.0' }),
},
},
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
dependencies: {
c: '^1.0.0',
d: '^1.0.0',
},
devDependencies: {
baz: '^1.0.0',
},
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
}),
},
d: {
'package.json': JSON.stringify({
name: 'd',
version: '1.0.0',
dependencies: {
foo: '^1.1.1',
},
}),
},
group: {
e: {
'package.json': JSON.stringify({
name: 'e',
version: '1.0.0',
}),
},
f: {
'package.json': JSON.stringify({
name: 'f',
version: '1.0.0',
}),
},
},
},
})
await sbom.exec(exec)
t.matchSnapshot(result())
}
t.test('should list workspaces properly with default configs', t => mockWorkspaces(t, [], {
'sbom-format': 'spdx',
}))
t.test('should not list workspaces with --no-workspaces', t => mockWorkspaces(t, [], {
'sbom-format': 'spdx',
workspaces: false,
}))
t.test('should filter worksapces with --workspace', t => mockWorkspaces(t, [], {
'sbom-format': 'spdx',
workspace: 'a',
}))
t.test('should filter workspaces with multiple --workspace flags', t => mockWorkspaces(t, [], {
'sbom-format': 'spdx',
workspace: ['e', 'f'],
}))
})
})

304
deps/npm/test/lib/commands/search.js vendored Normal file
View File

@ -0,0 +1,304 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const MockRegistry = require('@npmcli/mock-registry')
const libnpmsearchResultFixture =
require('../../fixtures/libnpmsearch-stream-result.js')
t.test('search', t => {
t.test('no args', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('search', []),
/search must be called with arguments/,
'should throw usage instructions'
)
})
t.test('<name> text', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm'])
t.matchSnapshot(joinedOutput(), 'should have expected search results')
})
t.test('multiple terms text', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm', 'publish'])
t.matchSnapshot(joinedOutput(), 'should have expected search results')
})
t.test('<name> --json', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { json: true } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm'])
t.same(
JSON.parse(joinedOutput()),
libnpmsearchResultFixture,
'should have expected search results as json'
)
})
t.test('<name> --parseable', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { parseable: true } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm'])
t.matchSnapshot(joinedOutput(), 'should have expected search results as parseable')
})
t.test('<name> --color', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { color: 'always' } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm'])
t.matchSnapshot(joinedOutput(), 'should have expected search results with color')
})
t.test('multiple terms --color', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { color: 'always' } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm', 'publish'])
t.matchSnapshot(joinedOutput(), 'should have expected search results with color')
})
t.test('/<name>/--color', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { color: 'always' } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['/libnpm/'])
t.matchSnapshot(joinedOutput(), 'should have expected search results with color')
})
t.test('<name>', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: [{
name: 'foo',
scope: 'unscoped',
version: '1.0.0',
description: '',
keywords: [],
date: null,
author: { name: 'Foo', email: 'foo@npmjs.com' },
publisher: { username: 'foo', email: 'foo@npmjs.com' },
maintainers: [
{ username: 'foo', email: 'foo@npmjs.com' },
],
}, {
name: 'custom-registry',
scope: 'unscoped',
version: '1.0.0',
description: '',
keywords: [],
date: null,
author: { name: 'Foo', email: 'foo@npmjs.com' },
maintainers: [
{ username: 'foo', email: 'foo@npmjs.com' },
],
}, {
name: 'libnpmversion',
scope: 'unscoped',
version: '1.0.0',
description: '',
keywords: [],
date: null,
author: { name: 'Foo', email: 'foo@npmjs.com' },
publisher: { username: 'foo', email: 'foo@npmjs.com' },
maintainers: [
{ username: 'foo', email: 'foo@npmjs.com' },
],
}] })
await npm.exec('search', ['foo'])
t.matchSnapshot(joinedOutput(), 'should have filtered expected search results')
})
t.test('no publisher', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: [{
name: 'custom-registry',
scope: 'unscoped',
version: '1.0.0',
description: '',
keywords: [],
date: null,
author: { name: 'Foo', email: 'foo@npmjs.com' },
maintainers: [
{ username: 'foo', email: 'foo@npmjs.com' },
],
}, {
name: 'libnpmversion',
scope: 'unscoped',
version: '1.0.0',
description: '',
keywords: [],
date: null,
author: { name: 'Foo', email: 'foo@npmjs.com' },
publisher: { username: 'foo', email: 'foo@npmjs.com' },
maintainers: [
{ username: 'foo', email: 'foo@npmjs.com' },
],
}] })
await npm.exec('search', ['custom'])
t.matchSnapshot(joinedOutput(), 'should have filtered expected search results')
})
t.test('empty search results', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: [] })
await npm.exec('search', ['foo'])
t.matchSnapshot(joinedOutput(), 'should have expected search results')
})
t.test('empty search results --json', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { json: true } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: [] })
await npm.exec('search', ['foo'])
t.equal(joinedOutput(), '\n[]', 'should have expected empty square brackets')
})
t.test('api response error', async t => {
const { npm } = await loadMockNpm(t)
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ error: 'ERR' })
await t.rejects(
npm.exec('search', ['foo']),
/ERR/,
'should throw response error'
)
})
t.test('exclude string', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
searchexclude: 'libnpmversion',
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm'])
t.matchSnapshot(joinedOutput(), 'results should not have libnpmversion')
})
t.test('exclude string json', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
json: true,
searchexclude: 'libnpmversion',
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm'])
t.matchSnapshot(JSON.parse(joinedOutput()), 'results should not have libnpmversion')
})
t.test('exclude username with upper case letters', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { searchexclude: 'NLF' } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm'])
t.matchSnapshot(joinedOutput(), 'results should not have nlf')
})
t.test('exclude regex', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { searchexclude: '/version/' } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm'])
t.matchSnapshot(joinedOutput(), 'results should not have libnpmversion')
})
t.test('exclude forward slash', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: { searchexclude: '/version' } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.search({ results: libnpmsearchResultFixture })
await npm.exec('search', ['libnpm'])
t.matchSnapshot(joinedOutput(), 'results should not have libnpmversion')
})
t.end()
})

49
deps/npm/test/lib/commands/set.js vendored Normal file
View File

@ -0,0 +1,49 @@
const t = require('tap')
const fs = require('node:fs/promises')
const mockNpm = require('../../fixtures/mock-npm')
const { join } = require('node:path')
const { cleanNewlines } = require('../../fixtures/clean-snapshot')
t.test('no args', async t => {
const { npm } = await mockNpm(t)
t.rejects(npm.exec('set', []), /Usage:/, 'prints usage')
})
t.test('test-config-item', async t => {
const { npm, home, joinedOutput } = await mockNpm(t, {
homeDir: {
'.npmrc': 'original-config-test=original value',
},
})
t.equal(
npm.config.get('original-config-test'),
'original value',
'original config is set from npmrc'
)
t.not(
npm.config.get('fund'),
false,
'config is not already new value'
)
await npm.exec('set', ['fund=true'])
t.equal(joinedOutput(), '', 'outputs nothing')
t.equal(
npm.config.get('fund'),
true,
'config is set to new value'
)
t.equal(
cleanNewlines(await fs.readFile(join(home, '.npmrc'), 'utf-8')),
[
'original-config-test=original value',
'fund=true',
'',
].join('\n'),
'npmrc is written with new value'
)
})

232
deps/npm/test/lib/commands/shrinkwrap.js vendored Normal file
View File

@ -0,0 +1,232 @@
const t = require('tap')
const fs = require('node:fs')
const { resolve } = require('node:path')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
// Attempt to parse json values in snapshots before
// stringifying to remove escaped values like \\"
// This also doesn't reorder the keys of the object
// like tap does by default which is nice in this case
t.formatSnapshot = obj =>
JSON.stringify(
obj,
(k, v) => {
try {
return JSON.parse(v)
} catch {
// leave invalid JSON as a string
}
return v
},
2
)
// Run shrinkwrap against a specified prefixDir with config items
// and make some assertions that should always be true. Sets
// the results on t.context for use in child tests
const shrinkwrap = async (t, prefixDir = {}, config = {}) => {
const { npm, logs } = await loadMockNpm(t, {
config,
prefixDir,
})
await npm.exec('shrinkwrap', [])
const newFile = resolve(npm.prefix, 'npm-shrinkwrap.json')
const oldFile = resolve(npm.prefix, 'package-lock.json')
t.notOk(fs.existsSync(oldFile), 'package-lock is always deleted')
t.teardown(() => delete t.context)
t.context = {
localPrefix: prefixDir,
config,
shrinkwrap: JSON.parse(fs.readFileSync(newFile)),
logs: logs.notice,
warn: logs.warn,
}
}
// Run shrinkwrap against all combinations of existing and config
// lockfile versions
const shrinkwrapMatrix = async (t, file, assertions) => {
const ancient = JSON.stringify({ lockfileVersion: 1 })
const existing = JSON.stringify({ lockfileVersion: 2 })
const upgrade = { 'lockfile-version': 3 }
const downgrade = { 'lockfile-version': 1 }
let ancientDir = {}
let existingDir = null
if (file === 'package-lock') {
ancientDir = { 'package-lock.json': ancient }
existingDir = { 'package-lock.json': existing }
} else if (file === 'npm-shrinkwrap') {
ancientDir = { 'npm-shrinkwrap.json': ancient }
existingDir = { 'npm-shrinkwrap.json': existing }
} else if (file === 'hidden-lockfile') {
ancientDir = { node_modules: { '.package-lock.json': ancient } }
existingDir = { node_modules: { '.package-lock.json': existing } }
}
await t.test('ancient', async t => {
await shrinkwrap(t, ancientDir)
t.match(t.context, assertions.ancient)
t.matchSnapshot(t.context)
})
await t.test('ancient upgrade', async t => {
await shrinkwrap(t, ancientDir, upgrade)
t.match(t.context, assertions.ancientUpgrade)
t.matchSnapshot(t.context)
})
if (existingDir) {
await t.test('existing', async t => {
await shrinkwrap(t, existingDir)
t.match(t.context, assertions.existing)
t.matchSnapshot(t.context)
})
await t.test('existing upgrade', async t => {
await shrinkwrap(t, existingDir, upgrade)
t.match(t.context, assertions.existingUpgrade)
t.matchSnapshot(t.context)
})
await t.test('existing downgrade', async t => {
await shrinkwrap(t, existingDir, downgrade)
t.match(t.context, assertions.existingDowngrade)
t.matchSnapshot(t.context)
})
}
}
const NOTICES = {
CREATED: (v = '') => [`created a lockfile as npm-shrinkwrap.json${v && ` with version ${v}`}`],
RENAMED: (v = '') => [
`package-lock.json has been renamed to npm-shrinkwrap.json${
v && ` and updated to version ${v}`
}`,
],
UPDATED: (v = '') => [`npm-shrinkwrap.json updated to version ${v}`],
SAME: () => [`npm-shrinkwrap.json up to date`],
CONVERTING: (current, next) =>
[`Converting lock file (npm-shrinkwrap.json) from v${current} -> v${next}`],
}
t.test('with nothing', t =>
shrinkwrapMatrix(t, null, {
ancient: {
shrinkwrap: { lockfileVersion: 3 },
logs: NOTICES.CREATED(3),
warn: [],
},
ancientUpgrade: {
shrinkwrap: { lockfileVersion: 3 },
logs: NOTICES.CREATED(3),
warn: [],
},
})
)
t.test('with package-lock.json', t =>
shrinkwrapMatrix(t, 'package-lock', {
ancient: {
shrinkwrap: { lockfileVersion: 3 },
logs: NOTICES.RENAMED(3),
warn: NOTICES.CONVERTING(1, 3),
},
ancientUpgrade: {
shrinkwrap: { lockfileVersion: 3 },
logs: NOTICES.RENAMED(3),
warn: NOTICES.CONVERTING(1, 3),
},
existing: {
shrinkwrap: { lockfileVersion: 2 },
logs: NOTICES.RENAMED(),
warn: [],
},
existingUpgrade: {
shrinkwrap: { lockfileVersion: 3 },
logs: NOTICES.RENAMED(3),
warn: NOTICES.CONVERTING(2, 3),
},
existingDowngrade: {
shrinkwrap: { lockfileVersion: 1 },
logs: NOTICES.RENAMED(1),
warn: NOTICES.CONVERTING(2, 1),
},
})
)
t.test('with npm-shrinkwrap.json', t =>
shrinkwrapMatrix(t, 'npm-shrinkwrap', {
ancient: {
shrinkwrap: { lockfileVersion: 3 },
logs: NOTICES.UPDATED(3),
warn: NOTICES.CONVERTING(1, 3),
},
ancientUpgrade: {
shrinkwrap: { lockfileVersion: 3 },
logs: NOTICES.UPDATED(3),
warn: NOTICES.CONVERTING(1, 3),
},
existing: {
shrinkwrap: { lockfileVersion: 2 },
logs: NOTICES.SAME(),
warn: [],
},
existingUpgrade: {
shrinkwrap: { lockfileVersion: 3 },
logs: NOTICES.UPDATED(3),
warn: NOTICES.CONVERTING(2, 3),
},
existingDowngrade: {
shrinkwrap: { lockfileVersion: 1 },
logs: NOTICES.UPDATED(1),
warn: NOTICES.CONVERTING(2, 1),
},
})
)
t.test('with hidden lockfile', t =>
shrinkwrapMatrix(t, 'hidden-lockfile', {
ancient: {
shrinkwrap: { lockfileVersion: 1 },
logs: NOTICES.CREATED(),
warn: [],
},
ancientUpgrade: {
shrinkwrap: { lockfileVersion: 3 },
logs: NOTICES.CREATED(),
warn: NOTICES.CONVERTING(1, 3),
},
existing: {
shrinkwrap: { lockfileVersion: 2 },
logs: NOTICES.CREATED(),
warn: [],
},
existingUpgrade: {
shrinkwrap: { lockfileVersion: 3 },
logs: NOTICES.CREATED(3),
warn: NOTICES.CONVERTING(2, 3),
},
existingDowngrade: {
shrinkwrap: { lockfileVersion: 1 },
logs: NOTICES.CREATED(1),
warn: NOTICES.CONVERTING(2, 1),
},
})
)
t.test('throws in global mode', async t => {
t.rejects(
shrinkwrap(
t,
{},
{
global: true,
}
),
{
message: '`npm shrinkwrap` does not work for global packages',
code: 'ESHRINKWRAPGLOBAL',
}
)
})

61
deps/npm/test/lib/commands/star.js vendored Normal file
View File

@ -0,0 +1,61 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const MockRegistry = require('@npmcli/mock-registry')
const pkgName = '@npmcli/test-package'
const authToken = 'test-auth-token'
const username = 'test-user'
const auth = { '//registry.npmjs.org/:_authToken': authToken }
t.test('no args', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('star', []),
{ code: 'EUSAGE' },
'should throw usage error'
)
})
t.test('first person to star a package unicode:false', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: { unicode: false, ...auth },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
const manifest = registry.manifest({ name: pkgName })
await registry.package({ manifest, query: { write: true } })
registry.whoami({ username })
registry.star(manifest, { [username]: true })
await npm.exec('star', [pkgName])
t.equal(
joinedOutput(),
'(*) @npmcli/test-package',
'should output starred package msg'
)
})
t.test('second person to star a package unicode:true', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: { unicode: true, ...auth },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
const manifest = registry.manifest({ name: pkgName, users: { otheruser: true } })
await registry.package({ manifest, query: { write: true } })
registry.whoami({ username })
registry.star(manifest, { otheruser: true, [username]: true })
await npm.exec('star', [pkgName])
t.equal(
joinedOutput(),
'★ @npmcli/test-package',
'should output starred package msg'
)
})

127
deps/npm/test/lib/commands/stars.js vendored Normal file
View File

@ -0,0 +1,127 @@
const t = require('tap')
const realFetch = require('npm-registry-fetch')
const mockNpm = require('../../fixtures/mock-npm')
const noop = () => {}
const mockStars = async (t, { npmFetch = noop, exec = true, ...opts }) => {
const mock = await mockNpm(t, {
command: 'stars',
exec,
mocks: {
'npm-registry-fetch': Object.assign(noop, realFetch, { json: npmFetch }),
'{LIB}/utils/get-identity.js': async () => 'foo',
},
...opts,
})
return {
...mock,
result: mock.stars.output,
logs: () => mock.logs.byTitle('stars'),
}
}
t.test('no args', async t => {
t.plan(3)
const npmFetch = async (uri, opts) => {
t.equal(uri, '/-/_view/starredByUser', 'should fetch from expected uri')
t.equal(opts.query.key, '"foo"', 'should match logged in username')
return {
rows: [
{ value: '@npmcli/arborist' },
{ value: '@npmcli/map-workspaces' },
{ value: 'libnpmfund' },
{ value: 'libnpmpublish' },
{ value: 'ipt' },
],
}
}
const { result } = await mockStars(t, { npmFetch })
t.matchSnapshot(
result,
'should output a list of starred packages'
)
})
t.test('npm star <user>', async t => {
t.plan(3)
const npmFetch = async (uri, opts) => {
t.equal(uri, '/-/_view/starredByUser', 'should fetch from expected uri')
t.equal(opts.query.key, '"ruyadorno"', 'should match username')
return {
rows: [{ value: '@npmcli/arborist' }],
}
}
const { result } = await mockStars(t, { npmFetch, exec: ['ruyadorno'] })
t.match(
result,
'@npmcli/arborist',
'should output expected list of starred packages'
)
})
t.test('unauthorized request', async t => {
const npmFetch = async () => {
throw Object.assign(
new Error('Not logged in'),
{ code: 'ENEEDAUTH' }
)
}
const { joinedOutput, stars, logs } = await mockStars(t, { npmFetch, exec: false })
await t.rejects(
stars.exec([]),
/Not logged in/,
'should throw unauthorized request msg'
)
t.strictSame(
logs(),
['stars auth is required to look up your username'],
'should warn auth required msg'
)
t.equal(
joinedOutput(),
'',
'should have empty output'
)
})
t.test('unexpected error', async t => {
const npmFetch = async () => {
throw new Error('ERROR')
}
const { stars, logs } = await mockStars(t, { npmFetch, exec: false })
await t.rejects(
stars.exec([]),
/ERROR/,
'should throw unexpected error message'
)
t.strictSame(logs(), [], 'no logs')
})
t.test('no pkg starred', async t => {
const npmFetch = async () => ({ rows: [] })
const { logs } = await mockStars(t, { npmFetch })
t.strictSame(
logs(),
['stars user has not starred any packages'],
'should warn no starred packages msg'
)
})

33
deps/npm/test/lib/commands/start.js vendored Normal file
View File

@ -0,0 +1,33 @@
const t = require('tap')
const tspawk = require('../../fixtures/tspawk')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const spawk = tspawk(t)
const isCmdRe = /(?:^|\\)cmd(?:\.exe)?$/i
t.test('should run start script from package.json', async t => {
const { npm } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
scripts: {
start: 'node ./test-start.js',
},
}),
},
config: {
loglevel: 'silent',
'script-shell': process.platform === 'win32' ? process.env.COMSPEC : 'sh',
},
})
const scriptShell = npm.config.get('script-shell')
const scriptArgs = isCmdRe.test(scriptShell)
? ['/d', '/s', '/c', 'node ./test-start.js foo']
: ['-c', 'node ./test-start.js foo']
const script = spawk.spawn(scriptShell, scriptArgs)
await npm.exec('start', ['foo'])
t.ok(script.called, 'script ran')
})

33
deps/npm/test/lib/commands/stop.js vendored Normal file
View File

@ -0,0 +1,33 @@
const t = require('tap')
const tspawk = require('../../fixtures/tspawk')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const spawk = tspawk(t)
const isCmdRe = /(?:^|\\)cmd(?:\.exe)?$/i
t.test('should run stop script from package.json', async t => {
const { npm } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
scripts: {
stop: 'node ./test-stop.js',
},
}),
},
config: {
loglevel: 'silent',
'script-shell': process.platform === 'win32' ? process.env.COMSPEC : 'sh',
},
})
const scriptShell = npm.config.get('script-shell')
const scriptArgs = isCmdRe.test(scriptShell)
? ['/d', '/s', '/c', 'node ./test-stop.js foo']
: ['-c', 'node ./test-stop.js foo']
const script = spawk.spawn(scriptShell, scriptArgs)
await npm.exec('stop', ['foo'])
t.ok(script.called, 'script ran')
})

432
deps/npm/test/lib/commands/team.js vendored Normal file
View File

@ -0,0 +1,432 @@
const t = require('tap')
const mockNpm = require('../../fixtures/mock-npm')
t.cleanSnapshot = s => s.trim().replace(/\n+/g, '\n')
const mockTeam = async (t, { libnpmteam, ...opts } = {}) => {
const mock = await mockNpm(t, {
...opts,
command: 'team',
mocks: {
// XXX: this should be refactored to use the mock registry
libnpmteam: libnpmteam || {
async add () {},
async create () {},
async destroy () {},
async lsTeams () {},
async lsUsers () {},
async rm () {},
},
},
})
return {
...mock,
result: () => mock.joinedOutput(),
}
}
t.test('no args', async t => {
const { team } = await mockTeam(t)
await t.rejects(
team.exec([]),
'usage instructions',
'should throw usage instructions'
)
})
t.test('team add <scope:team> <user>', async t => {
t.test('default output', async t => {
const { team, result } = await mockTeam(t)
await team.exec(['add', '@npmcli:developers', 'foo'])
t.matchSnapshot(result(), 'should output success result for add user')
})
t.test('--parseable', async t => {
const { team, result } = await mockTeam(t, {
config: { parseable: true },
})
await team.exec(['add', '@npmcli:developers', 'foo'])
t.matchSnapshot(
result(),
'should output success result for parseable add user'
)
})
t.test('--json', async t => {
const { team, result } = await mockTeam(t, {
config: { json: true },
})
await team.exec(['add', '@npmcli:developers', 'foo'])
t.same(
JSON.parse(result()),
{
added: true,
team: 'npmcli:developers',
user: 'foo',
},
'should output success result for add user json'
)
})
t.test('--silent', async t => {
const { team, result } = await mockTeam(t, {
config: { silent: true },
})
await team.exec(['add', '@npmcli:developers', 'foo'])
t.same(result(), '', 'should not output success if silent')
})
})
t.test('team create <scope:team>', async t => {
t.test('default output', async t => {
const { team, result } = await mockTeam(t)
await team.exec(['create', '@npmcli:newteam'])
t.matchSnapshot(result(), 'should output success result for create team')
})
t.test('--parseable', async t => {
const { team, result } = await mockTeam(t, {
config: { parseable: true },
})
await team.exec(['create', '@npmcli:newteam'])
t.matchSnapshot(
result(),
'should output parseable success result for create team'
)
})
t.test('--json', async t => {
const { team, result } = await mockTeam(t, {
config: { json: true },
})
await team.exec(['create', '@npmcli:newteam'])
t.same(
JSON.parse(result()),
{
created: true,
team: 'npmcli:newteam',
},
'should output success result for create team'
)
})
t.test('--silent', async t => {
const { team, result } = await mockTeam(t, {
config: { silent: true },
})
await team.exec(['create', '@npmcli:newteam'])
t.same(result(), '', 'should not output create success if silent')
})
})
t.test('team destroy <scope:team>', async t => {
t.test('default output', async t => {
const { team, result } = await mockTeam(t)
await team.exec(['destroy', '@npmcli:newteam'])
t.matchSnapshot(result(), 'should output success result for destroy team')
})
t.test('--parseable', async t => {
const { team, result } = await mockTeam(t, {
config: { parseable: true },
})
await team.exec(['destroy', '@npmcli:newteam'])
t.matchSnapshot(result(), 'should output parseable result for destroy team')
})
t.test('--json', async t => {
const { team, result } = await mockTeam(t, {
config: { json: true },
})
await team.exec(['destroy', '@npmcli:newteam'])
t.same(
JSON.parse(result()),
{
deleted: true,
team: 'npmcli:newteam',
},
'should output parseable result for destroy team'
)
})
t.test('--silent', async t => {
const { team, result } = await mockTeam(t, {
config: { silent: true },
})
await team.exec(['destroy', '@npmcli:newteam'])
t.same(result(), '', 'should not output destroy if silent')
})
})
t.test('team ls <scope>', async t => {
const teams = {
async lsTeams () {
return [
'npmcli:developers',
'npmcli:designers',
'npmcli:product',
]
},
}
const noTeam = {
async lsTeams () {
return []
},
}
const singleTeam = {
async lsTeams () {
return ['npmcli:developers']
},
}
t.test('default output', async t => {
const { team, result } = await mockTeam(t, {
libnpmteam: teams,
})
await team.exec(['ls', '@npmcli'])
t.matchSnapshot(result(), 'should list teams for a given scope')
})
t.test('--parseable', async t => {
const { team, result } = await mockTeam(t, {
libnpmteam: teams,
config: { parseable: true },
})
await team.exec(['ls', '@npmcli'])
t.matchSnapshot(result(), 'should list teams for a parseable scope')
})
t.test('--json', async t => {
const { team, result } = await mockTeam(t, {
libnpmteam: teams,
config: { json: true },
})
await team.exec(['ls', '@npmcli'])
t.same(
JSON.parse(result()),
[
'npmcli:designers',
'npmcli:developers',
'npmcli:product',
],
'should json list teams for a scope json'
)
})
t.test('--silent', async t => {
const { team, result } = await mockTeam(t, {
libnpmteam: teams,
config: { silent: true },
})
await team.exec(['ls', '@npmcli'])
t.same(result(), '', 'should not list teams if silent')
})
t.test('no teams', async t => {
const { team, result } = await mockTeam(t, {
libnpmteam: noTeam,
})
await team.exec(['ls', '@npmcli'])
t.matchSnapshot(result(), 'should list no teams for a given scope')
})
t.test('single team', async t => {
const { team, result } = await mockTeam(t, {
libnpmteam: singleTeam,
})
await team.exec(['ls', '@npmcli'])
t.matchSnapshot(result(), 'should list single team for a given scope')
})
})
t.test('team ls <scope:team>', async t => {
const users = {
async lsUsers () {
return ['nlf', 'ruyadorno', 'darcyclarke', 'isaacs']
},
}
const singleUser = {
async lsUsers () {
return ['foo']
},
}
const noUsers = {
async lsUsers () {
return []
},
}
t.test('default output', async t => {
const { team, result } = await mockTeam(t, {
libnpmteam: users,
})
await team.exec(['ls', '@npmcli:developers'])
t.matchSnapshot(result(), 'should list users for a given scope:team')
})
t.test('--parseable', async t => {
const { team, result } = await mockTeam(t, {
libnpmteam: users,
config: { parseable: true },
})
await team.exec(['ls', '@npmcli:developers'])
t.matchSnapshot(result(), 'should list users for a parseable scope:team')
})
t.test('--json', async t => {
const { team, result } = await mockTeam(t, {
libnpmteam: users,
config: { json: true },
})
await team.exec(['ls', '@npmcli:developers'])
t.same(
JSON.parse(result()),
[
'darcyclarke',
'isaacs',
'nlf',
'ruyadorno',
],
'should list users for a scope:team json'
)
})
t.test('--silent', async t => {
const { team, result } = await mockTeam(t, {
libnpmteam: users,
config: { silent: true },
})
await team.exec(['ls', '@npmcli:developers'])
t.same(result(), '', 'should not output users if silent')
})
t.test('no users', async t => {
const { team, result } = await mockTeam(t, {
libnpmteam: noUsers,
})
await team.exec(['ls', '@npmcli:developers'])
t.matchSnapshot(result(), 'should list no users for a given scope')
})
t.test('single user', async t => {
const { team, result } = await mockTeam(t, {
libnpmteam: singleUser,
})
await team.exec(['ls', '@npmcli:developers'])
t.matchSnapshot(result(), 'should list single user for a given scope')
})
})
t.test('team rm <scope:team> <user>', async t => {
t.test('default output', async t => {
const { team, result } = await mockTeam(t)
await team.exec(['rm', '@npmcli:newteam', 'foo'])
t.matchSnapshot(result(), 'should output success result for remove user')
})
t.test('--parseable', async t => {
const { team, result } = await mockTeam(t, {
config: { parseable: true },
})
await team.exec(['rm', '@npmcli:newteam', 'foo'])
t.matchSnapshot(result(), 'should output parseable result for remove user')
})
t.test('--json', async t => {
const { team, result } = await mockTeam(t, {
config: { json: true },
})
await team.exec(['rm', '@npmcli:newteam', 'foo'])
t.same(
JSON.parse(result()),
{
removed: true,
team: 'npmcli:newteam',
user: 'foo',
},
'should output json result for remove user'
)
})
t.test('--silent', async t => {
const { team, result } = await mockTeam(t, {
config: { silent: true },
})
await team.exec(['rm', '@npmcli:newteam', 'foo'])
t.same(result(), '', 'should not output rm result if silent')
})
})
t.test('completion', async t => {
const { team } = await mockTeam(t)
t.test('npm team autocomplete', async t => {
const res = await team.completion({
conf: {
argv: {
remain: ['npm', 'team'],
},
},
})
t.strictSame(
res,
['create', 'destroy', 'add', 'rm', 'ls'],
'should auto complete with subcommands'
)
t.end()
})
t.test('npm team <subcommand> autocomplete', async t => {
for (const subcmd of ['create', 'destroy', 'add', 'rm', 'ls']) {
const res = await team.completion({
conf: {
argv: {
remain: ['npm', 'team', subcmd],
},
},
})
t.strictSame(
res,
[],
`should not autocomplete ${subcmd} subcommand`
)
}
})
t.test('npm team unknown subcommand autocomplete', async t => {
t.rejects(
team.completion({ conf: { argv: { remain: ['npm', 'team', 'missing-subcommand'] } } }),
{ message: 'missing-subcommand not recognized' }, 'should throw a a not recognized error'
)
t.end()
})
t.end()
})

33
deps/npm/test/lib/commands/test.js vendored Normal file
View File

@ -0,0 +1,33 @@
const t = require('tap')
const tspawk = require('../../fixtures/tspawk')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const spawk = tspawk(t)
const isCmdRe = /(?:^|\\)cmd(?:\.exe)?$/i
t.test('should run test script from package.json', async t => {
const { npm } = await loadMockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
scripts: {
test: 'node ./test-test.js',
},
}),
},
config: {
loglevel: 'silent',
'script-shell': process.platform === 'win32' ? process.env.COMSPEC : 'sh',
},
})
const scriptShell = npm.config.get('script-shell')
const scriptArgs = isCmdRe.test(scriptShell)
? ['/d', '/s', '/c', 'node ./test-test.js foo']
: ['-c', 'node ./test-test.js foo']
const script = spawk.spawn(scriptShell, scriptArgs)
await npm.exec('test', ['foo'])
t.ok(script.called, 'script ran')
})

382
deps/npm/test/lib/commands/token.js vendored Normal file
View File

@ -0,0 +1,382 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const MockRegistry = require('@npmcli/mock-registry')
const mockGlobals = require('@npmcli/mock-globals')
const stream = require('node:stream')
const authToken = 'abcd1234'
const password = 'this is not really a password'
const auth = {
'//registry.npmjs.org/:_authToken': authToken,
}
const now = new Date().toISOString()
const tokens = [
{
key: 'abcd1234abcd1234',
token: 'efgh5678efgh5678',
cidr_whitelist: null,
readonly: false,
created: now,
updated: now,
},
{
key: 'abcd1256',
token: 'hgfe8765',
cidr_whitelist: ['192.168.1.1/32'],
readonly: true,
created: now,
updated: now,
},
]
t.test('completion', async t => {
const { token } = await loadMockNpm(t, { command: 'token' })
const testComp = (argv, expect) => {
t.resolveMatch(token.completion({ conf: { argv: { remain: argv } } }), expect, argv.join(' '))
}
testComp(['npm', 'token'], ['list', 'revoke', 'create'])
testComp(['npm', 'token', 'list'], [])
testComp(['npm', 'token', 'revoke'], [])
testComp(['npm', 'token', 'create'], [])
t.rejects(token.completion({ conf: { argv: { remain: ['npm', 'token', 'foobar'] } } }), {
message: 'foobar not recognize',
})
})
t.test('token foobar', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(npm.exec('token', ['foobar']), /foobar is not a recognized subcommand/)
})
t.test('token list', async t => {
const { npm, outputs } = await loadMockNpm(t, {
config: { ...auth },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
registry.getTokens(tokens)
await npm.exec('token', [])
t.strictSame(outputs, [
`Publish token efgh5678efgh5678… with id abcd123 created ${now.slice(0, 10)}`,
'',
`Read only token hgfe8765… with id abcd125 created ${now.slice(0, 10)}`,
'with IP whitelist: 192.168.1.1/32',
'',
])
})
t.test('token list json output', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
...auth,
json: true,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
registry.getTokens(tokens)
await npm.exec('token', ['list'])
const parsed = JSON.parse(joinedOutput())
t.match(parsed, tokens, 'prints the json parsed tokens')
})
t.test('token list parseable output', async t => {
const { npm, outputs } = await loadMockNpm(t, {
config: {
...auth,
parseable: true,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
registry.getTokens(tokens)
await npm.exec('token', [])
t.strictSame(outputs, [
'key\ttoken\tcreated\treadonly\tCIDR whitelist',
`abcd1234abcd1234\tefgh5678efgh5678\t${now}\tfalse\t`,
`abcd1256\thgfe8765\t${now}\ttrue\t192.168.1.1/32`,
])
})
t.test('token revoke', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: { ...auth },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
registry.getTokens(tokens)
registry.nock.delete(`/-/npm/v1/tokens/token/${tokens[0].key}`).reply(200)
await npm.exec('token', ['rm', tokens[0].key.slice(0, 8)])
t.equal(joinedOutput(), 'Removed 1 token')
})
t.test('token revoke multiple tokens', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: { ...auth },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
registry.getTokens(tokens)
registry.nock.delete(`/-/npm/v1/tokens/token/${tokens[0].key}`).reply(200)
registry.nock.delete(`/-/npm/v1/tokens/token/${tokens[1].key}`).reply(200)
await npm.exec('token', ['rm', tokens[0].key.slice(0, 8), tokens[1].key.slice(0, 8)])
t.equal(joinedOutput(), 'Removed 2 tokens')
})
t.test('token revoke json output', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
...auth,
json: true,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
registry.getTokens(tokens)
registry.nock.delete(`/-/npm/v1/tokens/token/${tokens[0].key}`).reply(200)
await npm.exec('token', ['rm', tokens[0].key.slice(0, 8)])
const parsed = JSON.parse(joinedOutput())
t.same(parsed, [tokens[0].key], 'logs the token as json')
})
t.test('token revoke parseable output', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
...auth,
parseable: true,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
registry.getTokens(tokens)
registry.nock.delete(`/-/npm/v1/tokens/token/${tokens[0].key}`).reply(200)
await npm.exec('token', ['rm', tokens[0].key.slice(0, 8)])
t.equal(joinedOutput(), tokens[0].key, 'logs the token as a string')
})
t.test('token revoke by token', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: { ...auth },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
registry.getTokens(tokens)
registry.nock.delete(`/-/npm/v1/tokens/token/${tokens[0].token}`).reply(200)
await npm.exec('token', ['rm', tokens[0].token])
t.equal(joinedOutput(), 'Removed 1 token')
})
t.test('token revoke requires an id', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(npm.exec('token', ['rm']), {
code: 'EUSAGE',
message: '`<tokenKey>` argument is required',
})
})
t.test('token revoke ambiguous id errors', async t => {
const { npm } = await loadMockNpm(t, {
config: { ...auth },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
registry.getTokens(tokens)
await t.rejects(npm.exec('token', ['rm', 'abcd']), {
message: /Token ID "abcd" was ambiguous/,
})
})
t.test('token revoke unknown token', async t => {
const { npm } = await loadMockNpm(t, {
config: { ...auth },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
registry.getTokens(tokens)
await t.rejects(npm.exec('token', ['rm', '0xnotreal']),
'Unknown token id or value 0xnotreal'
)
})
t.test('token create', async t => {
const cidr = ['10.0.0.0/8', '192.168.1.0/24']
const { npm, outputs } = await loadMockNpm(t, {
config: {
...auth,
cidr,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
const stdin = new stream.PassThrough()
stdin.write(`${password}\n`)
mockGlobals(t, {
'process.stdin': stdin,
'process.stdout': new stream.PassThrough(), // to quiet readline
}, { replace: true })
registry.createToken({ password, cidr })
await npm.exec('token', ['create'])
t.strictSame(outputs, [
'',
'Created publish token n3wt0k3n',
'with IP whitelist: 10.0.0.0/8,192.168.1.0/24',
])
})
t.test('token create read only', async t => {
const { npm, outputs } = await loadMockNpm(t, {
config: {
...auth,
'read-only': true,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
const stdin = new stream.PassThrough()
stdin.write(`${password}\n`)
mockGlobals(t, {
'process.stdin': stdin,
'process.stdout': new stream.PassThrough(), // to quiet readline
}, { replace: true })
registry.createToken({ readonly: true, password })
await npm.exec('token', ['create'])
t.strictSame(outputs, [
'',
'Created read only token n3wt0k3n',
])
})
t.test('token create json output', async t => {
const cidr = ['10.0.0.0/8', '192.168.1.0/24']
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
...auth,
json: true,
cidr,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
const stdin = new stream.PassThrough()
stdin.write(`${password}\n`)
mockGlobals(t, {
'process.stdin': stdin,
'process.stdout': new stream.PassThrough(), // to quiet readline
}, { replace: true })
registry.createToken({ password, cidr })
await npm.exec('token', ['create'])
const parsed = JSON.parse(joinedOutput())
t.match(
parsed,
{ token: 'n3wt0k3n', readonly: false, cidr_whitelist: cidr }
)
t.ok(parsed.created, 'also returns created')
})
t.test('token create parseable output', async t => {
const cidr = ['10.0.0.0/8', '192.168.1.0/24']
const { npm, outputs } = await loadMockNpm(t, {
config: {
...auth,
parseable: true,
cidr,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
const stdin = new stream.PassThrough()
stdin.write(`${password}\n`)
mockGlobals(t, {
'process.stdin': stdin,
'process.stdout': new stream.PassThrough(), // to quiet readline
}, { replace: true })
registry.createToken({ password, cidr })
await npm.exec('token', ['create'])
t.equal(outputs[1], 'token\tn3wt0k3n')
t.ok(outputs[2].startsWith('created\t'))
t.equal(outputs[3], 'readonly\tfalse')
t.equal(outputs[4], 'cidr_whitelist\t10.0.0.0/8,192.168.1.0/24')
})
t.test('token create ipv6 cidr', async t => {
const { npm } = await loadMockNpm(t, {
config: {
...auth,
cidr: '::1/128',
},
})
await t.rejects(npm.exec('token', ['create'], {
code: 'EINVALIDCIDR',
message: /CIDR whitelist can only contain IPv4 addresses, ::1\/128 is IPv6/,
}))
})
t.test('token create invalid cidr', async t => {
const { npm } = await loadMockNpm(t, {
config: {
...auth,
cidr: 'apple/cider',
},
})
await t.rejects(npm.exec('token', ['create'], {
code: 'EINVALIDCIDR',
message: 'CIDR whitelist contains invalid CIDR entry: apple/cider',
}))
})

View File

@ -0,0 +1,72 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const MockRegistry = require('@npmcli/mock-registry')
const token = 'test-auth-token'
const auth = { '//registry.npmjs.org/:_authToken': token }
const versions = ['1.0.0', '1.0.1', '1.0.1-pre']
t.test('no args', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('undeprecate', []),
{ code: 'EUSAGE' },
'logs usage'
)
})
t.test('undeprecate', async t => {
const { npm, logs, joinedOutput } = await loadMockNpm(t, { config: { ...auth } })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: token,
})
const manifest = registry.manifest({
name: 'foo',
versions,
})
await registry.package({ manifest, query: { write: true } })
registry.nock.put('/foo', body => {
for (const version of versions) {
if (body.versions[version].deprecated !== '') {
return false
}
}
return true
}).reply(200, {})
await npm.exec('undeprecate', ['foo'])
t.match(logs.notice, [
'undeprecating foo@1.0.0',
'undeprecating foo@1.0.1',
'undeprecating foo@1.0.1-pre',
])
t.match(joinedOutput(), '')
})
t.test('dry-run', async t => {
const { npm, logs, joinedOutput } = await loadMockNpm(t, { config: {
'dry-run': true,
...auth,
} })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: token,
})
const manifest = registry.manifest({
name: 'foo',
versions,
})
await registry.package({ manifest, query: { write: true } })
await npm.exec('undeprecate', ['foo'])
t.match(logs.notice, [
'undeprecating foo@1.0.0',
'undeprecating foo@1.0.1',
'undeprecating foo@1.0.1-pre',
])
t.match(joinedOutput(), '')
})

202
deps/npm/test/lib/commands/uninstall.js vendored Normal file
View File

@ -0,0 +1,202 @@
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 = {}) => {
const res = await _mockNpm(t, {
...opts,
mocks: {
...opts.mocks,
'{LIB}/utils/reify-finish.js': async () => {},
},
})
return {
...res,
uninstall: (args) => res.npm.exec('uninstall', args),
}
}
t.test('remove single installed lib', async t => {
const { uninstall, prefix } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'test-rm-single-lib',
version: '1.0.0',
dependencies: {
a: '*',
b: '*',
},
}),
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
}),
},
},
'package-lock.json': JSON.stringify({
name: 'test-rm-single-lib',
version: '1.0.0',
lockfileVersion: 2,
requires: true,
packages: {
'': {
name: 'test-rm-single-lib',
version: '1.0.0',
dependencies: {
a: '*',
},
},
'node_modules/a': {
version: '1.0.0',
},
'node_modules/b': {
version: '1.0.0',
},
},
dependencies: {
a: {
version: '1.0.0',
},
b: {
version: '1.0.0',
},
},
}),
},
})
const b = resolve(prefix, 'node_modules/b')
t.ok(fs.statSync(b))
await uninstall(['b'])
t.throws(() => fs.statSync(b), 'should have removed package from npm')
})
t.test('remove multiple installed libs', async t => {
const { uninstall, prefix } = await mockNpm(t, {
prefixDir: {
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
}),
},
},
'package-lock.json': JSON.stringify({
name: 'test-rm-single-lib',
version: '1.0.0',
lockfileVersion: 2,
requires: true,
packages: {
'': {
name: 'test-rm-single-lib',
version: '1.0.0',
dependencies: {
a: '*',
},
},
'node_modules/a': {
version: '1.0.0',
},
'node_modules/b': {
version: '1.0.0',
},
},
dependencies: {
a: {
version: '1.0.0',
},
b: {
version: '1.0.0',
},
},
}),
},
})
const a = resolve(prefix, 'node_modules/a')
const b = resolve(prefix, 'node_modules/b')
t.ok(fs.statSync(a))
t.ok(fs.statSync(b))
await uninstall(['b'])
t.throws(() => fs.statSync(a), 'should have removed a package from nm')
t.throws(() => fs.statSync(b), 'should have removed b package from nm')
})
t.test('no args local', async t => {
const { uninstall } = await mockNpm(t)
await t.rejects(
uninstall([]),
/Must provide a package name to remove/,
'should throw package name required error'
)
})
t.test('no args global', async t => {
const { uninstall, npm } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
}),
},
globalPrefixDir: {
node_modules: {
a: t.fixture('symlink', '../../prefix'),
},
},
config: { global: true },
})
const a = resolve(npm.globalDir, 'a')
t.ok(fs.statSync(a))
await uninstall([])
t.throws(() => fs.statSync(a), 'should have removed global nm symlink')
})
t.test('no args global but no package.json', async t => {
const { uninstall } = await mockNpm(t, {
config: { global: true },
})
await t.rejects(
uninstall([]),
/npm uninstall/
)
})
t.test('non ENOENT error reading from localPrefix package.json', async t => {
const { uninstall } = await mockNpm(t, {
config: { global: true },
prefixDir: { 'package.json': 'not[json]' },
})
await t.rejects(
uninstall([]),
{ code: 'EJSONPARSE' },
'should throw non ENOENT error'
)
})

653
deps/npm/test/lib/commands/unpublish.js vendored Normal file
View File

@ -0,0 +1,653 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const MockRegistry = require('@npmcli/mock-registry')
const user = 'test-user'
const pkg = 'test-package'
const auth = { '//registry.npmjs.org/:_authToken': 'test-auth-token' }
t.test('no args --force success', async t => {
const { joinedOutput, npm } = await loadMockNpm(t, {
config: {
force: true,
...auth,
},
prefixDir: {
'package.json': JSON.stringify({
name: pkg,
version: '1.0.0',
}, null, 2),
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
const manifest = registry.manifest({ name: pkg })
await registry.package({ manifest, query: { write: true }, times: 2 })
registry.unpublish({ manifest })
await npm.exec('unpublish', [])
t.equal(joinedOutput(), '- test-package')
})
t.test('no args --force missing package.json', async t => {
const { npm } = await loadMockNpm(t, {
config: {
force: true,
},
})
await t.rejects(
npm.exec('unpublish', []),
{ code: 'EUSAGE' },
'should throw usage instructions on missing package.json'
)
})
t.test('no args --force error reading package.json', async t => {
const { npm } = await loadMockNpm(t, {
config: {
force: true,
},
prefixDir: {
'package.json': '{ not valid json ]',
},
})
await t.rejects(
npm.exec('unpublish', []),
/Invalid package.json/,
'should throw error from reading package.json'
)
})
t.test('with args --force error reading package.json', async t => {
const { npm } = await loadMockNpm(t, {
config: {
force: true,
},
prefixDir: {
'package.json': '{ not valid json ]',
},
})
await t.rejects(
npm.exec('unpublish', [pkg]),
/Invalid package.json/,
'should throw error from reading package.json'
)
})
t.test('no force entire project', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('unpublish', ['@npmcli/unpublish-test']),
/Refusing to delete entire project/
)
})
t.test('too many args', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('unpublish', ['a', 'b']),
{ code: 'EUSAGE' },
'should throw usage instructions if too many args'
)
})
t.test('range', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('unpublish', ['a@>1.0.0']),
{ code: 'EUSAGE' },
/single version/
)
})
t.test('tag', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('unpublish', ['a@>1.0.0']),
{ code: 'EUSAGE' },
/single version/
)
})
t.test('unpublish <pkg>@version not the last version', async t => {
const { joinedOutput, npm } = await loadMockNpm(t, {
config: {
force: true,
...auth,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
const manifest = registry.manifest({
name: pkg,
packuments: ['1.0.0', '1.0.1'],
})
await registry.package({ manifest, query: { write: true }, times: 3 })
registry.nock.put(`/${pkg}/-rev/${manifest._rev}`, body => {
// sets latest and deletes version 1.0.1
return body['dist-tags'].latest === '1.0.0' && body.versions['1.0.1'] === undefined
}).reply(201)
.intercept(`/${pkg}/-/${pkg}-1.0.1.tgz/-rev/${manifest._rev}`, 'DELETE').reply(201)
await npm.exec('unpublish', ['test-package@1.0.1'])
t.equal(joinedOutput(), '- test-package@1.0.1')
})
t.test('unpublish <pkg>@version last version', async t => {
const { npm } = await loadMockNpm(t, {
config: {
...auth,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
const manifest = registry.manifest({ name: pkg })
await registry.package({ manifest, query: { write: true } })
await t.rejects(
npm.exec('unpublish', ['test-package@1.0.0']),
/Refusing to delete the last version of the package/
)
})
t.test('no version found in package.json no force', async t => {
const { npm } = await loadMockNpm(t, {
config: {
...auth,
},
prefixDir: {
'package.json': JSON.stringify({
name: pkg,
}, null, 2),
},
})
await t.rejects(
npm.exec('unpublish', []),
/Refusing to delete entire project/
)
})
t.test('no version found in package.json with force', async t => {
const { joinedOutput, npm } = await loadMockNpm(t, {
config: {
force: true,
...auth,
},
prefixDir: {
'package.json': JSON.stringify({
name: pkg,
}, null, 2),
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
const manifest = registry.manifest({ name: pkg })
await registry.package({ manifest, query: { write: true }, times: 2 })
registry.unpublish({ manifest })
await npm.exec('unpublish', [])
t.equal(joinedOutput(), '- test-package')
})
t.test('unpublish <pkg> --force no version set', async t => {
const { joinedOutput, npm } = await loadMockNpm(t, {
config: {
force: true,
...auth,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
const manifest = registry.manifest({ name: pkg })
await registry.package({ manifest, query: { write: true }, times: 2 })
registry.unpublish({ manifest })
await npm.exec('unpublish', ['test-package'])
t.equal(joinedOutput(), '- test-package')
})
t.test('silent', async t => {
const { joinedOutput, npm } = await loadMockNpm(t, {
config: {
force: true,
loglevel: 'silent',
...auth,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
const manifest = registry.manifest({
name: pkg,
packuments: ['1.0.0', '1.0.1'],
})
await registry.package({ manifest, query: { write: true }, times: 3 })
registry.nock.put(`/${pkg}/-rev/${manifest._rev}`, body => {
// sets latest and deletes version 1.0.1
return body['dist-tags'].latest === '1.0.0' && body.versions['1.0.1'] === undefined
}).reply(201)
.delete(`/${pkg}/-/${pkg}-1.0.1.tgz/-rev/${manifest._rev}`).reply(201)
await npm.exec('unpublish', ['test-package@1.0.1'])
t.equal(joinedOutput(), '')
})
t.test('workspaces', async t => {
const prefixDir = {
'package.json': JSON.stringify({
name: pkg,
version: '1.0.0',
workspaces: ['workspace-a', 'workspace-b', 'workspace-c'],
}, null, 2),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.2.3-a',
repository: 'http://repo.workspace-a/',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '1.2.3-b',
repository: 'https://github.com/npm/workspace-b',
}),
},
'workspace-c': {
'package.json': JSON.stringify({
name: 'workspace-n',
version: '1.2.3-n',
}),
},
}
t.test('with package name no force', async t => {
const { npm } = await loadMockNpm(t, {
config: {
workspace: ['workspace-a'],
},
prefixDir,
})
await t.rejects(
npm.exec('unpublish', ['workspace-a']),
/Refusing to delete entire project/
)
})
t.test('all workspaces last version --force', async t => {
const { joinedOutput, npm } = await loadMockNpm(t, {
config: {
workspaces: true,
force: true,
...auth,
},
prefixDir,
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
const manifestA = registry.manifest({ name: 'workspace-a', versions: ['1.2.3-a'] })
const manifestB = registry.manifest({ name: 'workspace-b', versions: ['1.2.3-b'] })
const manifestN = registry.manifest({ name: 'workspace-n', versions: ['1.2.3-n'] })
await registry.package({ manifest: manifestA, query: { write: true }, times: 2 })
await registry.package({ manifest: manifestB, query: { write: true }, times: 2 })
await registry.package({ manifest: manifestN, query: { write: true }, times: 2 })
registry.nock.delete(`/workspace-a/-rev/${manifestA._rev}`).reply(201)
.delete(`/workspace-b/-rev/${manifestB._rev}`).reply(201)
.delete(`/workspace-n/-rev/${manifestN._rev}`).reply(201)
await npm.exec('unpublish', [])
t.equal(joinedOutput(), '- workspace-a\n- workspace-b\n- workspace-n')
})
})
t.test('dryRun with spec', async t => {
const { joinedOutput, npm } = await loadMockNpm(t, {
config: {
'dry-run': true,
...auth,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
const manifest = registry.manifest({
name: pkg,
packuments: ['1.0.0', '1.0.1'],
})
await registry.package({ manifest, query: { write: true } })
await npm.exec('unpublish', ['test-package@1.0.1'])
t.equal(joinedOutput(), '- test-package@1.0.1')
})
t.test('dryRun with no args', async t => {
const { joinedOutput, npm } = await loadMockNpm(t, {
config: {
force: true,
'dry-run': true,
...auth,
},
prefixDir: {
'package.json': JSON.stringify({
name: pkg,
version: '1.0.0',
}, null, 2),
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
const manifest = registry.manifest({
name: pkg,
packuments: ['1.0.0', '1.0.1'],
})
await registry.package({ manifest, query: { write: true } })
await npm.exec('unpublish', [])
t.equal(joinedOutput(), '- test-package@1.0.0')
})
t.test('publishConfig no spec', async t => {
const alternateRegistry = 'https://other.registry.npmjs.org'
const { logs, joinedOutput, npm } = await loadMockNpm(t, {
config: {
force: true,
'//other.registry.npmjs.org/:_authToken': 'test-other-token',
},
prefixDir: {
'package.json': JSON.stringify({
name: pkg,
version: '1.0.0',
publishConfig: {
other: 'not defined',
registry: alternateRegistry,
},
}, null, 2),
},
})
const registry = new MockRegistry({
tap: t,
registry: alternateRegistry,
authorization: 'test-other-token',
})
const manifest = registry.manifest({ name: pkg })
await registry.package({ manifest, query: { write: true }, times: 2 })
registry.unpublish({ manifest })
await npm.exec('unpublish', [])
t.equal(joinedOutput(), '- test-package')
t.same(logs.warn, [
'using --force Recommended protections disabled.',
'Unknown publishConfig config "other". This will stop working in the next major version of npm.',
])
})
t.test('prioritize CLI flags over publishConfig no spec', async t => {
const alternateRegistry = 'https://other.registry.npmjs.org'
const publishConfig = { registry: 'http://publishconfig' }
const { joinedOutput, npm } = await loadMockNpm(t, {
config: {
force: true,
'//other.registry.npmjs.org/:_authToken': 'test-other-token',
},
prefixDir: {
'package.json': JSON.stringify({
name: pkg,
version: '1.0.0',
publishConfig,
}, null, 2),
},
argv: ['--registry', alternateRegistry],
})
const registry = new MockRegistry({
tap: t,
registry: alternateRegistry,
authorization: 'test-other-token',
})
const manifest = registry.manifest({ name: pkg })
await registry.package({ manifest, query: { write: true }, times: 2 })
registry.unpublish({ manifest })
await npm.exec('unpublish', [])
t.equal(joinedOutput(), '- test-package')
})
t.test('publishConfig with spec', async t => {
const alternateRegistry = 'https://other.registry.npmjs.org'
const { joinedOutput, npm } = await loadMockNpm(t, {
config: {
force: true,
'//other.registry.npmjs.org/:_authToken': 'test-other-token',
},
prefixDir: {
'package.json': JSON.stringify({
name: pkg,
version: '1.0.0',
publishConfig: {
registry: alternateRegistry,
},
}, null, 2),
},
})
const registry = new MockRegistry({
tap: t,
registry: alternateRegistry,
authorization: 'test-other-token',
})
const manifest = registry.manifest({ name: pkg })
await registry.package({ manifest, query: { write: true }, times: 2 })
registry.unpublish({ manifest })
await npm.exec('unpublish', ['test-package'])
t.equal(joinedOutput(), '- test-package')
})
t.test('scoped registry config', async t => {
const scopedPkg = `@npm/test-package`
const alternateRegistry = 'https://other.registry.npmjs.org'
const { npm } = await loadMockNpm(t, {
config: {
force: true,
'@npm:registry': alternateRegistry,
'//other.registry.npmjs.org/:_authToken': 'test-other-token',
},
prefixDir: {
'package.json': JSON.stringify({
name: pkg,
version: '1.0.0',
publishConfig: {
registry: alternateRegistry,
},
}, null, 2),
},
})
const registry = new MockRegistry({
tap: t,
registry: alternateRegistry,
authorization: 'test-other-token',
})
const manifest = registry.manifest({ name: scopedPkg })
await registry.package({ manifest, query: { write: true }, times: 2 })
registry.unpublish({ manifest })
await npm.exec('unpublish', [scopedPkg])
})
t.test('completion', async t => {
const { npm, unpublish } = await loadMockNpm(t, {
command: 'unpublish',
config: {
...auth,
},
})
const testComp =
async (t, { argv, partialWord, expect, title }) => {
const res = await unpublish.completion(
{ conf: { argv: { remain: argv } }, partialWord }
)
t.strictSame(res, expect, title || argv.join(' '))
}
t.test('completing with multiple versions from the registry', async t => {
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
const manifest = registry.manifest({
name: pkg,
packuments: ['1.0.0', '1.0.1'],
})
await registry.package({ manifest, query: { write: true } })
registry.whoami({ username: user })
registry.getPackages({ team: user, packages: { [pkg]: 'write' } })
await testComp(t, {
argv: ['npm', 'unpublish'],
partialWord: 'test-package',
expect: [
'test-package@1.0.0',
'test-package@1.0.1',
],
})
})
t.test('no versions retrieved', async t => {
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
const manifest = registry.manifest({ name: pkg })
manifest.versions = {}
await registry.package({ manifest, query: { write: true } })
registry.whoami({ username: user })
registry.getPackages({ team: user, packages: { [pkg]: 'write' } })
await testComp(t, {
argv: ['npm', 'unpublish'],
partialWord: pkg,
expect: [
pkg,
],
title: 'should autocomplete package name only',
})
})
t.test('packages starting with same letters', async t => {
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
registry.whoami({ username: user })
registry.getPackages({ team: user,
packages: {
[pkg]: 'write',
[`${pkg}a`]: 'write',
[`${pkg}b`]: 'write',
} })
await testComp(t, {
argv: ['npm', 'unpublish'],
partialWord: pkg,
expect: [
pkg,
`${pkg}a`,
`${pkg}b`,
],
})
})
t.test('no packages retrieved', async t => {
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
registry.whoami({ username: user })
registry.getPackages({ team: user, packages: {} })
await testComp(t, {
argv: ['npm', 'unpublish'],
partialWord: 'pkg',
expect: [],
title: 'should have no autocompletion',
})
})
t.test('no pkg name to complete', async t => {
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
registry.whoami({ username: user })
registry.getPackages({ team: user,
packages: {
[pkg]: 'write',
[`${pkg}a`]: 'write',
} })
await testComp(t, {
argv: ['npm', 'unpublish'],
partialWord: undefined,
expect: [pkg, `${pkg}a`],
title: 'should autocomplete with available package names from user',
})
})
t.test('logged out user', async t => {
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
registry.whoami({ responseCode: 404 })
await testComp(t, {
argv: ['npm', 'unpublish'],
partialWord: pkg,
expect: [],
})
})
t.test('too many args', async t => {
await testComp(t, {
argv: ['npm', 'unpublish', 'foo'],
partialWord: undefined,
expect: [],
})
})
})

62
deps/npm/test/lib/commands/unstar.js vendored Normal file
View File

@ -0,0 +1,62 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm.js')
const MockRegistry = require('@npmcli/mock-registry')
const pkgName = '@npmcli/test-package'
const authToken = 'test-auth-token'
const username = 'test-user'
const auth = { '//registry.npmjs.org/:_authToken': authToken }
t.test('no args', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('unstar', []),
{ code: 'EUSAGE' },
'should throw usage error'
)
})
t.test('unstar a package unicode:false', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: { unicode: false, ...auth },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
const manifest = registry.manifest({ name: pkgName, users: { [username]: true } })
await registry.package({ manifest, query: { write: true } })
registry.whoami({ username })
registry.star(manifest, {})
await npm.exec('unstar', [pkgName])
t.equal(
joinedOutput(),
'( ) @npmcli/test-package',
'should output unstarred package msg'
)
})
t.test('unstar a package unicode:true', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: { unicode: true, ...auth },
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: authToken,
})
const manifest = registry.manifest({ name: pkgName, users: { [username]: true } })
await registry.package({ manifest, query: { write: true } })
registry.whoami({ username })
registry.star(manifest, {})
await npm.exec('unstar', [pkgName])
t.equal(
joinedOutput(),
'☆ @npmcli/test-package',
'should output unstarred package msg'
)
})

81
deps/npm/test/lib/commands/update.js vendored Normal file
View File

@ -0,0 +1,81 @@
const t = require('tap')
const _mockNpm = require('../../fixtures/mock-npm')
// XXX: this test has been refactored to use the new mockNpm
// but it still only asserts the options passed to arborist.
// TODO: make this really test npm update scenarios
const mockUpdate = async (t, { exec = [], ...opts } = {}) => {
let ctor = null
let reify = null
let finish = null
const res = await _mockNpm(t, {
...opts,
mocks: {
'@npmcli/arborist': class Arborist {
constructor (o) {
ctor = o
}
reify (o) {
reify = o
}
},
'{LIB}/utils/reify-finish.js': (_, o) => {
finish = o
},
},
})
await res.npm.exec('update', exec)
return {
...res,
ctor,
reify,
finish,
}
}
t.test('no args', async t => {
const { ctor, reify, finish, prefix } = await mockUpdate(t)
t.equal(ctor.path, prefix, 'path')
t.equal(ctor.save, false, 'should default to save=false')
t.equal(ctor.workspaces, undefined, 'workspaces')
t.equal(reify.update, true, 'should update all deps')
t.equal(finish.constructor.name, 'Arborist')
})
t.test('with args', async t => {
const { ctor, reify } = await mockUpdate(t, {
config: { save: true },
exec: ['ipt'],
})
t.equal(ctor.save, true, 'save')
t.strictSame(reify.update, ['ipt'], 'ipt')
})
t.test('update --depth=<number>', async t => {
const { logs } = await mockUpdate(t, {
config: { depth: 1 },
})
t.match(
logs.warn.byTitle('update')[0],
/The --depth option no longer has any effect/,
'should print expected warning message'
)
})
t.test('update --global', async t => {
const { ctor, globalPrefix } = await mockUpdate(t, {
config: { global: true },
})
t.match(ctor.path, globalPrefix)
t.ok(ctor.path.startsWith(globalPrefix))
})

365
deps/npm/test/lib/commands/version.js vendored Normal file
View File

@ -0,0 +1,365 @@
const { readFileSync, statSync } = require('node:fs')
const { resolve } = require('node:path')
const t = require('tap')
const _mockNpm = require('../../fixtures/mock-npm')
const mockGlobals = require('@npmcli/mock-globals')
const mockNpm = async (t, opts = {}) => {
const res = await _mockNpm(t, {
...opts,
command: 'version',
mocks: {
...opts.mocks,
'{ROOT}/package.json': { version: '1.0.0' },
},
})
return {
...res,
result: () => res.outputs[0],
}
}
t.test('node@1', async t => {
mockGlobals(t, { 'process.versions': { node: '1.0.0' } }, { replace: true })
t.test('no args', async t => {
const { version, result } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify({
name: 'test-version-no-args',
version: '3.2.1',
}),
},
})
await version.exec([])
t.strictSame(
result(),
"{ 'test-version-no-args': '3.2.1', npm: '1.0.0', node: '1.0.0' }",
'should output expected values for various versions in npm'
)
})
t.test('too many args', async t => {
const { version } = await mockNpm(t)
await t.rejects(
version.exec(['foo', 'bar']),
/npm version/,
'should throw usage instructions error'
)
})
t.test('completion', async t => {
const { version } = await mockNpm(t)
const testComp = async (argv, expect) => {
const res = await version.completion({ conf: { argv: { remain: argv } } })
t.strictSame(res, expect, argv.join(' '))
}
await testComp(
['npm', 'version'],
['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease', 'from-git']
)
await testComp(['npm', 'version', 'major'], [])
})
t.test('failure reading package.json', async t => {
const { version, result } = await mockNpm(t)
await version.exec([])
t.strictSame(
result(),
`{ npm: '1.0.0', node: '1.0.0' }`,
'should not have package name on returning object'
)
})
})
t.test('empty versions', async t => {
mockGlobals(t, { 'process.versions': {} }, { replace: true })
t.test('--json option', async t => {
const { version, result } = await mockNpm(t, {
config: { json: true },
})
await version.exec([])
t.same(result(), '{\n "npm": "1.0.0"\n}', 'should return json stringified result')
})
t.test('with one arg', async t => {
const { version, result } = await mockNpm(t, {
mocks: {
libnpmversion: () => '4.0.0',
},
})
await version.exec(['major'])
t.same(result(), 'v4.0.0', 'outputs the new version prefixed by the tagVersionPrefix')
})
t.test('workspaces', async t => {
t.test('no args, all workspaces', async t => {
const { version, result } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify(
{
name: 'workspaces-test',
version: '1.0.0',
workspaces: ['workspace-a', 'workspace-b'],
},
null,
2
),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.0.0',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '1.0.0',
}),
},
},
config: { workspaces: true },
})
await version.exec([])
t.same(
result(),
`{
'workspace-a': '1.0.0',
'workspace-b': '1.0.0',
'workspaces-test': '1.0.0',
npm: '1.0.0'
}`,
'outputs includes main package and workspace versions'
)
})
t.test('no args, single workspaces', async t => {
const { version, result } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify(
{
name: 'workspaces-test',
version: '1.0.0',
workspaces: ['workspace-a', 'workspace-b'],
},
null,
2
),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.0.0',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '1.0.0',
}),
},
},
config: {
workspace: 'workspace-a',
},
})
await version.exec([])
t.same(
result(),
"{ 'workspace-a': '1.0.0', 'workspaces-test': '1.0.0', npm: '1.0.0' }",
'outputs includes main package and requested workspace versions'
)
})
t.test('no args, all workspaces, workspace with missing name or version', async t => {
const { version, result } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify(
{
name: 'workspaces-test',
version: '1.0.0',
workspaces: ['workspace-a', 'workspace-b', 'workspace-c'],
},
null,
2
),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.0.0',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
}),
},
'workspace-c': {
'package.json': JSON.stringify({
version: '1.0.0',
}),
},
},
config: { workspaces: true },
})
await version.exec([])
t.same(
result(),
"{ 'workspace-a': '1.0.0', 'workspaces-test': '1.0.0', npm: '1.0.0' }",
'outputs includes main package and valid workspace versions'
)
})
t.test('with one arg, all workspaces', async t => {
const { version, outputs, prefix } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify(
{
name: 'workspaces-test',
version: '1.0.0',
workspaces: ['workspace-a', 'workspace-b'],
},
null,
2
),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.0.0',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '1.0.0',
}),
},
},
config: { workspaces: true },
})
await version.exec(['major'])
t.same(
outputs.slice(0, 4),
['workspace-a', 'v2.0.0', 'workspace-b', 'v2.0.0'],
'outputs the new version for only the workspaces prefixed by the tagVersionPrefix'
)
t.matchSnapshot(readFileSync(resolve(prefix, 'package-lock.json'), 'utf8'))
})
t.test('with one arg, all workspaces, saves package.json', async t => {
const { version, outputs, prefix } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify(
{
name: 'workspaces-test',
version: '1.0.0',
workspaces: ['workspace-a', 'workspace-b'],
dependencies: {
'workspace-a': '^1.0.0',
'workspace-b': '^1.0.0',
},
},
null,
2
),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.0.0',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '1.0.0',
}),
},
},
config: {
save: true,
workspaces: true,
},
})
await version.exec(['major'])
t.same(
outputs.slice(0, 4),
['workspace-a', 'v2.0.0', 'workspace-b', 'v2.0.0'],
'outputs the new version for only the workspaces prefixed by the tagVersionPrefix'
)
t.matchSnapshot(readFileSync(resolve(prefix, 'package-lock.json'), 'utf8'))
})
t.test('too many args', async t => {
const { version } = await mockNpm(t, { config: { workspaces: true } })
await t.rejects(
version.exec(['foo', 'bar']),
/npm version/,
'should throw usage instructions error'
)
})
t.test('no workspaces-update', async t => {
const { version, outputs, prefix } = await mockNpm(t, {
prefixDir: {
'package.json': JSON.stringify(
{
name: 'workspaces-test',
version: '1.0.0',
workspaces: ['workspace-a', 'workspace-b'],
},
null,
2
),
'workspace-a': {
'package.json': JSON.stringify({
name: 'workspace-a',
version: '1.0.0',
}),
},
'workspace-b': {
'package.json': JSON.stringify({
name: 'workspace-b',
version: '1.0.0',
}),
},
},
mocks: {
libnpmversion: () => {
return '2.0.0'
},
},
config: {
workspaces: true,
'workspaces-update': false,
},
})
await version.exec(['major'])
t.same(
outputs.slice(0, 4),
['workspace-a', 'v2.0.0', 'workspace-b', 'v2.0.0'],
'outputs the new version for only the workspaces prefixed by the tagVersionPrefix'
)
t.throws(
() => statSync(resolve(prefix, 'package-lock.json')),
'should not have a lockfile since have not reified'
)
})
})
})

771
deps/npm/test/lib/commands/view.js vendored Normal file
View File

@ -0,0 +1,771 @@
const t = require('tap')
const { load: _loadMockNpm } = require('../../fixtures/mock-npm.js')
t.cleanSnapshot = str => str
.replace(/(published ).*?( ago)/g, '$1{TIME}$2')
// run the same as tap does when running directly with node
process.stdout.columns = undefined
// 3 days. its never yesterday and never a week ago
const yesterday = new Date(Date.now() - 1000 * 60 * 60 * 24 * 3)
const packument = (nv, opts) => {
if (!opts.fullMetadata) {
throw new Error('must fetch fullMetadata')
}
if (!opts.preferOnline) {
throw new Error('must fetch with preferOnline')
}
const mocks = {
red: {
_id: 'red@1.0.1',
name: 'red',
'dist-tags': {
'1.0.1': {},
},
time: {
unpublished: {
time: '2012-12-20T00:00:00.000Z',
},
},
},
blue: {
_id: 'blue',
name: 'blue',
'dist-tags': {
v1: '1.0.0',
next: '1.0.1',
prev: '1.0.0',
latest: '1.0.0',
a: '1.0.0',
c: '1.0.0',
b: '1.0.0',
d: '1.0.0',
f: '1.0.1',
g: '1.0.1',
h: '1.0.1',
e: '1.0.1',
z: '1.0.0',
x: '1.0.1',
y: '1.0.0',
},
time: {
'1.0.0': yesterday,
'1.0.1': '2012-12-20T00:00:00.000Z',
},
versions: {
'1.0.0': {
name: 'blue',
version: '1.0.0',
dist: {
shasum: '123',
tarball: 'http://hm.blue.com/1.0.0.tgz',
fileCount: 1,
},
},
'1.0.1': {
name: 'blue',
version: '1.0.1',
dist: {
shasum: '124',
tarball: 'http://hm.blue.com/1.0.1.tgz',
integrity: '---',
fileCount: 1,
unpackedSize: 1000,
},
},
},
},
cyan: {
_npmUser: {
name: 'claudia',
email: 'claudia@cyan.com',
},
name: 'cyan',
'dist-tags': {
latest: '1.0.0',
},
versions: {
'1.0.0': {
version: '1.0.0',
name: 'cyan',
dist: {
shasum: '123',
tarball: 'http://hm.cyan.com/1.0.0.tgz',
integrity: '---',
fileCount: 1,
unpackedSize: 1000000,
},
},
'1.0.1': {},
},
},
brown: {
name: 'brown',
},
yellow: {
_id: 'yellow',
name: 'yellow',
author: {
name: 'foo',
email: 'foo@yellow.com',
twitter: 'foo',
},
empty: '',
readme: 'a very useful readme',
versions: {
'1.0.0': {
version: '1.0.0',
author: 'claudia',
readme: 'a very useful readme',
maintainers: [
{ name: 'claudia', email: 'c@yellow.com', twitter: 'cyellow' },
{ name: 'isaacs', email: 'i@yellow.com', twitter: 'iyellow' },
],
},
'1.0.1': {
version: '1.0.1',
author: 'claudia',
},
'1.0.2': {
version: '1.0.2',
author: 'claudia',
},
},
},
purple: {
name: 'purple',
versions: {
'1.0.0': {
foo: 1,
maintainers: [
{ name: 'claudia' },
],
},
'1.0.1': {},
},
},
green: {
_id: 'green',
name: 'green',
'dist-tags': {
latest: '1.0.0',
},
maintainers: [
{ name: 'claudia', email: 'c@yellow.com', twitter: 'cyellow' },
{ name: 'isaacs', email: 'i@yellow.com', twitter: 'iyellow' },
],
keywords: ['colors', 'green', 'crayola'],
versions: {
'1.0.0': {
_id: 'green',
version: '1.0.0',
description: 'green is a very important color',
bugs: {
url: 'http://bugs.green.com',
},
deprecated: true,
repository: {
url: 'http://repository.green.com',
},
license: { type: 'ACME' },
bin: {
green: 'bin/green.js',
},
dependencies: {
red: '1.0.0',
yellow: '1.0.0',
},
dist: {
shasum: '123',
tarball: 'http://hm.green.com/1.0.0.tgz',
integrity: '---',
fileCount: 1,
unpackedSize: 1000000000,
},
},
'1.0.1': {},
},
},
black: {
name: 'black',
'dist-tags': {
latest: '1.0.0',
},
versions: {
'1.0.0': {
version: '1.0.0',
bugs: 'http://bugs.black.com',
license: {},
dependencies: (() => {
const deps = {}
for (let i = 0; i < 25; i++) {
deps[i] = '1.0.0'
}
return deps
})(),
dist: {
shasum: '123',
tarball: 'http://hm.black.com/1.0.0.tgz',
integrity: '---',
fileCount: 1,
unpackedSize: 1,
},
},
'1.0.1': {},
},
},
pink: {
name: 'pink',
'dist-tags': {
latest: '1.0.0',
},
versions: {
'1.0.0': {
version: '1.0.0',
maintainers: [
{ name: 'claudia', url: 'http://c.pink.com' },
{ name: 'isaacs', url: 'http://i.pink.com' },
],
repository: 'http://repository.pink.com',
license: {},
dist: {
shasum: '123',
tarball: 'http://hm.pink.com/1.0.0.tgz',
integrity: '---',
fileCount: 1,
unpackedSize: 1,
},
},
'1.0.1': {},
},
},
orange: {
name: 'orange',
'dist-tags': {
latest: '1.0.0',
},
versions: {
'1.0.0': {
version: '1.0.0',
homepage: 'http://hm.orange.com',
license: {},
dist: {
shasum: '123',
tarball: 'http://hm.orange.com/1.0.0.tgz',
integrity: '---',
fileCount: 1,
unpackedSize: 1,
},
},
'1.0.1': {},
'100000000000000000.0.0': {
},
},
},
'single-version': {
_id: 'single-version',
name: 'single-version',
'dist-tags': {
latest: '1.0.0',
},
versions: {
'1.0.0': {
name: 'single-version',
version: '1.0.0',
dist: {
shasum: '123',
tarball: 'http://hm.single-version.com/1.0.0.tgz',
fileCount: 1,
},
},
},
},
// this is a packaged named error which will conflict with
// the error key in json output
error: {
_id: 'error',
name: 'error',
'dist-tags': {
latest: '1.0.0',
},
versions: {
'1.0.0': {
name: 'error',
version: '1.0.0',
dist: {
shasum: '123',
tarball: 'http://hm.error.com/1.0.0.tgz',
fileCount: 1,
},
},
},
},
}
if (nv.type === 'git') {
return mocks[nv.hosted.project]
}
if (nv.raw === './blue') {
return mocks.blue
}
if (mocks[nv.name]) {
return mocks[nv.name]
}
if (nv.name === 'unknown-error') {
throw new Error('Unknown error')
}
throw Object.assign(new Error('404'), { code: 'E404' })
}
const loadMockNpm = async function (t, opts = {}) {
const mockNpm = await _loadMockNpm(t, {
command: 'view',
mocks: {
pacote: {
packument,
},
},
...opts,
config: {
color: 'always',
...opts.config,
},
})
return mockNpm
}
t.test('package from git', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { unicode: false } })
await view.exec(['https://github.com/npm/green'])
t.matchSnapshot(joinedOutput())
})
t.test('deprecated package with license, bugs, repository and other fields', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { unicode: false } })
await view.exec(['green@1.0.0'])
t.matchSnapshot(joinedOutput())
})
t.test('deprecated package with unicode', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { unicode: true } })
await view.exec(['green@1.0.0'])
t.matchSnapshot(joinedOutput())
})
t.test('package with more than 25 deps', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { unicode: false } })
await view.exec(['black@1.0.0'])
t.matchSnapshot(joinedOutput())
})
t.test('package with maintainers info as object', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { unicode: false } })
await view.exec(['pink@1.0.0'])
t.matchSnapshot(joinedOutput())
})
t.test('package with homepage', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { unicode: false } })
await view.exec(['orange@1.0.0'])
t.matchSnapshot(joinedOutput())
})
t.test('package with invalid version', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { unicode: false } })
await view.exec(['orange', 'versions'])
t.matchSnapshot(joinedOutput())
})
t.test('package with no versions', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { unicode: false } })
await view.exec(['brown'])
t.equal(joinedOutput(), '', 'no info to display')
})
t.test('package with no repo or homepage', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { unicode: false } })
await view.exec(['blue@1.0.0'])
t.matchSnapshot(joinedOutput())
})
t.test('package with semver range', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { unicode: false } })
await view.exec(['blue@^1.0.0'])
t.matchSnapshot(joinedOutput())
})
t.test('package with no modified time', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { unicode: false } })
await view.exec(['cyan@1.0.0'])
t.matchSnapshot(joinedOutput())
})
t.test('package with --json and semver range', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { json: true } })
await view.exec(['cyan@^1.0.0'])
t.matchSnapshot(joinedOutput())
})
t.test('package with --json and no versions', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { json: true } })
await view.exec(['brown'])
t.equal(joinedOutput(), '', 'no info to display')
})
t.test('package with --json and single string arg', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { json: true } })
await view.exec(['blue', 'dist-tags.latest'])
t.equal(JSON.parse(joinedOutput()), '1.0.0', 'no info to display')
})
t.test('package with single version', async t => {
t.test('full json', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { json: true } })
await view.exec(['single-version'])
t.matchSnapshot(joinedOutput())
})
t.test('json and versions arg', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { json: true } })
await view.exec(['single-version', 'versions'])
const parsed = JSON.parse(joinedOutput())
t.strictSame(parsed, ['1.0.0'], 'does not unwrap single item arrays in json')
})
t.test('no json and versions arg', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { config: { json: false } })
await view.exec(['single-version', 'versions'])
t.strictSame(joinedOutput(), '1.0.0', 'unwraps single item arrays in basic mode')
})
})
t.test('package in cwd', async t => {
const prefixDir = {
'package.json': JSON.stringify({
name: 'blue',
version: '1.0.0',
}, null, 2),
}
t.test('specific version', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { prefixDir })
await view.exec(['.@1.0.0'])
t.matchSnapshot(joinedOutput())
})
t.test('non-specific version', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { prefixDir })
await view.exec(['.'])
t.matchSnapshot(joinedOutput())
})
t.test('directory', async t => {
const { view, joinedOutput } = await loadMockNpm(t, { prefixDir })
await view.exec(['./blue'])
t.matchSnapshot(joinedOutput())
})
})
t.test('specific field names', async t => {
const { view, joinedOutput, clearOutput } = await loadMockNpm(t, { config: { color: false } })
t.afterEach(() => clearOutput())
t.test('readme', async t => {
await view.exec(['yellow@1.0.0', 'readme'])
t.matchSnapshot(joinedOutput())
})
t.test('several fields', async t => {
await view.exec(['yellow@1.0.0', 'name', 'version', 'foo[bar]'])
t.matchSnapshot(joinedOutput())
})
t.test('several fields with several versions', async t => {
await view.exec(['yellow@1.x.x', 'author'])
t.matchSnapshot(joinedOutput())
})
t.test('nested field with brackets', async t => {
await view.exec(['orange@1.0.0', 'dist[shasum]'])
t.matchSnapshot(joinedOutput())
})
t.test('maintainers with email', async t => {
await view.exec(['yellow@1.0.0', 'maintainers', 'name'])
t.matchSnapshot(joinedOutput())
})
t.test('maintainers with url', async t => {
await view.exec(['pink@1.0.0', 'maintainers'])
t.matchSnapshot(joinedOutput())
})
t.test('unknown nested field ', async t => {
await view.exec(['yellow@1.0.0', 'dist.foobar'])
t.equal(joinedOutput(), '', 'no info to display')
})
t.test('array field - 1 element', async t => {
await view.exec(['purple@1.0.0', 'maintainers.name'])
t.matchSnapshot(joinedOutput())
})
t.test('array field - 2 elements', async t => {
await view.exec(['yellow@1.x.x', 'maintainers.name'])
t.matchSnapshot(joinedOutput())
})
t.test('fields with empty values', async t => {
await view.exec(['yellow', 'empty'])
t.matchSnapshot(joinedOutput())
})
})
t.test('throw error if global mode', async t => {
const { npm } = await loadMockNpm(t, { config: { global: true } })
await t.rejects(
npm.exec('view', []),
/Cannot use view command in global mode./
)
})
t.test('throw ENOENT error if package.json missing', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('view', []),
{ code: 'ENOENT' }
)
})
t.test('throw error if package.json has no name', async t => {
const { npm } = await loadMockNpm(t, {
prefixDir: {
'package.json': '{}',
},
})
await t.rejects(
npm.exec('view', []),
/Invalid package.json, no "name" field/
)
})
t.test('throws when unpublished', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('view', ['red']),
{ code: 'E404', pkgid: 'red@1.0.1', message: 'Unpublished on 2012-12-20T00:00:00.000Z' }
)
})
t.test('throws when version not matched', async t => {
const { npm } = await loadMockNpm(t)
await t.rejects(
npm.exec('view', ['blue@2.0.0']),
{ code: 'E404', pkgid: 'blue@2.0.0', message: 'No match found for version 2.0.0' }
)
})
t.test('workspaces', async t => {
const prefixDir = {
'package.json': JSON.stringify({
name: 'workspaces-test-package',
version: '1.2.3',
workspaces: ['test-workspace-a', 'test-workspace-b'],
}),
'test-workspace-a': {
'package.json': JSON.stringify({
name: 'green',
version: '1.2.3',
}),
},
'test-workspace-b': {
'package.json': JSON.stringify({
name: 'orange',
version: '1.2.3',
}),
},
}
const prefixDir404 = {
'test-workspace-b': {
'package.json': JSON.stringify({
name: 'missing-package',
version: '1.2.3',
}),
},
}
const prefixDirError = {
'test-workspace-b': {
'package.json': JSON.stringify({
name: 'unknown-error',
version: '1.2.3',
}),
},
}
const prefixPackageNamedError = {
'test-workspace-a': {
'package.json': JSON.stringify({
name: 'error',
version: '1.2.3',
}),
},
}
t.test('all workspaces', async t => {
const { view, joinedOutput } = await loadMockNpm(t, {
prefixDir,
config: { unicode: false, workspaces: true },
})
await view.exec([])
t.matchSnapshot(joinedOutput())
})
t.test('one specific workspace', async t => {
const { view, joinedOutput } = await loadMockNpm(t, {
prefixDir,
config: { unicode: false, workspace: ['green'] },
})
await view.exec([])
t.matchSnapshot(joinedOutput())
})
t.test('all workspaces --json', async t => {
const { view, joinedOutput } = await loadMockNpm(t, {
prefixDir,
config: { unicode: false, workspaces: true, json: true },
})
await view.exec([])
t.matchSnapshot(joinedOutput())
})
t.test('all workspaces single field', async t => {
const { view, joinedOutput } = await loadMockNpm(t, {
prefixDir,
config: { unicode: false, workspaces: true },
})
await view.exec(['.', 'name'])
t.matchSnapshot(joinedOutput())
})
t.test('all workspaces nonexistent field', async t => {
const { view, joinedOutput } = await loadMockNpm(t, {
prefixDir,
config: { unicode: false, workspaces: true },
})
await view.exec(['.', 'foo'])
t.matchSnapshot(joinedOutput())
})
t.test('all workspaces nonexistent field --json', async t => {
const { view, joinedOutput } = await loadMockNpm(t, {
prefixDir,
config: { unicode: false, workspaces: true, json: true },
})
await view.exec(['.', 'foo'])
t.matchSnapshot(joinedOutput())
})
t.test('all workspaces single field --json', async t => {
const { view, joinedOutput } = await loadMockNpm(t, {
prefixDir,
config: { unicode: false, workspaces: true, json: true },
})
await view.exec(['.', 'name'])
t.matchSnapshot(joinedOutput())
})
t.test('single workspace --json', async t => {
const { view, joinedOutput } = await loadMockNpm(t, {
prefixDir,
config: { unicode: false, workspace: ['green'], json: true },
})
await view.exec([])
t.matchSnapshot(joinedOutput())
})
t.test('remote package name', async t => {
const { view, logs, joinedOutput } = await loadMockNpm(t, {
prefixDir,
config: { unicode: false, workspaces: true },
})
await view.exec(['pink'])
t.matchSnapshot(joinedOutput())
t.matchSnapshot(logs.warn, 'should have warning of ignoring workspaces')
})
t.test('404 workspaces', async t => {
t.test('basic', async t => {
const { view, joinedFullOutput } = await loadMockNpm(t, {
prefixDir: { ...prefixDir, ...prefixDir404 },
config: { workspaces: true, loglevel: 'error' },
})
await view.exec([])
t.matchSnapshot(joinedFullOutput())
t.equal(process.exitCode, 1)
})
t.test('json', async t => {
const { view, joinedFullOutput } = await loadMockNpm(t, {
prefixDir: { ...prefixDir, ...prefixDir404 },
config: { workspaces: true, json: true, loglevel: 'error' },
})
await view.exec([])
t.matchSnapshot(joinedFullOutput())
t.equal(process.exitCode, 1)
})
t.test('json with package named error', async t => {
const { view, joinedFullOutput } = await loadMockNpm(t, {
prefixDir: { ...prefixDir, ...prefixDir404, ...prefixPackageNamedError },
config: { workspaces: true, json: true, loglevel: 'warn' },
})
await view.exec([])
t.matchSnapshot(joinedFullOutput())
t.equal(process.exitCode, 1)
})
t.test('non-404 error rejects', async t => {
const { view, joinedFullOutput } = await loadMockNpm(t, {
prefixDir: { ...prefixDir, ...prefixDirError },
config: { workspaces: true, loglevel: 'error' },
})
await t.rejects(view.exec([]))
t.matchSnapshot(joinedFullOutput())
})
t.test('non-404 error rejects with single arg', async t => {
const { view, joinedFullOutput } = await loadMockNpm(t, {
prefixDir: { ...prefixDir, ...prefixDirError },
config: { workspaces: true, loglevel: 'error' },
})
await t.rejects(view.exec(['.', 'version']))
t.matchSnapshot(joinedFullOutput())
})
})
})
t.test('completion', async t => {
const { view } = await loadMockNpm(t, { command: 'view' })
const res = await view.completion({
conf: { argv: { remain: ['npm', 'view', 'green@1.0.0'] } },
})
t.ok(res, 'returns back fields')
})
t.test('no package completion', async t => {
const { view } = await loadMockNpm(t, { command: 'view' })
const res = await view.completion({ conf: { argv: { remain: ['npm', 'view'] } } })
t.notOk(res, 'there is no package completion')
t.end()
})

94
deps/npm/test/lib/commands/whoami.js vendored Normal file
View File

@ -0,0 +1,94 @@
const t = require('tap')
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
const MockRegistry = require('@npmcli/mock-registry')
const nock = require('nock')
const username = 'foo'
const auth = { '//registry.npmjs.org/:_authToken': 'test-auth-token' }
t.test('npm whoami', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: auth })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
registry.whoami({ username })
await npm.exec('whoami', [])
t.equal(joinedOutput(), username, 'should print username')
})
t.test('npm whoami --json', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
json: true,
...auth,
},
})
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
authorization: 'test-auth-token',
})
registry.whoami({ username })
await npm.exec('whoami', [])
t.equal(JSON.parse(joinedOutput()), username, 'should print username')
})
t.test('npm whoami using mTLS', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, { config: {
'//registry.npmjs.org/:certfile': '/some.cert',
'//registry.npmjs.org/:keyfile': '/some.key',
} })
const registry = new MockRegistry({
tap: t,
registry: npm.config.get('registry'),
})
registry.whoami({ username })
await npm.exec('whoami', [])
t.equal(joinedOutput(), username, 'should print username')
})
t.test('credentials from token', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
'//registry.npmjs.org/:username': username,
'//registry.npmjs.org/:_password': 'hunter2',
},
})
await npm.exec('whoami', [])
t.equal(joinedOutput(), username, 'should print username')
})
t.test('not logged in', async t => {
const { npm } = await loadMockNpm(t, {
config: {
json: true,
},
})
await t.rejects(npm.exec('whoami', []), { code: 'ENEEDAUTH' })
})
t.test('non-string username in response', async t => {
nock.disableNetConnect()
t.teardown(() => {
nock.enableNetConnect()
})
const server = nock('https://registry.npmjs.org', {
reqheaders: {
authorization: 'Bearer abcd1234',
},
})
.get('/-/whoami')
.reply(200, { username: null })
const { npm } = await loadMockNpm(t, {
config: {
'//registry.npmjs.org/:_authToken': 'abcd1234',
},
})
await t.rejects(npm.exec('whoami', []), { code: 'ENEEDAUTH' })
t.ok(server.isDone())
})

136
deps/npm/test/lib/docs.js vendored Normal file
View File

@ -0,0 +1,136 @@
const t = require('tap')
const { join, resolve, basename, extname } = require('node:path')
const fs = require('node:fs/promises')
const localeCompare = require('@isaacs/string-locale-compare')('en')
const docs = require('@npmcli/docs')
const { load: loadMockNpm } = require('../fixtures/mock-npm.js')
const { definitions } = require('@npmcli/config/lib/definitions')
const cmdList = require('../../lib/utils/cmd-list.js')
const pkg = require('../../package.json')
const { cleanCwd } = require('../fixtures/clean-snapshot.js')
t.test('command list', async t => {
for (const [key, value] of Object.entries(cmdList)) {
t.matchSnapshot(value, key)
}
})
t.test('shorthands', async t => {
t.matchSnapshot(docs.shorthands(docs.TAGS.SHORTHANDS, {}), 'docs')
})
t.test('config', async t => {
const keys = Object.keys(definitions)
const flat = Object.entries(definitions).filter(([, d]) => d.flatten).map(([k]) => k)
const notFlat = keys.filter(k => !flat.includes(k))
t.matchSnapshot(keys, 'all keys')
t.matchSnapshot(flat, 'keys that are flattened')
t.matchSnapshot(notFlat, 'keys that are not flattened')
t.matchSnapshot(docs.config(docs.TAGS.CONFIG, {}), 'all definitions')
})
t.test('flat options', async t => {
t.cleanSnapshot = (s) => cleanCwd(s)
.split(cleanCwd(process.execPath)).join('{NODE}')
const { npm } = await loadMockNpm(t, {
command: 'version',
exec: true,
npm: ({ other }) => ({
npmRoot: other,
}),
otherDirs: {
'package.json': JSON.stringify({ version: '1.1.1' }),
},
globals: {
'process.stdout.isTTY': false,
'process.stderr.isTTY': false,
'process.env': {
EDITOR: '{EDITOR}',
SHELL: '{SHELL}',
},
'process.version': '2.2.2',
'process.platform': '{PLATFORM}',
'process.arch': '{ARCH}',
},
mocks: {
'ci-info': { isCI: true, name: '{CI}' },
// currently the config version is read from npmRoot and the Npm
// constructor version is read from the root package.json file. This
// snapshot is meant to explicitly show that they are read from different
// places. In the future we should probably only read it from npmRoot,
// which would require more changes to ensure nothing depends on it being
// a static prop on the constructor
'{ROOT}/package.json': { version: '3.3.3' },
},
})
t.matchSnapshot(npm.flatOptions, 'full flat options object')
})
t.test('basic usage', async t => {
// snapshot basic usage without commands since all the command snapshots
// are generated in the following test
const { npm } = await loadMockNpm(t, {
mocks: {
'{LIB}/utils/cmd-list.js': { ...cmdList, commands: [] },
},
config: { userconfig: '/some/config/file/.npmrc' },
globals: { process: { platform: 'posix' } },
})
t.cleanSnapshot = str => str
.replace(npm.npmRoot, '{BASEDIR}')
.replace(npm.config.get('userconfig'), '{USERCONFIG}')
.split(pkg.version).join('{VERSION}')
t.matchSnapshot(npm.usage)
})
t.test('usage', async t => {
const readdir = async (dir, ext) => {
const files = await fs.readdir(dir)
return files.filter(f => extname(f) === ext).map(f => basename(f, ext))
}
const fsCommands = await readdir(resolve(__dirname, '../../lib/commands'), '.js')
const docsCommands = await readdir(join(docs.paths.content, 'commands'), docs.DOC_EXT)
const bareCommands = ['npm', 'npx']
// XXX: These extra commands exist as js files but not as docs pages
const allDocs = docsCommands.concat(['get', 'set', 'll']).map(n => n.replace('npm-', ''))
// ensure that the list of js files in commands, docs files, and the command list
// are all in sync. eg, this will error if a command is removed but not its docs file
t.strictSame(
fsCommands.sort(localeCompare),
cmdList.commands,
'command list and fs are the same'
)
t.strictSame(
allDocs.filter(f => !bareCommands.includes(f)).sort(localeCompare),
cmdList.commands,
'command list and docs files are the same'
)
// use the list of files from the docs since those include the special cases
// for the bare npm and npx usage
for (const cmd of allDocs) {
t.test(cmd, async t => {
let output = null
if (!bareCommands.includes(cmd)) {
const mock = await loadMockNpm(t, { command: cmd })
output = mock[cmd].usage
}
const usage = docs.usage(docs.TAGS.USAGE, { path: cmd })
const params = docs.params(docs.TAGS.CONFIG, { path: cmd })
.split('\n')
.filter(l => l.startsWith('#### '))
.join('\n') || 'NO PARAMS'
t.matchSnapshot([output, usage, params].filter(Boolean).join('\n\n'))
})
}
})

32
deps/npm/test/lib/lifecycle-cmd.js vendored Normal file
View File

@ -0,0 +1,32 @@
const t = require('tap')
const mockNpm = require('../fixtures/mock-npm')
const LifecycleCmd = require('../../lib/lifecycle-cmd.js')
t.test('create a lifecycle command', async t => {
let runArgs = null
const { npm } = await mockNpm(t)
npm.exec = async (cmd, args) => {
if (cmd === 'run-script') {
runArgs = args
return 'called the right thing'
}
}
class TestStage extends LifecycleCmd {
static get name () {
return 'test-stage'
}
}
const cmd = new TestStage(npm)
t.match(cmd.usage, /test-stage/)
let result
result = await cmd.exec(['some', 'args'])
t.same(runArgs, ['test-stage', 'some', 'args'])
t.strictSame(result, 'called the right thing')
result = await cmd.execWorkspaces(['some', 'args'])
t.same(runArgs, ['test-stage', 'some', 'args'])
t.strictSame(result, 'called the right thing')
})

87
deps/npm/test/lib/load-all-commands.js vendored Normal file
View File

@ -0,0 +1,87 @@
// Our coverage mapping means that stuff like this doen't count for coverage.
// It does ensure that every command has a usage that renders, contains its
// name, a description, and if it has completion it is a function. That it
// renders also ensures that any params we've defined in our commands work.
const t = require('tap')
const util = require('node:util')
const { load: loadMockNpm } = require('../fixtures/mock-npm.js')
const { commands } = require('../../lib/utils/cmd-list.js')
const BaseCommand = require('../../lib/base-cmd.js')
const isAsyncFn = (v) => typeof v === 'function' && /^\[AsyncFunction:/.test(util.inspect(v))
t.test('load each command', async t => {
const counts = {
completion: 0,
ignoreImplicitWorkspace: 0,
workspaces: 0,
noParams: 0,
}
for (const cmd of commands) {
await t.test(cmd, async t => {
const { npm, outputs, cmd: impl } = await loadMockNpm(t, {
command: cmd,
config: { usage: true },
})
const ctor = impl.constructor
t.notOk(impl.completion, 'completion is static, not on instance')
if (ctor.completion) {
t.ok(isAsyncFn(ctor.completion), 'completion is async function')
counts.completion++
}
// exec fn
t.ok(isAsyncFn(impl.exec), 'exec is async')
t.ok(impl.exec.length <= 1, 'exec fn has 0 or 1 args')
// workspaces
t.type(ctor.ignoreImplicitWorkspace, 'boolean', 'ctor has ignoreImplictWorkspace boolean')
if (ctor.ignoreImplicitWorkspace !== BaseCommand.ignoreImplicitWorkspace) {
counts.ignoreImplicitWorkspace++
}
t.type(ctor.workspaces, 'boolean', 'ctor has workspaces boolean')
if (ctor.workspaces !== BaseCommand.workspaces) {
counts.workspaces++
}
if (ctor.workspaces) {
t.ok(isAsyncFn(impl.execWorkspaces), 'execWorkspaces is async')
t.ok(impl.exec.length <= 1, 'execWorkspaces fn has 0 or 1 args')
} else {
t.notOk(impl.execWorkspaces, 'has no execWorkspaces fn')
}
// name/desc
t.ok(impl.description, 'implementation has a description')
t.equal(impl.description, ctor.description, 'description is same on instance and ctor')
t.ok(impl.name, 'implementation has a name')
t.equal(impl.name, ctor.name, 'name is same on instance and ctor')
t.equal(cmd, impl.name, 'command list and name are the same')
// params are optional
if (impl.params) {
t.equal(impl.params, ctor.params, 'params is same on instance and ctor')
t.ok(impl.params, 'implementation has a params')
} else {
counts.noParams++
}
// usage
t.match(impl.usage, cmd, 'usage contains the command')
await npm.exec(cmd, [])
t.match(outputs[0], impl.usage, 'usage is what is output')
t.match(outputs[0], ctor.describeUsage, 'usage is what is output')
t.notOk(impl.describeUsage, 'describe usage is only static')
})
}
// make sure refactors dont move or rename these static properties since
// we guard against the tests for them above
t.ok(counts.completion > 0, 'has some completion functions')
t.ok(counts.ignoreImplicitWorkspace > 0, 'has some commands that change ignoreImplicitWorkspace')
t.ok(counts.workspaces > 0, 'has some commands that change workspaces')
t.ok(counts.noParams > 0, 'has some commands that do not have params')
})

569
deps/npm/test/lib/npm.js vendored Normal file
View File

@ -0,0 +1,569 @@
const t = require('tap')
const { resolve, dirname, join } = require('node:path')
const fs = require('node:fs')
const { time } = require('proc-log')
const { load: loadMockNpm } = require('../fixtures/mock-npm.js')
const mockGlobals = require('@npmcli/mock-globals')
const { commands } = require('../../lib/utils/cmd-list.js')
t.test('not yet loaded', async t => {
const { npm, logs } = await loadMockNpm(t, { load: false })
t.match(npm, {
started: Number,
command: null,
config: {
loaded: false,
get: Function,
set: Function,
},
version: String,
})
t.throws(() => npm.config.set('foo', 'bar'))
t.throws(() => npm.config.get('foo'))
t.same(logs, [])
})
t.test('npm.load', async t => {
await t.test('load error', async t => {
const { npm } = await loadMockNpm(t, { load: false })
npm.config.load = async () => {
throw new Error('load error')
}
await t.rejects(
() => npm.load(),
/load error/
)
})
await t.test('basic loading', async t => {
const { npm, logs, cache } = await loadMockNpm(t, {
prefixDir: { node_modules: {} },
config: {
timing: true,
},
})
t.equal(npm.loaded, true)
t.equal(npm.config.loaded, true)
t.equal(npm.config.get('force'), false)
t.ok(npm.usage, 'has usage')
t.match(npm, {
flatOptions: {},
})
t.match(logs.timing.filter((p) => /^npm:load/.test(p)), [
/npm:load.* Completed in [0-9.]+ms/,
])
mockGlobals(t, { process: { platform: 'posix' } })
t.equal(resolve(npm.cache), resolve(cache), 'cache is cache')
t.equal(npm.lockfileVersion, 2, 'lockfileVersion getter')
t.equal(npm.prefix, npm.localPrefix, 'prefix is local prefix')
t.not(npm.prefix, npm.globalPrefix, 'prefix is not global prefix')
t.equal(npm.bin, npm.localBin, 'bin is local bin')
t.not(npm.bin, npm.globalBin, 'bin is not global bin')
npm.config.set('global', true)
t.equal(npm.prefix, npm.globalPrefix, 'prefix is global prefix after setting global')
t.not(npm.prefix, npm.localPrefix, 'prefix is not local prefix after setting global')
t.equal(npm.bin, npm.globalBin, 'bin is global bin after setting global')
t.not(npm.bin, npm.localBin, 'bin is not local bin after setting global')
t.equal(npm.dir, npm.globalDir, 'dir is global dir after setting global')
t.not(npm.dir, npm.localDir, 'dir is not local dir after setting global')
mockGlobals(t, { process: { platform: 'win32' } })
t.equal(npm.bin, npm.globalBin, 'bin is global bin in windows mode')
t.equal(npm.dir, npm.globalDir, 'dir is global dir in windows mode')
})
await t.test('forceful loading', async t => {
const { logs } = await loadMockNpm(t, {
config: {
force: true,
},
})
t.match(logs.warn, [
'using --force Recommended protections disabled.',
])
})
await t.test('node is a symlink', async t => {
const node = process.platform === 'win32' ? 'node.exe' : 'node'
const { Npm, npm, logs, outputs, prefix } = await loadMockNpm(t, {
prefixDir: {
bin: t.fixture('symlink', dirname(process.execPath)),
},
config: {
timing: true,
usage: '',
scope: 'foo',
},
argv: [
'token',
'revoke',
'blergggg',
],
globals: (dirs) => ({
'process.env.PATH': resolve(dirs.prefix, 'bin'),
'process.argv': [
node,
process.argv[1],
],
}),
})
t.equal(npm.config.get('scope'), '@foo', 'added the @ sign to scope')
t.match([
...logs.timing.filter((p) => p.startsWith('npm:load:whichnode')),
...logs.verbose,
...logs.timing.filter((p) => p.startsWith('npm:load')),
], [
/npm:load:whichnode Completed in [0-9.]+ms/,
`node symlink ${resolve(prefix, 'bin', node)}`,
/title npm token revoke blergggg/,
/argv "token" "revoke" "blergggg".*"--usage" "--scope" "foo"/,
/logfile logs-max:\d+ dir:.*/,
/logfile .*-debug-0.log/,
/npm:load:.* Completed in [0-9.]+ms/,
])
t.equal(process.execPath, resolve(prefix, 'bin', node))
outputs.length = 0
logs.length = 0
await npm.exec('ll', [])
t.equal(npm.command, 'll', 'command set to first npm command')
t.equal(npm.flatOptions.npmCommand, 'll', 'npmCommand flatOption set')
const ll = Npm.cmd('ll')
t.same(outputs, [ll.describeUsage], 'print usage')
npm.config.set('usage', false)
outputs.length = 0
logs.length = 0
await npm.exec('get', ['scope', 'usage'])
t.strictSame([npm.command, npm.flatOptions.npmCommand], ['ll', 'll'],
'does not change npm.command when another command is called')
t.match(logs, [
/timing config:load:flatten Completed in [0-9.]+ms/,
/timing command:config Completed in [0-9.]+ms/,
])
t.same(outputs, ['scope=@foo\nusage=false'])
})
await t.test('--no-workspaces with --workspace', async t => {
const { npm } = await loadMockNpm(t, {
prefixDir: {
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
scripts: { test: 'echo test a' },
}),
},
},
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
workspaces: ['./packages/*'],
}),
},
config: {
workspaces: false,
workspace: 'a',
},
})
await t.rejects(
npm.exec('run', []),
/Can not use --no-workspaces and --workspace at the same time/
)
})
await t.test('workspace-aware configs and commands', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir: {
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
scripts: { test: 'echo test a' },
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
scripts: { test: 'echo test b' },
}),
},
},
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
workspaces: ['./packages/*'],
}),
},
config: {
workspaces: true,
},
})
await npm.exec('run', [])
t.equal(npm.command, 'run-script', 'npm.command set to canonical name')
t.matchSnapshot(joinedOutput(), 'should exec workspaces version of commands')
})
await t.test('workspaces in global mode', async t => {
const { npm } = await loadMockNpm(t, {
prefixDir: {
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
scripts: { test: 'echo test a' },
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
scripts: { test: 'echo test b' },
}),
},
},
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
workspaces: ['./packages/*'],
}),
},
config: {
workspaces: true,
global: true,
},
})
await t.rejects(
npm.exec('run', []),
/Workspaces not supported for global packages/
)
})
})
t.test('set process.title', async t => {
t.test('basic title setting', async t => {
const { npm } = await loadMockNpm(t, {
config: {
usage: true,
scope: 'foo',
},
argv: ['ls'],
})
t.equal(npm.title, 'npm ls')
t.equal(process.title, 'npm ls')
})
t.test('do not expose token being revoked', async t => {
const { npm } = await loadMockNpm(t, {
config: {
usage: true,
scope: 'foo',
},
argv: ['token', 'revoke', `npm_${'a'.repeat(36)}`],
})
t.equal(npm.title, 'npm token revoke npm_***')
t.equal(process.title, 'npm token revoke npm_***')
})
t.test('do show *** unless a token is actually being revoked', async t => {
const { npm } = await loadMockNpm(t, {
config: {
usage: true,
scope: 'foo',
},
argv: ['token', 'revoke', 'notatoken'],
})
t.equal(npm.title, 'npm token revoke notatoken')
t.equal(process.title, 'npm token revoke notatoken')
})
})
t.test('debug log', async t => {
t.test('writes log file', async t => {
const { npm, debugFile } = await loadMockNpm(t, { load: false })
const log1 = ['silly', 'test', 'before load']
const log2 = ['silly', 'test', 'after load']
const log3 = ['silly', 'test', 'hello\x00world']
process.emit('log', ...log1)
await npm.load()
process.emit('log', ...log2)
process.emit('log', ...log3)
const debug = await debugFile()
t.equal(npm.logFiles.length, 1, 'one debug file')
t.match(debug, log1.join(' '), 'before load appears')
t.match(debug, log2.join(' '), 'after load log appears')
t.match(debug, 'hello^@world')
})
t.test('can load with bad dir', async t => {
const { npm, testdir } = await loadMockNpm(t, {
load: false,
config: (dirs) => ({
'logs-dir': join(dirs.testdir, 'my_logs_dir'),
}),
})
const logsDir = join(testdir, 'my_logs_dir')
// make logs dir a file before load so it files
fs.writeFileSync(logsDir, 'A_TEXT_FILE')
await t.resolves(npm.load(), 'loads with invalid logs dir')
t.equal(npm.logFiles.length, 0, 'no log files array')
t.strictSame(fs.readFileSync(logsDir, 'utf-8'), 'A_TEXT_FILE')
})
})
t.test('cache dir', async t => {
t.test('creates a cache dir', async t => {
const { npm } = await loadMockNpm(t)
t.ok(fs.existsSync(npm.cache), 'cache dir exists')
})
t.test('can load with a bad cache dir', async t => {
const { npm, cache } = await loadMockNpm(t, {
load: false,
// The easiest way to make mkdir(cache) fail is to make it a file.
// This will have the same effect as if its read only or inaccessible.
cacheDir: 'A_TEXT_FILE',
})
await t.resolves(npm.load(), 'loads with cache dir as a file')
t.equal(fs.readFileSync(cache, 'utf-8'), 'A_TEXT_FILE')
})
})
t.test('timings', async t => {
t.test('writes timings file', async t => {
const { npm, timingFile } = await loadMockNpm(t, {
config: { timing: true },
})
time.start('foo')
time.end('foo')
time.start('bar')
npm.finish()
const timings = await timingFile()
t.match(timings, {
metadata: {
command: [],
logfiles: [String],
version: String,
},
unfinishedTimers: {
bar: [Number, Number],
},
timers: {
foo: Number,
'npm:load': Number,
},
})
})
t.test('does not write timings file with timers:false', async t => {
const { npm, timingFile } = await loadMockNpm(t, {
config: { timing: false },
})
npm.finish()
await t.rejects(() => timingFile())
})
const timingDisplay = [
[{ loglevel: 'silly' }, true, false],
[{ loglevel: 'silly', timing: true }, true, true],
[{ loglevel: 'silent', timing: true }, false, false],
]
for (const [config, expectedDisplay, expectedTiming] of timingDisplay) {
const msg = `${JSON.stringify(config)}, display:${expectedDisplay}, timing:${expectedTiming}`
await t.test(`timing display: ${msg}`, async t => {
const { logs } = await loadMockNpm(t, { config })
t.equal(!!logs.length, expectedDisplay, 'display')
t.equal(!!logs.timing.length, expectedTiming, 'timing display')
})
}
})
t.test('aliases and typos', async t => {
const { Npm } = await loadMockNpm(t, { init: false })
t.throws(() => Npm.cmd('thisisnotacommand'), { code: 'EUNKNOWNCOMMAND' })
t.throws(() => Npm.cmd(''), { code: 'EUNKNOWNCOMMAND' })
t.throws(() => Npm.cmd('birthday'), { code: 'EUNKNOWNCOMMAND' })
t.match(Npm.cmd('it').name, 'install-test')
t.match(Npm.cmd('installTe').name, 'install-test')
t.match(Npm.cmd('access').name, 'access')
t.match(Npm.cmd('auth').name, 'owner')
})
t.test('explicit workspace rejection', async t => {
const mock = await loadMockNpm(t, {
prefixDir: {
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
scripts: { test: 'echo test a' },
}),
},
},
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
workspaces: ['./packages/a'],
}),
},
config: {
workspace: './packages/a',
},
})
await t.rejects(
mock.npm.exec('ping', []),
/This command does not support workspaces/
)
})
t.test('implicit workspace rejection', async t => {
const mock = await loadMockNpm(t, {
prefixDir: {
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
scripts: { test: 'echo test a' },
}),
},
},
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
workspaces: ['./packages/a'],
}),
},
chdir: ({ prefix }) => join(prefix, 'packages', 'a'),
config: {
workspace: './packages/a',
},
})
await t.rejects(
mock.npm.exec('team', []),
/This command does not support workspaces/
)
})
t.test('implicit workspace accept', async t => {
const mock = await loadMockNpm(t, {
prefixDir: {
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
scripts: { test: 'echo test a' },
}),
},
},
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
workspaces: ['./packages/a'],
}),
},
chdir: ({ prefix }) => join(prefix, 'packages', 'a'),
})
await t.rejects(mock.npm.exec('org', []), /.*Usage/)
})
t.test('usage', async t => {
t.test('with browser', async t => {
const { npm } = await loadMockNpm(t, { globals: { process: { platform: 'posix' } } })
const usage = npm.usage
npm.config.set('viewer', 'browser')
const browserUsage = npm.usage
t.notMatch(usage, '(in a browser)')
t.match(browserUsage, '(in a browser)')
})
t.test('windows always uses browser', async t => {
const { npm } = await loadMockNpm(t, { globals: { process: { platform: 'win32' } } })
const usage = npm.usage
npm.config.set('viewer', 'browser')
const browserUsage = npm.usage
t.match(usage, '(in a browser)')
t.match(browserUsage, '(in a browser)')
})
t.test('includes commands', async t => {
const { npm } = await loadMockNpm(t)
const usage = npm.usage
npm.config.set('long', true)
const longUsage = npm.usage
const lastCmd = commands[commands.length - 1]
for (const cmd of commands) {
const isLast = cmd === lastCmd
const shortCmd = new RegExp(`\\s${cmd}${isLast ? '\\n' : ',[\\s\\n]'}`)
const longCmd = new RegExp(`^\\s+${cmd}\\s+\\w.*\n\\s+Usage:\\n`, 'm')
t.match(usage, shortCmd, `usage includes ${cmd}`)
t.notMatch(usage, longCmd, `usage does not include long ${cmd}`)
t.match(longUsage, longCmd, `long usage includes ${cmd}`)
if (!isLast) {
// long usage includes false positives for the last command since it is
// not followed by a comma
t.notMatch(longUsage, shortCmd, `long usage does not include short ${cmd}`)
}
}
})
t.test('set process.stdout.columns', async t => {
const { npm } = await loadMockNpm(t, {
config: { viewer: 'man' },
})
t.cleanSnapshot = str =>
str.replace(npm.config.get('userconfig'), '{USERCONFIG}')
.replace(npm.npmRoot, '{NPMROOT}')
.replace(`npm@${npm.version}`, 'npm@{VERSION}')
const widths = [0, 1, 10, 24, 40, 41, 75, 76, 90, 100]
for (const width of widths) {
t.test(`column width ${width}`, async t => {
mockGlobals(t, { 'process.stdout.columns': width })
const usage = npm.usage
t.matchSnapshot(usage)
})
}
})
})
t.test('print usage if non-command param provided', async t => {
const { npm, joinedOutput } = await loadMockNpm(t)
await t.rejects(npm.exec('tset'), { command: 'tset', exitCode: 1 })
t.match(joinedOutput(), 'Unknown command: "tset"')
t.match(joinedOutput(), 'Did you mean this?')
})

121
deps/npm/test/lib/utils/audit-error.js vendored Normal file
View File

@ -0,0 +1,121 @@
const t = require('tap')
const mockNpm = require('../../fixtures/mock-npm')
const tmock = require('../../fixtures/tmock')
const auditError = async (t, { command, error, ...config } = {}) => {
const mockAuditError = tmock(t, '{LIB}/utils/audit-error')
const mock = await mockNpm(t, {
command,
config,
exec: true,
prefixDir: { 'package.json': '{}', 'package-lock.json': '{}' },
})
const res = {}
try {
res.result = mockAuditError(mock.npm, error ? { error } : {})
} catch (err) {
res.error = err
}
mock.npm.finish()
return {
...res,
logs: mock.logs.warn.byTitle('audit'),
output: mock.joinedOutput(),
}
}
t.test('no error, not audit command', async t => {
const { result, error, logs, output } = await auditError(t, { command: 'install' })
t.equal(result, false, 'no error')
t.notOk(error, 'no error')
t.match(output.trim(), /up to date/, 'install output')
t.match(output.trim(), /found 0 vulnerabilities/, 'install output')
t.strictSame(logs, [], 'no warnings')
})
t.test('error, not audit command', async t => {
const { result, error, logs, output } = await auditError(t, {
command: 'install',
error: {
message: 'message',
body: Buffer.from('body'),
method: 'POST',
uri: 'https://example.com/not/a/registry',
headers: {
head: ['ers'],
},
statusCode: '420',
},
})
t.equal(result, true, 'had error')
t.notOk(error, 'no error')
t.match(output.trim(), /up to date/, 'install output')
t.match(output.trim(), /found 0 vulnerabilities/, 'install output')
t.strictSame(logs, [], 'no warnings')
})
t.test('error, audit command, not json', async t => {
const { result, error, logs, output } = await auditError(t, {
command: 'audit',
error: {
message: 'message',
body: Buffer.from('body error text'),
method: 'POST',
uri: 'https://example.com/not/a/registry',
headers: {
head: ['ers'],
},
statusCode: '420',
},
})
t.equal(result, undefined)
t.ok(error, 'throws error')
t.match(output, 'body error text', 'some output')
t.strictSame(logs, ['audit message'], 'some warnings')
})
t.test('error, audit command, json', async t => {
const { result, error, logs, output } = await auditError(t, {
json: true,
command: 'audit',
error: {
message: 'message',
body: { response: 'body' },
method: 'POST',
uri: 'https://username:password@example.com/not/a/registry',
headers: {
head: ['ers'],
},
statusCode: '420',
},
})
t.equal(result, undefined)
t.ok(error, 'throws error')
t.match(output,
'{\n' +
' "message": "message",\n' +
' "method": "POST",\n' +
' "uri": "https://username:***@example.com/not/a/registry",\n' +
' "headers": {\n' +
' "head": [\n' +
' "ers"\n' +
' ]\n' +
' },\n' +
' "statusCode": "420",\n' +
' "body": {\n' +
' "response": "body"\n' +
' }\n' +
'}'
, 'some output')
t.strictSame(logs, ['audit message'], 'some warnings')
})

143
deps/npm/test/lib/utils/auth.js vendored Normal file
View File

@ -0,0 +1,143 @@
const t = require('tap')
const setupMockNpm = require('../../fixtures/mock-npm')
const tmock = require('../../fixtures/tmock')
// TODO make this real and not a mock
const setupOtplease = async (t, { otp = {}, ...rest }, fn) => {
const { otplease } = tmock(t, '{LIB}/utils/auth.js', {
'{LIB}/utils/read-user-info.js': {
otp: async () => '1234',
},
'{LIB}/utils/open-url.js': {
createOpener: () => () => {},
},
'npm-profile': {
webAuthOpener: async (opener) => {
opener()
return { token: '1234' }
},
},
})
return otplease(await setupMockNpm(t, rest).then(({ npm }) => npm), otp, fn)
}
t.test('returns function results on success', async (t) => {
const result = await setupOtplease(t, {}, () => 'test string')
t.equal('test string', result)
})
t.test('returns function results on otp success', async (t) => {
const fn = ({ otp }) => {
if (otp) {
return 'success'
}
throw Object.assign(new Error('nope'), { code: 'EOTP' })
}
const result = await setupOtplease(t, {
globals: {
'process.stdin': { isTTY: true },
'process.stdout': { isTTY: true },
},
}, fn)
t.equal('success', result)
})
t.test('prompts for otp for EOTP', async (t) => {
let called = false
const fn = async (opts) => {
if (!called) {
called = true
throw Object.assign(new Error('nope'), { code: 'EOTP' })
}
return opts
}
const result = await setupOtplease(t, {
otp: { some: 'prop' },
globals: {
'process.stdin': { isTTY: true },
'process.stdout': { isTTY: true },
},
}, fn)
t.strictSame(result, { some: 'prop', otp: '1234' })
})
t.test('returns function results on webauth success', async (t) => {
const fn = ({ otp }) => {
if (!otp) {
throw Object.assign(new Error('nope'), {
code: 'EOTP',
body: {
authUrl: 'https://www.example.com/auth',
doneUrl: 'https://www.example.com/done',
},
})
}
return 'success'
}
const result = await setupOtplease(t, {
config: { browser: 'firefox' },
globals: {
'process.stdin': { isTTY: true },
'process.stdout': { isTTY: true },
},
}, fn)
t.equal('success', result)
})
t.test('prompts for otp for 401', async (t) => {
let called = false
const fn = async (opts) => {
if (!called) {
called = true
throw Object.assign(new Error('nope'), {
code: 'E401',
body: 'one-time pass required',
})
}
return opts
}
const result = await setupOtplease(t, {
globals: {
'process.stdin': { isTTY: true },
'process.stdout': { isTTY: true },
},
}, fn)
t.strictSame(result, { otp: '1234' })
})
t.test('does not prompt for non-otp errors', async (t) => {
const fn = async () => {
throw new Error('nope')
}
await t.rejects(setupOtplease(t, {
globals: {
'process.stdin': { isTTY: true },
'process.stdout': { isTTY: true },
},
}, fn), { message: 'nope' }, 'rejects with the original error')
})
t.test('does not prompt if stdin or stdout is not a tty', async (t) => {
const fn = async () => {
throw Object.assign(new Error('nope'), { code: 'EOTP' })
}
await t.rejects(setupOtplease(t, {
globals: {
'process.stdin': { isTTY: false },
'process.stdout': { isTTY: false },
},
}, fn), { message: 'nope' }, 'rejects with the original error')
})

47
deps/npm/test/lib/utils/did-you-mean.js vendored Normal file
View File

@ -0,0 +1,47 @@
const t = require('tap')
const dym = require('../../../lib/utils/did-you-mean.js')
t.test('did-you-mean', async t => {
t.test('with package.json', async t => {
const pkg = {
bin: {
npx: 'exists',
},
scripts: {
install: 'exists',
posttest: 'exists',
},
}
t.test('nistall', async t => {
const result = dym(pkg, 'nistall')
t.match(result, 'npm install')
})
t.test('sttest', async t => {
const result = dym(pkg, 'sttest')
t.match(result, 'npm test')
t.match(result, 'npm run posttest')
})
t.test('npz', async t => {
const result = dym(pkg, 'npxx')
t.match(result, 'npm exec npx')
})
t.test('qwuijbo', async t => {
const result = dym(pkg, 'qwuijbo')
t.match(result, '')
})
})
t.test('with no package.json', t => {
t.test('nistall', async t => {
const result = dym(null, 'nistall')
t.match(result, 'npm install')
})
t.end()
})
t.test('missing bin and script properties', async t => {
const pkg = {
name: 'missing-bin',
}
const result = dym(pkg, 'nistall')
t.match(result, 'npm install')
})
})

199
deps/npm/test/lib/utils/display.js vendored Normal file
View File

@ -0,0 +1,199 @@
const t = require('tap')
const timers = require('node:timers/promises')
const tmock = require('../../fixtures/tmock')
const mockLogs = require('../../fixtures/mock-logs')
const mockGlobals = require('@npmcli/mock-globals')
const { inspect } = require('node:util')
const mockDisplay = async (t, { mocks, load } = {}) => {
const procLog = require('proc-log')
const logs = mockLogs()
const Display = tmock(t, '{LIB}/utils/display', mocks)
const display = new Display(logs.streams)
const displayLoad = async (opts) => display.load({
loglevel: 'silly',
stderrColor: false,
stdoutColot: false,
heading: 'npm',
...opts,
})
if (load !== false) {
await displayLoad(load)
}
t.teardown(() => display.off())
return {
...procLog,
display,
displayLoad,
...logs.logs,
}
}
t.test('can log cleanly', async (t) => {
const { log, logs } = await mockDisplay(t)
log.error('', 'test\x00message')
t.match(logs.error, ['test^@message'])
})
t.test('can handle special eresolves', async (t) => {
const explains = []
const { log, logs } = await mockDisplay(t, {
mocks: {
'{LIB}/utils/explain-eresolve.js': {
explain: (...args) => {
explains.push(args)
return 'EXPLAIN'
},
},
},
})
log.warn('ERESOLVE', 'hello', { some: 'object' })
t.strictSame(logs.warn, ['ERESOLVE hello', 'EXPLAIN'])
t.match(explains, [[{ some: 'object' }, Function, 2]])
})
t.test('can buffer output when paused', async t => {
const { displayLoad, outputs, output } = await mockDisplay(t, {
load: false,
})
output.buffer('Message 1')
output.standard('Message 2')
t.strictSame(outputs, [])
await displayLoad()
t.strictSame(outputs, ['Message 1', 'Message 2'])
})
t.test('can do progress', async (t) => {
const { log, logs, outputs, outputErrors, output, input } = await mockDisplay(t, {
load: {
progress: true,
},
})
// wait for initial timer interval to load
await timers.setTimeout(200)
log.error('', 'before input')
output.standard('before input')
const end = input.start()
log.error('', 'during input')
output.standard('during input')
end()
// wait long enough for all spinner frames to render
await timers.setTimeout(800)
log.error('', 'after input')
output.standard('after input')
t.strictSame([...new Set(outputErrors)].sort(), ['-', '/', '\\', '|'])
t.strictSame(logs, ['error before input', 'error during input', 'error after input'])
t.strictSame(outputs, ['before input', 'during input', 'after input'])
})
t.test('handles log throwing', async (t) => {
class ThrowInspect {
#crashes = 0;
[inspect.custom] () {
throw new Error(`Crashed ${++this.#crashes}`)
}
}
const errors = []
mockGlobals(t, { 'console.error': (...msg) => errors.push(msg) })
const { log, logs } = await mockDisplay(t)
log.error('woah', new ThrowInspect())
t.strictSame(logs.error, [])
t.equal(errors.length, 1)
t.match(errors[0], [
'attempt to log crashed',
new Error('Crashed 1'),
new Error('Crashed 2'),
])
})
t.test('incorrect levels', async t => {
const { outputs } = await mockDisplay(t)
process.emit('output', 'not a real level')
t.strictSame(outputs, [], 'output is ignored')
})
t.test('Display.clean', async (t) => {
const { output, outputs, clearOutput } = await mockDisplay(t)
class CustomObj {
#inspected
constructor (val) {
this.#inspected = val
}
[inspect.custom] () {
return this.#inspected
}
}
const tests = [
[477, '477'],
[null, 'null'],
[NaN, 'NaN'],
[true, 'true'],
[undefined, 'undefined'],
['🚀', '🚀'],
// Cover the bounds of each range and a few characters from inside each range
// \x00 through \x1f
['hello\x00world', 'hello^@world'],
['hello\x07world', 'hello^Gworld'],
['hello\x1bworld', 'hello^[world'],
['hello\x1eworld', 'hello^^world'],
['hello\x1fworld', 'hello^_world'],
// \x7f is C0
['hello\x7fworld', 'hello^?world'],
// \x80 through \x9f
['hello\x80world', 'hello^@world'],
['hello\x87world', 'hello^Gworld'],
['hello\x9eworld', 'hello^^world'],
['hello\x9fworld', 'hello^_world'],
// Allowed C0
['hello\tworld', 'hello\tworld'],
['hello\nworld', 'hello\nworld'],
['hello\vworld', 'hello\vworld'],
['hello\rworld', 'hello\rworld'],
// Allowed SGR
['hello\x1b[38;5;254mworld', 'hello\x1b[38;5;254mworld'],
['hello\x1b[mworld', 'hello\x1b[mworld'],
// Unallowed CSI / OSC
['hello\x1b[2Aworld', 'hello^[[2Aworld'],
['hello\x9b[2Aworld', 'hello^[[2Aworld'],
['hello\x9decho goodbye\x9cworld', 'hello^]echo goodbye^\\world'],
// This is done twice to ensure we define inspect.custom as writable
[{ test: 'object' }, "{ test: 'object' }"],
// Make sure custom util.inspect doesn't bypass our cleaning
[new CustomObj(NaN), 'NaN'],
[new CustomObj(null), 'null'],
[new CustomObj(477), '477'],
[new CustomObj({ custom: 'rend\x00ering' }), "{ custom: 'rend\\x00ering' }"],
[new CustomObj('custom\x00rendering'), 'custom^@rendering'],
[new CustomObj(undefined), 'undefined'],
// UTF-16 form of 8-bit C1
['hello\xc2\x9bworld', 'hello\xc2^[world'],
]
for (const [dirty, clean] of tests) {
output.standard(dirty)
t.equal(outputs[0], clean)
clearOutput()
}
})

433
deps/npm/test/lib/utils/error-message.js vendored Normal file
View File

@ -0,0 +1,433 @@
const t = require('tap')
const { resolve } = require('node:path')
const fs = require('node:fs/promises')
const { load: _loadMockNpm } = require('../../fixtures/mock-npm.js')
const mockGlobals = require('@npmcli/mock-globals')
const tmock = require('../../fixtures/tmock')
const { cleanCwd, cleanDate } = require('../../fixtures/clean-snapshot.js')
t.formatSnapshot = (p) => {
if (Array.isArray(p.files) && !p.files.length) {
delete p.files
}
if (p?.json === undefined) {
delete p.json
}
return p
}
t.cleanSnapshot = p => cleanDate(cleanCwd(p))
mockGlobals(t, {
process: {
getuid: () => 867,
getgid: () => 5309,
arch: 'x64',
version: '123.456.789-node',
platform: 'posix',
},
})
const loadMockNpm = async (t, { errorMocks, ...opts } = {}) => {
const mockError = tmock(t, '{LIB}/utils/error-message.js', errorMocks)
const res = await _loadMockNpm(t, {
...opts,
mocks: {
...opts.mocks,
'{ROOT}/package.json': {
version: '123.456.789-npm',
},
},
})
return {
...res,
errorMessage: (er) => mockError.errorMessage(er, res.npm),
}
}
t.test('just simple messages', async t => {
const { errorMessage } = await loadMockNpm(t, {
prefixDir: { 'package-lock.json': '{}' },
command: 'audit',
exec: true,
})
const codes = [
'ENOAUDIT',
'ENOLOCK',
'ECONNREFUSED',
'ENOGIT',
'EPUBLISHCONFLICT',
'EISGIT',
'EEXIST',
'ENEEDAUTH',
'ECONNRESET',
'ENOTFOUND',
'ETIMEDOUT',
'EAI_FAIL',
'EBADENGINE',
'ENOSPC',
'EROFS',
'ENOENT',
'EMISSINGARG',
'EUNKNOWNTYPE',
'EINVALIDTYPE',
'ETOOMANYARGS',
'ETARGET',
'E403',
'ERR_SOCKET_TIMEOUT',
]
for (const code of codes) {
const path = '/some/path'
const pkgid = 'some@package'
const file = '/some/file'
const stack = 'dummy stack trace'
const er = Object.assign(new Error('foo'), {
code,
path,
pkgid,
file,
stack,
})
t.matchSnapshot(errorMessage(er))
}
})
t.test('replace message/stack sensistive info', async t => {
const { errorMessage } = await loadMockNpm(t, { command: 'audit' })
const er = Object.assign(new Error('Error at registry: https://user:pass@registry.npmjs.org/'), {
code: 'ENOAUDIT',
path: '/some/path',
pkgid: 'some@package',
file: '/some/file',
stack: 'dummy stack trace at https://user:pass@registry.npmjs.org/',
})
t.matchSnapshot(errorMessage(er))
})
t.test('bad engine without config loaded', async t => {
const { errorMessage } = await loadMockNpm(t, { load: false })
const path = '/some/path'
const pkgid = 'some@package'
const file = '/some/file'
const stack = 'dummy stack trace'
const er = Object.assign(new Error('foo'), {
code: 'EBADENGINE',
path,
pkgid,
file,
stack,
})
t.matchSnapshot(errorMessage(er))
})
t.test('enoent without a file', async t => {
const { errorMessage } = await loadMockNpm(t)
const path = '/some/path'
const pkgid = 'some@package'
const stack = 'dummy stack trace'
const er = Object.assign(new Error('foo'), {
code: 'ENOENT',
path,
pkgid,
stack,
})
t.matchSnapshot(errorMessage(er))
})
t.test('enolock without a command', async t => {
const { errorMessage } = await loadMockNpm(t, { command: null })
const path = '/some/path'
const pkgid = 'some@package'
const file = '/some/file'
const stack = 'dummy stack trace'
const er = Object.assign(new Error('foo'), {
code: 'ENOLOCK',
path,
pkgid,
file,
stack,
})
t.matchSnapshot(errorMessage(er))
})
t.test('default message', async t => {
const { errorMessage } = await loadMockNpm(t)
t.matchSnapshot(errorMessage(new Error('error object')))
t.matchSnapshot(errorMessage('error string'))
t.matchSnapshot(errorMessage(Object.assign(new Error('cmd err'), {
cmd: 'some command',
signal: 'SIGYOLO',
args: ['a', 'r', 'g', 's'],
stdout: 'stdout',
stderr: 'stderr',
})))
})
t.test('args are cleaned', async t => {
const { errorMessage } = await loadMockNpm(t)
t.matchSnapshot(errorMessage(Object.assign(new Error('cmd err'), {
cmd: 'some command',
signal: 'SIGYOLO',
args: ['a', 'r', 'g', 's', 'https://evil:password@npmjs.org'],
stdout: 'stdout',
stderr: 'stderr',
})))
})
t.test('eacces/eperm', async t => {
const runTest = (windows, loaded, cachePath, cacheDest) => async t => {
const { errorMessage, logs, cache } = await loadMockNpm(t, {
windows,
load: loaded,
globals: windows ? { 'process.platform': 'win32' } : [],
})
const path = `${cachePath ? cache : '/not/cache/dir'}/path`
const dest = `${cacheDest ? cache : '/not/cache/dir'}/dest`
const er = Object.assign(new Error('whoopsie'), {
code: 'EACCES',
path,
dest,
stack: 'dummy stack trace',
})
t.matchSnapshot(errorMessage(er))
t.matchSnapshot(logs.verbose)
}
for (const windows of [true, false]) {
for (const loaded of [true, false]) {
for (const cachePath of [true, false]) {
for (const cacheDest of [true, false]) {
const m = JSON.stringify({ windows, loaded, cachePath, cacheDest })
t.test(m, runTest(windows, loaded, cachePath, cacheDest))
}
}
}
}
})
t.test('json parse', t => {
mockGlobals(t, { 'process.argv': ['arg', 'v'] })
t.test('merge conflict in package.json', async t => {
const prefixDir = {
'package.json': await fs.readFile(
resolve(__dirname, '../../fixtures/merge-conflict.json'), 'utf-8'),
}
const { errorMessage, npm } = await loadMockNpm(t, { prefixDir })
t.matchSnapshot(errorMessage(Object.assign(new Error('conflicted'), {
code: 'EJSONPARSE',
path: resolve(npm.prefix, 'package.json'),
})))
t.end()
})
t.test('just regular bad json in package.json', async t => {
const prefixDir = {
'package.json': 'not even slightly json',
}
const { errorMessage, npm } = await loadMockNpm(t, { prefixDir })
t.matchSnapshot(errorMessage(Object.assign(new Error('not json'), {
code: 'EJSONPARSE',
path: resolve(npm.prefix, 'package.json'),
})))
t.end()
})
t.test('json somewhere else', async t => {
const prefixDir = {
'blerg.json': 'not even slightly json',
}
const { npm, errorMessage } = await loadMockNpm(t, { prefixDir })
t.matchSnapshot(errorMessage(Object.assign(new Error('not json'), {
code: 'EJSONPARSE',
path: resolve(npm.prefix, 'blerg.json'),
})))
t.end()
})
t.end()
})
t.test('eotp/e401', async t => {
const { errorMessage } = await loadMockNpm(t)
t.test('401, no auth headers', t => {
t.matchSnapshot(errorMessage(Object.assign(new Error('nope'), {
code: 'E401',
})))
t.end()
})
t.test('401, no message', t => {
t.matchSnapshot(errorMessage({
code: 'E401',
}))
t.end()
})
t.test('one-time pass challenge code', t => {
t.matchSnapshot(errorMessage(Object.assign(new Error('nope'), {
code: 'EOTP',
})))
t.end()
})
t.test('one-time pass challenge message', t => {
const message = 'one-time pass'
t.matchSnapshot(errorMessage(Object.assign(new Error(message), {
code: 'E401',
})))
t.end()
})
t.test('www-authenticate challenges', t => {
const auths = [
'Bearer realm=do, charset="UTF-8", challenge="yourself"',
'Basic realm=by, charset="UTF-8", challenge="your friends"',
'PickACardAnyCard realm=friday, charset="UTF-8"',
'WashYourHands, charset="UTF-8"',
]
t.plan(auths.length)
for (const auth of auths) {
t.test(auth, t => {
const er = Object.assign(new Error('challenge!'), {
headers: {
'www-authenticate': [auth],
},
code: 'E401',
})
t.matchSnapshot(errorMessage(er))
t.end()
})
}
})
})
t.test('404', async t => {
const { errorMessage } = await loadMockNpm(t)
t.test('no package id', t => {
const er = Object.assign(new Error('404 not found'), { code: 'E404' })
t.matchSnapshot(errorMessage(er))
t.end()
})
t.test('you should publish it', t => {
const er = Object.assign(new Error('404 not found'), {
pkgid: 'yolo',
code: 'E404',
})
t.matchSnapshot(errorMessage(er))
t.end()
})
t.test('name with warning', t => {
const er = Object.assign(new Error('404 not found'), {
pkgid: new Array(215).fill('x').join(''),
code: 'E404',
})
t.matchSnapshot(errorMessage(er))
t.end()
})
t.test('name with error', t => {
const er = Object.assign(new Error('404 not found'), {
pkgid: 'node_modules',
code: 'E404',
})
t.matchSnapshot(errorMessage(er))
t.end()
})
t.test('cleans sensitive info from package id', t => {
const er = Object.assign(new Error('404 not found'), {
pkgid: 'http://evil:password@npmjs.org/not-found',
code: 'E404',
})
t.matchSnapshot(errorMessage(er))
t.end()
})
})
t.test('bad platform', async t => {
const { errorMessage } = await loadMockNpm(t)
t.test('string os/arch', t => {
const er = Object.assign(new Error('a bad plat'), {
pkgid: 'lodash@1.0.0',
current: {
os: 'posix',
cpu: 'x64',
},
required: {
os: '!yours',
cpu: 'x420',
},
code: 'EBADPLATFORM',
})
t.matchSnapshot(errorMessage(er))
t.end()
})
t.test('array os/arch', t => {
const er = Object.assign(new Error('a bad plat'), {
pkgid: 'lodash@1.0.0',
current: {
os: 'posix',
cpu: 'x64',
},
required: {
os: ['!yours', 'mine'],
cpu: ['x867', 'x5309'],
},
code: 'EBADPLATFORM',
})
t.matchSnapshot(errorMessage(er))
t.end()
})
t.test('omits keys with no required value', t => {
const er = Object.assign(new Error('a bad plat'), {
pkgid: 'lodash@1.0.0',
current: {
os: 'posix',
cpu: 'x64',
libc: 'musl',
},
required: {
os: ['!yours', 'mine'],
libc: [], // empty arrays should also lead to a key being removed
cpu: undefined, // XXX npm-install-checks sets unused keys to undefined
},
code: 'EBADPLATFORM',
})
const msg = errorMessage(er)
t.matchSnapshot(msg)
t.notMatch(msg, /Valid cpu/, 'omits cpu from message')
t.notMatch(msg, /Valid libc/, 'omits libc from message')
t.end()
})
})
t.test('explain ERESOLVE errors', async t => {
const EXPLAIN_CALLED = []
const { errorMessage } = await loadMockNpm(t, {
errorMocks: {
'{LIB}/utils/explain-eresolve.js': {
report: (...args) => {
EXPLAIN_CALLED.push(...args)
return { explanation: 'explanation', file: 'report' }
},
},
},
config: {
color: 'always',
},
})
const er = Object.assign(new Error('could not resolve'), {
code: 'ERESOLVE',
})
t.matchSnapshot(errorMessage(er))
t.equal(EXPLAIN_CALLED.length, 3)
t.match(EXPLAIN_CALLED, [er, Function, Function])
t.not(EXPLAIN_CALLED[1].level, 0, 'color chalk level is not 0')
t.equal(EXPLAIN_CALLED[2].level, 0, 'colorless chalk level is 0')
})

277
deps/npm/test/lib/utils/explain-dep.js vendored Normal file
View File

@ -0,0 +1,277 @@
const { resolve } = require('node:path')
const t = require('tap')
const { explainNode, printNode } = require('../../../lib/utils/explain-dep.js')
const { cleanCwd } = require('../../fixtures/clean-snapshot')
t.cleanSnapshot = (str) => cleanCwd(str)
const getCases = (testdir) => {
const cases = {
prodDep: {
name: 'prod-dep',
version: '1.2.3',
location: 'node_modules/prod-dep',
dependents: [
{
type: 'prod',
name: 'prod-dep',
spec: '1.x',
from: {
location: '/path/to/project',
},
},
],
},
deepDev: {
name: 'deep-dev',
version: '2.3.4',
location: 'node_modules/deep-dev',
dev: true,
dependents: [
{
type: 'prod',
name: 'deep-dev',
spec: '2.x',
from: {
name: 'metadev',
version: '3.4.5',
location: 'node_modules/dev/node_modules/metadev',
dependents: [
{
type: 'prod',
name: 'metadev',
spec: '3.x',
from: {
name: 'topdev',
version: '4.5.6',
location: 'node_modules/topdev',
dependents: [
{
type: 'dev',
name: 'topdev',
spec: '4.x',
from: {
location: '/path/to/project',
},
},
],
},
},
],
},
},
],
},
optional: {
name: 'optdep',
version: '1.0.0',
location: 'node_modules/optdep',
optional: true,
dependents: [
{
type: 'optional',
name: 'optdep',
spec: '1.0.0',
from: {
location: '/path/to/project',
},
},
],
},
peer: {
name: 'peer',
version: '1.0.0',
location: 'node_modules/peer',
peer: true,
dependents: [
{
type: 'peer',
name: 'peer',
spec: '1.0.0',
from: {
location: '/path/to/project',
},
},
],
},
bundled: {
name: 'bundle-of-joy',
version: '1.0.0',
location: 'node_modules/bundle-of-joy',
bundled: true,
dependents: [
{
type: 'prod',
name: 'prod-dep',
spec: '1.x',
bundled: true,
from: {
location: '/path/to/project',
},
},
],
},
extraneous: {
name: 'extra-neos',
version: '1337.420.69-lol',
location: 'node_modules/extra-neos',
dependents: [],
extraneous: true,
},
overridden: {
name: 'overridden-root',
version: '1.0.0',
location: 'node_modules/overridden-root',
overridden: true,
dependents: [{
type: 'prod',
name: 'overridden-dep',
spec: '1.0.0',
rawSpec: '^2.0.0',
overridden: true,
from: {
location: '/path/to/project',
},
}],
},
}
cases.manyDeps = {
name: 'manydep',
version: '1.0.0',
dependents: [
{
type: 'prod',
name: 'manydep',
spec: '1.0.0',
from: cases.prodDep,
},
{
type: 'optional',
name: 'manydep',
spec: '1.x',
from: cases.optional,
},
{
type: 'prod',
name: 'manydep',
spec: '1.0.x',
from: cases.extraneous,
},
{
type: 'dev',
name: 'manydep',
spec: '*',
from: cases.deepDev,
},
{
type: 'peer',
name: 'manydep',
spec: '>1.0.0-beta <1.0.1',
from: cases.peer,
},
{
type: 'prod',
name: 'manydep',
spec: '>1.0.0-beta <1.0.1',
from: {
location: '/path/to/project',
},
},
{
type: 'prod',
name: 'manydep',
spec: '1',
from: {
name: 'a package with a pretty long name',
version: '1.2.3',
dependents: {
location: '/path/to/project',
},
},
},
{
type: 'prod',
name: 'manydep',
spec: '1',
from: {
name: 'another package with a pretty long name',
version: '1.2.3',
dependents: {
location: '/path/to/project',
},
},
},
{
type: 'prod',
name: 'manydep',
spec: '1',
from: {
name: 'yet another a package with a pretty long name',
version: '1.2.3',
dependents: {
location: '/path/to/project',
},
},
},
],
}
cases.workspaces = {
name: 'a',
version: '1.0.0',
location: 'a',
isWorkspace: true,
dependents: [],
linksIn: [
{
name: 'a',
version: '1.0.0',
location: 'node_modules/a',
isWorkspace: true,
dependents: [
{
type: 'workspace',
name: 'a',
spec: `file:${resolve(testdir, 'ws-project', 'a')}`,
from: { location: resolve(testdir, 'ws-project') },
},
],
},
],
}
return cases
}
t.test('basic', async t => {
const { Chalk } = await import('chalk')
const color = new Chalk({ level: 3 })
const noColor = new Chalk({ level: 0 })
const testdir = t.testdir()
const cases = getCases(testdir)
for (const [name, expl] of Object.entries(getCases(testdir))) {
t.test(name, t => {
t.matchSnapshot(printNode(expl, color), 'print color')
t.matchSnapshot(printNode(expl, noColor), 'print nocolor')
t.matchSnapshot(explainNode(expl, Infinity, color), 'explain color deep')
t.matchSnapshot(explainNode(expl, 2, noColor), 'explain nocolor shallow')
t.end()
})
}
// make sure that we show the last one if it's the only one that would
// hit the ...
cases.manyDeps.dependents.pop()
t.matchSnapshot(explainNode(cases.manyDeps, 2, noColor), 'ellipses test one')
cases.manyDeps.dependents.pop()
t.matchSnapshot(explainNode(cases.manyDeps, 2, noColor), 'ellipses test two')
})

View File

@ -0,0 +1,31 @@
const t = require('tap')
const { explain, report } = require('../../../lib/utils/explain-eresolve.js')
const cases = require('../../fixtures/eresolve-explanations.js')
t.test('basic', async t => {
const { Chalk } = await import('chalk')
const color = new Chalk({ level: 3 })
const noColor = new Chalk({ level: 0 })
for (const [name, expl] of Object.entries(cases)) {
// no sense storing the whole contents of each object in the snapshot
// we can trust that JSON.stringify still works just fine.
expl.toJSON = () => ({ name, json: true })
t.test(name, t => {
const colorReport = report(expl, color, noColor)
t.matchSnapshot(colorReport.explanation, 'report with color')
t.matchSnapshot(colorReport.file, 'report from color')
const noColorReport = report(expl, noColor, noColor)
t.matchSnapshot(noColorReport.explanation, 'report with no color')
t.equal(noColorReport.file, colorReport.file, 'same report written for object')
t.matchSnapshot(explain(expl, color, 2), 'explain with color, depth of 2')
t.matchSnapshot(explain(expl, noColor, 6), 'explain with no color, depth of 6')
t.end()
})
}
})

View File

@ -0,0 +1,222 @@
const { resolve } = require('node:path')
const t = require('tap')
const getWorkspaces = require('../../../lib/utils/get-workspaces.js')
const normalizePath = p => p
.replace(/\\+/g, '/')
.replace(/\r\n/g, '\n')
const cleanOutput = (str, path) => normalizePath(str)
.replace(normalizePath(path), '{PATH}')
const clean = (res, path) => {
const cleaned = new Map()
for (const [key, value] of res.entries()) {
cleaned.set(key, cleanOutput(value, path))
}
return cleaned
}
const path = t.testdir({
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
scripts: { glorp: 'echo a doing the glerp glop' },
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '2.0.0',
scripts: { glorp: 'echo b doing the glerp glop' },
}),
},
c: {
'package.json': JSON.stringify({
name: 'c',
version: '1.0.0',
scripts: {
test: 'exit 0',
posttest: 'echo posttest',
lorem: 'echo c lorem',
},
}),
},
d: {
'package.json': JSON.stringify({
name: 'd',
version: '1.0.0',
scripts: {
test: 'exit 0',
posttest: 'echo posttest',
},
}),
},
e: {
'package.json': JSON.stringify({
name: 'e',
scripts: { test: 'exit 0', start: 'echo start something' },
}),
},
noscripts: {
'package.json': JSON.stringify({
name: 'noscripts',
version: '1.0.0',
}),
},
},
'package.json': JSON.stringify({
name: 'x',
version: '1.2.3',
workspaces: ['packages/*'],
}),
})
let workspaces
t.test('filter by package name', async t => {
workspaces = await getWorkspaces(['a', 'b'], { path, relativeFrom: path })
t.same(
clean(workspaces, path),
new Map(Object.entries({
a: '{PATH}/packages/a',
b: '{PATH}/packages/b',
})),
'should filter by package name'
)
})
t.test('include workspace root', async t => {
workspaces = await getWorkspaces(['a', 'b'],
{ path, includeWorkspaceRoot: true, relativeFrom: path })
t.same(
clean(workspaces, path),
new Map(Object.entries({
x: '{PATH}',
a: '{PATH}/packages/a',
b: '{PATH}/packages/b',
})),
'include rootspace root'
)
})
t.test('filter by package directory', async t => {
workspaces = await getWorkspaces(['./packages/c'], { path, relativeFrom: path })
t.same(
clean(workspaces, path),
new Map(Object.entries({
c: '{PATH}/packages/c',
})),
'should filter by package directory'
)
})
t.test('filter by rel package directory', async t => {
workspaces = await getWorkspaces(['packages/c'], { path, relativeFrom: path })
t.same(
clean(workspaces, path),
new Map(Object.entries({
c: '{PATH}/packages/c',
})),
'should filter by rel package directory'
)
})
t.test('filter by absolute package directory', async t => {
workspaces = await getWorkspaces([resolve(path, 'packages/c')], { path, relativeFrom: path })
t.same(
clean(workspaces, path),
new Map(Object.entries({
c: '{PATH}/packages/c',
})),
'should filter by absolute package directory'
)
})
t.test('filter by parent directory name', async t => {
workspaces = await getWorkspaces(['packages'], { path, relativeFrom: path })
t.same(
clean(workspaces, path),
new Map(Object.entries({
a: '{PATH}/packages/a',
b: '{PATH}/packages/b',
c: '{PATH}/packages/c',
d: '{PATH}/packages/d',
e: '{PATH}/packages/e',
noscripts: '{PATH}/packages/noscripts',
})),
'should filter by parent directory name'
)
})
t.test('filter by parent directory path', async t => {
workspaces = await getWorkspaces(['./packages/'], { path, relativeFrom: path })
t.same(
clean(workspaces, path),
new Map(Object.entries({
a: '{PATH}/packages/a',
b: '{PATH}/packages/b',
c: '{PATH}/packages/c',
d: '{PATH}/packages/d',
e: '{PATH}/packages/e',
noscripts: '{PATH}/packages/noscripts',
})),
'should filter by parent directory path'
)
})
t.test('filter by absolute parent directory path', async t => {
workspaces = await getWorkspaces([resolve(path, './packages')], { path, relativeFrom: path })
t.same(
clean(workspaces, path),
new Map(Object.entries({
a: '{PATH}/packages/a',
b: '{PATH}/packages/b',
c: '{PATH}/packages/c',
d: '{PATH}/packages/d',
e: '{PATH}/packages/e',
noscripts: '{PATH}/packages/noscripts',
})),
'should filter by absolute parent directory path'
)
})
t.test('no filter set', async t => {
workspaces = await getWorkspaces([], { path, relativeFrom: path })
t.same(
clean(workspaces, path),
new Map(Object.entries({
a: '{PATH}/packages/a',
b: '{PATH}/packages/b',
c: '{PATH}/packages/c',
d: '{PATH}/packages/d',
e: '{PATH}/packages/e',
noscripts: '{PATH}/packages/noscripts',
})),
'should return all workspaces if no filter set'
)
})
t.test('missing workspace', async t => {
await t.rejects(
getWorkspaces(['missing'], { path, relativeFrom: path }),
/No workspaces found/,
'should throw no workspaces found error'
)
})
t.test('no workspaces configured', async t => {
const unconfiguredWorkspaces = t.testdir({
'package.json': JSON.stringify({
name: 'no-configured-workspaces',
version: '1.0.0',
}),
})
await t.rejects(
getWorkspaces([], { path: unconfiguredWorkspaces, relativeFrom: path }),
/No workspaces found/,
'should throw no workspaces found error'
)
})

View File

@ -0,0 +1,205 @@
const t = require('tap')
const installedDeep = require('../../../lib/utils/installed-deep.js')
const mockNpm = require('../../fixtures/mock-npm')
const fixture = {
'package.json': JSON.stringify({
name: 'test-installed-deep',
version: '1.0.0',
dependencies: {
a: '^1.0.0',
b: '^1.0.0',
c: '^1.0.0',
},
devDependencies: {
d: '^1.0.0',
},
peerDependencies: {
e: '^1.0.0',
},
}),
node_modules: {
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
dependencies: {
f: '^1.0.0',
},
}),
},
b: {
'package.json': JSON.stringify({
name: 'b',
version: '1.0.0',
}),
},
c: {
'package.json': JSON.stringify({
name: 'c',
version: '1.0.0',
dependencies: {
ch: '1.0.0',
},
}),
},
ch: {
'package.json': JSON.stringify({
name: 'ch',
version: '1.0.0',
}),
},
d: {
'package.json': JSON.stringify({
name: 'd',
version: '1.0.0',
}),
},
e: {
'package.json': JSON.stringify({
name: 'e',
version: '1.0.0',
}),
},
f: {
'package.json': JSON.stringify({
name: 'f',
version: '1.0.0',
dependencies: {
g: '^1.0.0',
e: '^2.0.0',
},
}),
node_modules: {
e: {
'package.json': JSON.stringify({
name: 'e',
version: '2.0.0',
dependencies: {
bb: '^1.0.0',
},
}),
node_modules: {
bb: {
'package.json': JSON.stringify({
name: 'bb',
version: '1.0.0',
}),
},
},
},
},
},
g: {
'package.json': JSON.stringify({
name: 'g',
version: '1.0.0',
}),
},
},
}
const globalFixture = {
node_modules: {
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.0.0',
}),
},
bar: {
'package.json': JSON.stringify({
name: 'bar',
version: '1.0.0',
dependencies: {
'a-bar': '^1.0.0',
},
}),
node_modules: {
'a-bar': {
'package.json': JSON.stringify({
name: 'a-bar',
version: '1.0.0',
}),
},
},
},
},
}
const mockDeep = async (t, config) => {
const mock = await mockNpm(t, {
prefixDir: fixture,
globalPrefixDir: globalFixture,
config: {
depth: Infinity,
...config,
},
})
const res = await installedDeep(mock.npm)
return res
}
t.test('get list of package names', async t => {
const res = await mockDeep(t)
t.same(
res,
[
['bar', '-g'],
['foo', '-g'],
['a-bar', '-g'],
'a', 'b', 'c',
'ch', 'd', 'e',
'f', 'g', 'bb',
],
'should return list of package names and global flag'
)
t.end()
})
t.test('get list of package names as global', async t => {
const res = await mockDeep(t, { global: true })
t.same(
res,
[
'bar',
'foo',
'a-bar',
],
'should return list of global packages with no extra flags'
)
})
t.test('limit depth', async t => {
const res = await mockDeep(t, { depth: 0 })
t.same(
res,
[
['bar', '-g'],
['foo', '-g'],
// XXX https://github.com/npm/statusboard/issues/380
['a-bar', '-g'],
'a', 'b',
'c', 'ch',
'd', 'e',
'f', 'g',
],
'should print only packages up to the specified depth'
)
})
t.test('limit depth as global', async t => {
const res = await mockDeep(t, { depth: 0, global: true })
t.same(
res,
[
'bar',
'foo',
// https://github.com/npm/statusboard/issues/380
'a-bar',
],
'should reorder so that packages above that level depth goes last'
)
})

View File

@ -0,0 +1,55 @@
const t = require('tap')
const installed = require('../../../lib/utils/installed-shallow.js')
const mockNpm = require('../../fixtures/mock-npm')
const mockShallow = async (t, config) => {
const res = await mockNpm(t, {
globalPrefixDir: {
node_modules: {
x: {},
'@scope': {
y: {},
},
},
},
prefixDir: {
node_modules: {
a: {},
'@scope': {
b: {},
},
},
},
config: { global: false, ...config },
})
return res
}
t.test('global not set, include globals with -g', async t => {
const { npm } = await mockShallow(t)
const opt = { conf: { argv: { remain: [] } } }
const res = await installed(npm, opt)
t.strictSame(res.sort(), [
'@scope/y -g',
'x -g',
'a',
'@scope/b',
].sort())
})
t.test('global set, include globals and not locals', async t => {
const { npm } = await mockShallow(t, { global: true })
const opt = { conf: { argv: { remain: [] } } }
const res = await installed(npm, opt)
t.strictSame(res.sort(), [
'@scope/y',
'x',
].sort())
})
t.test('more than 3 items in argv, skip it', async t => {
const { npm } = await mockShallow(t)
const opt = { conf: { argv: { remain: [1, 2, 3, 4, 5, 6] } } }
const res = await installed(npm, opt)
t.strictSame(res, null)
})

361
deps/npm/test/lib/utils/log-file.js vendored Normal file
View File

@ -0,0 +1,361 @@
const t = require('tap')
const _fs = require('node:fs')
const fs = _fs.promises
const path = require('node:path')
const os = require('node:os')
const fsMiniPass = require('fs-minipass')
const tmock = require('../../fixtures/tmock')
const LogFile = require('../../../lib/utils/log-file.js')
const { cleanCwd, cleanDate } = require('../../fixtures/clean-snapshot')
t.cleanSnapshot = (s) => cleanDate(cleanCwd(s))
const getId = (d = new Date()) => d.toISOString().replace(/[.:]/g, '_')
const last = arr => arr[arr.length - 1]
const range = (n) => Array.from(Array(n).keys())
const makeOldLogs = (count, oldStyle) => {
const d = new Date()
d.setHours(-1)
d.setSeconds(0)
return range(oldStyle ? count : (count / 2)).reduce((acc, i) => {
const cloneDate = new Date(d.getTime())
cloneDate.setSeconds(i)
const dateId = getId(cloneDate)
if (oldStyle) {
acc[`${dateId}-debug.log`] = 'hello'
} else {
acc[`${dateId}-debug-0.log`] = 'hello'
acc[`${dateId}-debug-1.log`] = 'hello'
}
return acc
}, {})
}
const cleanErr = (message) => {
const err = new Error(message)
const stack = err.stack.split('\n')
err.stack = stack[0] + '\n' + range(10)
.map((__, i) => stack[1].replace(/^(\s+at\s).*/, `$1stack trace line ${i}`))
.join('\n')
return err
}
const loadLogFile = async (t, { buffer = [], mocks, testdir = {}, ...options } = {}) => {
const root = t.testdir(testdir)
const MockLogFile = tmock(t, '{LIB}/utils/log-file.js', mocks)
const logFile = new MockLogFile(Object.keys(options).length ? options : undefined)
// Create a fake public method since there is not one on logFile anymore
logFile.log = (...b) => process.emit('log', ...b)
buffer.forEach((b) => logFile.log(...b))
const id = getId()
await logFile.load({ path: path.join(root, `${id}-`), ...options })
t.teardown(() => logFile.off())
return {
root,
logFile,
LogFile,
readLogs: async () => {
const logDir = await fs.readdir(root, { withFileTypes: true })
const logFiles = logDir
.filter(f => f.isFile())
.map((f) => path.join(root, f.name))
.filter((f) => _fs.existsSync(f))
return Promise.all(logFiles.map(async (f) => {
const content = await fs.readFile(f, 'utf8')
const rawLogs = content.split(os.EOL)
return {
filename: f,
content,
rawLogs,
logs: rawLogs.filter(Boolean),
}
}))
},
}
}
t.test('init', async t => {
const maxLogsPerFile = 10
const { root, logFile, readLogs } = await loadLogFile(t, {
maxLogsPerFile,
maxFilesPerProcess: 20,
buffer: [['error', 'buffered']],
})
for (const i of range(50)) {
logFile.log('error', `log ${i}`)
}
// Ignored
logFile.log('pause')
logFile.log('resume')
logFile.log('pause')
for (const i of range(50)) {
logFile.log('verb', `log ${i}`)
}
logFile.off()
logFile.log('error', 'ignored')
const logs = await readLogs()
t.equal(logs.length, 11, 'total log files')
t.ok(logs.slice(0, 10).every(f => f.logs.length === maxLogsPerFile), 'max logs per file')
t.ok(last(logs).logs.length, 1, 'last file has remaining logs')
t.ok(logs.every(f => last(f.rawLogs) === ''), 'all logs end with newline')
t.strictSame(
logFile.files,
logs.map((l) => path.resolve(root, l.filename))
)
})
t.test('max files per process', async t => {
const maxLogsPerFile = 10
const maxFilesPerProcess = 5
const { logFile, readLogs } = await loadLogFile(t, {
maxLogsPerFile,
maxFilesPerProcess,
})
for (const i of range(maxLogsPerFile * maxFilesPerProcess)) {
logFile.log('error', `log ${i}`)
}
for (const i of range(5)) {
logFile.log('verbose', `ignored after maxlogs hit ${i}`)
}
const logs = await readLogs()
t.equal(logs.length, maxFilesPerProcess, 'total log files')
t.match(last(last(logs).logs), /49 error log \d+/)
})
t.test('stream error', async t => {
let times = 0
const { logFile, readLogs } = await loadLogFile(t, {
maxLogsPerFile: 1,
maxFilesPerProcess: 99,
mocks: {
'fs-minipass': {
WriteStreamSync: class {
constructor (...args) {
if (times >= 5) {
throw new Error('bad stream')
}
times++
return new fsMiniPass.WriteStreamSync(...args)
}
},
},
},
})
for (const i of range(10)) {
logFile.log('verbose', `log ${i}`)
}
const logs = await readLogs()
t.equal(logs.length, 5, 'total log files')
})
t.test('initial stream error', async t => {
const { logFile, readLogs } = await loadLogFile(t, {
mocks: {
'fs-minipass': {
WriteStreamSync: class {
constructor () {
throw new Error('no stream')
}
},
},
},
})
for (const i of range(10)) {
logFile.log('verbose', `log ${i}`)
}
const logs = await readLogs()
t.equal(logs.length, 0, 'total log files')
})
t.test('turns off', async t => {
const { logFile, readLogs } = await loadLogFile(t)
logFile.log('error', 'test')
logFile.off()
logFile.log('error', 'test2')
logFile.load()
const logs = await readLogs()
t.match(last(last(logs).logs), /^\d+ error test$/)
})
t.test('cleans logs', async t => {
const logsMax = 5
const { readLogs } = await loadLogFile(t, {
logsMax,
testdir: makeOldLogs(10),
})
const logs = await readLogs()
t.equal(logs.length, logsMax + 1)
})
t.test('cleans logs even when find folder inside logs folder', async t => {
const logsMax = 5
const { readLogs } = await loadLogFile(t, {
logsMax,
testdir: {
...makeOldLogs(10),
ignore_folder: {
'ignored-file.txt': 'hello',
},
},
})
const logs = await readLogs()
t.equal(logs.length, logsMax + 1)
})
t.test('doesnt clean current log by default', async t => {
const logsMax = 1
const { readLogs, logFile } = await loadLogFile(t, {
logsMax,
testdir: makeOldLogs(10),
})
logFile.log('error', 'test')
const logs = await readLogs()
t.match(last(logs).content, /\d+ error test/)
})
t.test('negative logs max', async t => {
const logsMax = -10
const { readLogs, logFile } = await loadLogFile(t, {
logsMax,
testdir: makeOldLogs(10),
})
logFile.log('error', 'test')
const logs = await readLogs()
t.equal(logs.length, 0)
})
t.test('doesnt need to clean', async t => {
const logsMax = 20
const oldLogs = 10
const { readLogs } = await loadLogFile(t, {
logsMax,
testdir: makeOldLogs(oldLogs),
})
const logs = await readLogs()
t.equal(logs.length, oldLogs + 1)
})
t.test('cleans old style logs too', async t => {
const logsMax = 5
const oldLogs = 10
const { readLogs } = await loadLogFile(t, {
logsMax,
testdir: makeOldLogs(oldLogs, true),
})
const logs = await readLogs()
t.equal(logs.length, logsMax + 1)
})
t.test('rimraf error', async t => {
const logsMax = 5
const oldLogs = 10
let count = 0
const { readLogs } = await loadLogFile(t, {
logsMax,
testdir: makeOldLogs(oldLogs),
mocks: {
'node:fs/promises': {
readdir: fs.readdir,
rm: async (...args) => {
if (count >= 3) {
throw new Error('bad rimraf')
}
count++
return fs.rm(...args)
},
},
},
})
const logs = await readLogs()
t.equal(logs.length, oldLogs - 3 + 1)
t.match(last(logs).content, /error removing log file .* bad rimraf/)
})
t.test('delete log file while open', async t => {
const { logFile, root, readLogs } = await loadLogFile(t)
logFile.log('error', '', 'log 1')
const [log] = await readLogs(true)
t.match(log.content, /\d+ error log 1/)
await fs.unlink(path.resolve(root, log.filename))
logFile.log('error', '', 'log 2')
const logs = await readLogs()
// XXX: do some retry logic after error?
t.strictSame(logs, [], 'logs arent written after error')
})
t.test('snapshot', async t => {
const { logFile, readLogs } = await loadLogFile(t, { logsMax: 10 })
logFile.log('error', '', 'no prefix')
logFile.log('error', 'prefix', 'with prefix')
logFile.log('error', 'prefix', 1, 2, 3)
const nestedObj = { obj: { with: { many: { props: 1 } } } }
logFile.log('verbose', '', nestedObj)
logFile.log('verbose', '', JSON.stringify(nestedObj))
logFile.log('verbose', '', JSON.stringify(nestedObj, null, 2))
const arr = ['test', 'with', 'an', 'array']
logFile.log('verbose', '', arr)
logFile.log('verbose', '', JSON.stringify(arr))
logFile.log('verbose', '', JSON.stringify(arr, null, 2))
const nestedArr = ['test', ['with', ['an', ['array']]]]
logFile.log('verbose', '', nestedArr)
logFile.log('verbose', '', JSON.stringify(nestedArr))
logFile.log('verbose', '', JSON.stringify(nestedArr, null, 2))
// XXX: multiple errors are hard to parse visually
// the second error should start on a newline
logFile.log(...[
'error',
'pre',
'has',
'many',
'errors',
cleanErr('message'),
cleanErr('message2'),
])
const err = new Error('message')
delete err.stack
logFile.log(...[
'error',
'nostack',
err,
])
const logs = await readLogs()
t.matchSnapshot(logs.map(l => l.content).join('\n'))
})

312
deps/npm/test/lib/utils/open-url.js vendored Normal file
View File

@ -0,0 +1,312 @@
const t = require('tap')
const tmock = require('../../fixtures/tmock')
const mockNpm = require('../../fixtures/mock-npm')
const EventEmitter = require('node:events')
const mockOpenUrl = async (t, args, { openerResult, ...config } = {}) => {
let openerUrl = null
let openerOpts = null
const open = async (url, options) => {
openerUrl = url
openerOpts = options
if (openerResult) {
throw openerResult
}
}
const mock = await mockNpm(t, { config })
const { openUrl } = tmock(t, '{LIB}/utils/open-url.js', {
'@npmcli/promise-spawn': { open },
})
const openWithNpm = (...a) => openUrl(mock.npm, ...a)
if (args) {
await openWithNpm(...args)
}
mock.npm.finish()
return {
...mock,
openUrl: openWithNpm,
openerUrl: () => openerUrl,
openerOpts: () => openerOpts,
}
}
const mockOpenUrlPrompt = async (t, {
questionShouldResolve = true,
openUrlPromptInterrupted = false,
openerResult = null,
isTTY = true,
abort = false,
url: openUrl = 'https://www.npmjs.com',
...config
}) => {
const mock = await mockNpm(t, {
globals: {
'process.stdin.isTTY': isTTY,
'process.stdout.isTTY': isTTY,
},
config,
})
let openerUrl = null
let openerOpts = null
const { openUrlPrompt } = tmock(t, '{LIB}/utils/open-url.js', {
'@npmcli/promise-spawn': {
open: async (url, options) => {
openerUrl = url
openerOpts = options
if (openerResult) {
throw openerResult
}
},
},
'node:readline/promises': {
createInterface: () => {
return Object.assign(new EventEmitter(), {
question: async (p, { signal } = {}) => {
if (questionShouldResolve !== true) {
await new Promise((res, rej) => {
if (signal) {
signal.addEventListener('abort', () => {
const err = new Error('abort')
err.name = 'AbortError'
rej(err)
})
}
})
}
},
close: () => {},
once: function (event, cb) {
if (openUrlPromptInterrupted && event === 'SIGINT') {
cb()
}
},
})
},
},
})
let error
const abortController = new AbortController()
const args = [mock.npm, openUrl, 'npm home', 'prompt', { signal: abortController.signal }]
if (abort) {
mock.open = openUrlPrompt(...args)
} else {
await openUrlPrompt(...args).catch((er) => error = er)
}
mock.npm.finish()
return {
...mock,
openerUrl,
openerOpts,
OUTPUT: mock.joinedOutput(),
error,
abortController,
}
}
t.test('open url prompt', async t => {
t.test('does not open a url in non-interactive environments', async t => {
const { openerUrl, openerOpts } = await mockOpenUrlPrompt(t, { isTTY: false })
t.equal(openerUrl, null, 'did not open')
t.same(openerOpts, null, 'did not open')
})
t.test('opens a url', async t => {
const { OUTPUT, openerUrl, openerOpts } = await mockOpenUrlPrompt(t, { browser: true })
t.equal(openerUrl, 'https://www.npmjs.com', 'opened the given url')
t.same(openerOpts, { command: null }, 'passed command as null (the default)')
t.matchSnapshot(OUTPUT)
})
t.test('opens a url with browser string', async t => {
const { openerUrl, openerOpts } = await mockOpenUrlPrompt(t, { browser: 'firefox' })
t.equal(openerUrl, 'https://www.npmjs.com', 'opened the given url')
// FIXME: browser string is parsed as a boolean in config layer
// this is a bug that should be fixed or the config should not allow it
t.same(openerOpts, { command: null }, 'passed command as null (the default)')
})
t.test('prints json output', async t => {
const { OUTPUT } = await mockOpenUrlPrompt(t, { json: true })
t.matchSnapshot(OUTPUT)
})
t.test('returns error for non-https url', async t => {
const { error, OUTPUT, openerUrl, openerOpts } = await mockOpenUrlPrompt(t, {
url: 'ftp://www.npmjs.com',
})
t.match(error, /Invalid URL/, 'got the correct error')
t.equal(openerUrl, null, 'did not open')
t.same(openerOpts, null, 'did not open')
t.same(OUTPUT, '', 'printed no output')
})
t.test('does not open url if canceled', async t => {
const { openerUrl, openerOpts, open, abortController } = await mockOpenUrlPrompt(t, {
questionShouldResolve: false,
abort: true,
})
abortController.abort()
await open
t.equal(openerUrl, null, 'did not open')
t.same(openerOpts, null, 'did not open')
})
t.test('returns error when opener errors', async t => {
const { error, openerUrl } = await mockOpenUrlPrompt(t, {
openerResult: Object.assign(new Error('Opener failed'), { code: 1 }),
})
t.match(error, /Opener failed/, 'got the correct error')
t.equal(openerUrl, 'https://www.npmjs.com', 'did not open')
})
t.test('does not error when opener can not find command', async t => {
const { OUTPUT, error, openerUrl } = await mockOpenUrlPrompt(t, {
openerResult: Object.assign(new Error('Opener failed'), { code: 127 }),
})
t.notOk(error, 'Did not error')
t.equal(openerUrl, 'https://www.npmjs.com', 'did not open')
t.matchSnapshot(OUTPUT, 'Outputs extra Browser unavailable message and url')
})
t.test('throws "canceled" error on SIGINT', async t => {
const { open } = await mockOpenUrlPrompt(t, {
questionShouldResolve: false,
openUrlPromptInterrupted: true,
abort: true,
})
await t.rejects(open, /canceled/, 'message is canceled')
})
})
t.test('open url', async t => {
t.test('opens a url', async t => {
const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t,
['https://www.npmjs.com', 'npm home'])
t.equal(openerUrl(), 'https://www.npmjs.com', 'opened the given url')
t.same(openerOpts(), { command: null }, 'passed command as null (the default)')
t.same(joinedOutput(), '', 'printed no output')
})
t.test('returns error for non-https url', async t => {
const { openUrl, openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t)
await t.rejects(
openUrl('ftp://www.npmjs.com', 'npm home'),
/Invalid URL/,
'got the correct error'
)
t.equal(openerUrl(), null, 'did not open')
t.same(openerOpts(), null, 'did not open')
t.same(joinedOutput(), '', 'printed no output')
})
t.test('returns error for file url', async t => {
const { openUrl, openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t)
await t.rejects(
openUrl('file:///usr/local/bin/ls', 'npm home'),
/Invalid URL/,
'got the correct error'
)
t.equal(openerUrl(), null, 'did not open')
t.same(openerOpts(), null, 'did not open')
t.same(joinedOutput(), '', 'printed no output')
})
t.test('file url allowed if explicitly asked for', async t => {
const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t,
['file:///man/page/npm-install', 'npm home', true])
t.equal(openerUrl(), 'file:///man/page/npm-install', 'opened the given url')
t.same(openerOpts(), { command: null }, 'passed command as null (the default)')
t.same(joinedOutput(), '', 'printed no output')
})
t.test('returns error for non-parseable url', async t => {
const { openUrl, openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t)
await t.rejects(
openUrl('git+ssh://user@host:repo.git', 'npm home'),
/Invalid URL/,
'got the correct error'
)
t.equal(openerUrl(), null, 'did not open')
t.same(openerOpts(), null, 'did not open')
t.same(joinedOutput(), '', 'printed no output')
})
t.test('encodes non-URL-safe characters in url provided', async t => {
const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t,
['https://www.npmjs.com/|cat', 'npm home'])
t.equal(openerUrl(), 'https://www.npmjs.com/%7Ccat', 'opened the encoded url')
t.same(openerOpts(), { command: null }, 'passed command as null (the default)')
t.same(joinedOutput(), '', 'printed no output')
})
t.test('opens a url with the given browser', async t => {
const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t,
['https://www.npmjs.com', 'npm home'], { browser: 'chrome' })
t.equal(openerUrl(), 'https://www.npmjs.com', 'opened the given url')
// FIXME: browser string is parsed as a boolean in config layer
// this is a bug that should be fixed or the config should not allow it
t.same(openerOpts(), { command: null }, 'passed the given browser as command')
t.same(joinedOutput(), '', 'printed no output')
})
t.test('prints where to go when browser is disabled', async t => {
const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t,
['https://www.npmjs.com', 'npm home'], { browser: false })
t.equal(openerUrl(), null, 'did not open')
t.same(openerOpts(), null, 'did not open')
t.matchSnapshot(joinedOutput(), 'printed expected message')
})
t.test('prints where to go when browser is disabled and json is enabled', async t => {
const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t,
['https://www.npmjs.com', 'npm home'], { browser: false, json: true })
t.equal(openerUrl(), null, 'did not open')
t.same(openerOpts(), null, 'did not open')
t.matchSnapshot(joinedOutput(), 'printed expected message')
})
t.test('prints where to go when given browser does not exist', async t => {
const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t,
['https://www.npmjs.com', 'npm home'],
{
openerResult: Object.assign(new Error('failed'), { code: 127 }),
}
)
t.equal(openerUrl(), 'https://www.npmjs.com', 'tried to open the correct url')
t.same(openerOpts(), { command: null }, 'tried to use the correct browser')
t.matchSnapshot(joinedOutput(), 'printed expected message')
})
t.test('handles unknown opener error', async t => {
const { openUrl } = await mockOpenUrl(t, null, {
browser: 'firefox',
openerResult: Object.assign(new Error('failed'), { code: 'ENOBRIAN' }),
})
await t.rejects(openUrl('https://www.npmjs.com', 'npm home'), 'failed', 'got the correct error')
})
})

965
deps/npm/test/lib/utils/queryable.js vendored Normal file
View File

@ -0,0 +1,965 @@
const { inspect } = require('node:util')
const t = require('tap')
const Queryable = require('../../../lib/utils/queryable.js')
t.test('retrieve single nested property', async t => {
const fixture = {
foo: {
bar: 'bar',
baz: 'baz',
},
lorem: {
ipsum: 'ipsum',
},
}
const q = new Queryable(fixture)
const query = 'foo.bar'
t.strictSame(q.query(query), { [query]: 'bar' },
'should retrieve property value when querying for dot-sep name')
})
t.test('query', async t => {
const fixture = {
o: 'o',
single: [
'item',
],
w: [
'a',
'b',
'c',
],
list: [
{
name: 'first',
},
{
name: 'second',
},
],
foo: {
bar: 'bar',
baz: 'baz',
},
lorem: {
ipsum: 'ipsum',
dolor: [
'a',
'b',
'c',
{
sit: [
'amet',
],
},
],
},
a: [
[
[
{
b: [
[
{
c: 'd',
},
],
],
},
],
],
],
}
const q = new Queryable(fixture)
t.strictSame(
q.query(['foo.baz', 'lorem.dolor[0]']),
{
'foo.baz': 'baz',
'lorem.dolor[0]': 'a',
},
'should retrieve property values when querying for multiple dot-sep names')
t.strictSame(
q.query('lorem.dolor[3].sit[0]'),
{
'lorem.dolor[3].sit[0]': 'amet',
},
'should retrieve property from nested array items')
t.strictSame(
q.query('a[0][0][0].b[0][0].c'),
{
'a[0][0][0].b[0][0].c': 'd',
},
'should retrieve property from deep nested array items')
t.strictSame(
q.query('o'),
{
o: 'o',
},
'should retrieve single level property value')
t.strictSame(
q.query('list.name'),
{
'list[0].name': 'first',
'list[1].name': 'second',
},
'should automatically expand arrays')
t.strictSame(
q.query(['list.name']),
{
'list[0].name': 'first',
'list[1].name': 'second',
},
'should automatically expand multiple arrays')
t.strictSame(
q.query('w'),
{
w: ['a', 'b', 'c'],
},
'should return arrays')
t.strictSame(
q.query('single'),
{
single: 'item',
},
'should return single item')
t.strictSame(
q.query('missing'),
undefined,
'should return undefined')
t.strictSame(
q.query('missing[bar]'),
undefined,
'should return undefined also')
t.throws(() => q.query('lorem.dolor[]'),
{ code: 'EINVALIDSYNTAX' },
'should throw if using empty brackets notation'
)
t.throws(() => q.query('lorem.dolor[].sit[0]'),
{ code: 'EINVALIDSYNTAX' },
'should throw if using nested empty brackets notation'
)
const qq = new Queryable({
foo: {
bar: 'bar',
},
})
t.strictSame(
qq.query(''),
{
'': {
foo: {
bar: 'bar',
},
},
},
'should return an object with results in an empty key'
)
})
t.test('missing key', async t => {
const fixture = {
foo: {
bar: 'bar',
},
}
const q = new Queryable(fixture)
const query = 'foo.missing'
t.equal(q.query(query), undefined,
'should retrieve no results')
})
t.test('no data object', async t => {
t.throws(
() => new Queryable(),
{ code: 'ENOQUERYABLEOBJ' },
'should throw ENOQUERYABLEOBJ error'
)
t.throws(
() => new Queryable(1),
{ code: 'ENOQUERYABLEOBJ' },
'should throw ENOQUERYABLEOBJ error'
)
})
t.test('get values', async t => {
const q = new Queryable({
foo: {
bar: 'bar',
},
})
t.equal(q.get('foo.bar'), 'bar', 'should retrieve value')
t.equal(q.get('missing'), undefined, 'should return undefined')
})
t.test('set property values', async t => {
const fixture = {
foo: {
bar: 'bar',
},
}
const q = new Queryable(fixture)
q.set('foo.baz', 'baz')
t.strictSame(
q.toJSON(),
{
foo: {
bar: 'bar',
baz: 'baz',
},
},
'should add new property and its assigned value'
)
q.set('foo[lorem.ipsum]', 'LOREM IPSUM')
t.strictSame(
q.toJSON(),
{
foo: {
bar: 'bar',
baz: 'baz',
'lorem.ipsum': 'LOREM IPSUM',
},
},
'should be able to set square brackets props'
)
q.set('a.b[c.d]', 'omg')
t.strictSame(
q.toJSON(),
{
foo: {
bar: 'bar',
baz: 'baz',
'lorem.ipsum': 'LOREM IPSUM',
},
a: {
b: {
'c.d': 'omg',
},
},
},
'should be able to nest square brackets props'
)
q.set('a.b[e][f.g][1.0.0]', 'multiple')
t.strictSame(
q.toJSON(),
{
foo: {
bar: 'bar',
baz: 'baz',
'lorem.ipsum': 'LOREM IPSUM',
},
a: {
b: {
'c.d': 'omg',
e: {
'f.g': {
'1.0.0': 'multiple',
},
},
},
},
},
'should be able to nest multiple square brackets props'
)
q.set('a.b[e][f.g][2.0.0].author.name', 'Ruy Adorno')
t.strictSame(
q.toJSON(),
{
foo: {
bar: 'bar',
baz: 'baz',
'lorem.ipsum': 'LOREM IPSUM',
},
a: {
b: {
'c.d': 'omg',
e: {
'f.g': {
'1.0.0': 'multiple',
'2.0.0': {
author: {
name: 'Ruy Adorno',
},
},
},
},
},
},
},
'should be able to use dot-sep notation after square bracket props'
)
q.set('a.b[e][f.g][2.0.0].author[url]', 'https://npmjs.com')
t.strictSame(
q.toJSON(),
{
foo: {
bar: 'bar',
baz: 'baz',
'lorem.ipsum': 'LOREM IPSUM',
},
a: {
b: {
'c.d': 'omg',
e: {
'f.g': {
'1.0.0': 'multiple',
'2.0.0': {
author: {
name: 'Ruy Adorno',
url: 'https://npmjs.com',
},
},
},
},
},
},
},
'should be able to have multiple, separated, square brackets props'
)
q.set('a.b[e][f.g][2.0.0].author[foo][bar].lorem.ipsum[dolor][sit][amet].omg', 'O_O')
t.strictSame(
q.toJSON(),
{
foo: {
bar: 'bar',
baz: 'baz',
'lorem.ipsum': 'LOREM IPSUM',
},
a: {
b: {
'c.d': 'omg',
e: {
'f.g': {
'1.0.0': 'multiple',
'2.0.0': {
author: {
name: 'Ruy Adorno',
url: 'https://npmjs.com',
foo: {
bar: {
lorem: {
ipsum: {
dolor: {
sit: {
amet: {
omg: 'O_O',
},
},
},
},
},
},
},
},
},
},
},
},
},
},
'many many times...'
)
t.throws(
() => q.set('foo.bar.nest', 'should throw'),
{ code: 'EOVERRIDEVALUE' },
'should throw if trying to override a literal value with an object'
)
q.set('foo.bar.nest', 'use the force!', { force: true })
t.strictSame(
q.toJSON().foo,
{
bar: {
nest: 'use the force!',
},
baz: 'baz',
'lorem.ipsum': 'LOREM IPSUM',
},
'should allow overriding literal values when using force option'
)
const qq = new Queryable({})
qq.set('foo.bar.baz', 'BAZ')
t.strictSame(
qq.toJSON(),
{
foo: {
bar: {
baz: 'BAZ',
},
},
},
'should add new props to qq object'
)
qq.set('foo.bar.bario', 'bario')
t.strictSame(
qq.toJSON(),
{
foo: {
bar: {
baz: 'BAZ',
bario: 'bario',
},
},
},
'should add new props to a previously existing object'
)
qq.set('lorem', 'lorem')
t.strictSame(
qq.toJSON(),
{
foo: {
bar: {
baz: 'BAZ',
bario: 'bario',
},
},
lorem: 'lorem',
},
'should append new props added to object later'
)
qq.set('foo.bar[foo.bar]', 'foo.bar.with.dots')
t.strictSame(
qq.toJSON(),
{
foo: {
bar: {
'foo.bar': 'foo.bar.with.dots',
baz: 'BAZ',
bario: 'bario',
},
},
lorem: 'lorem',
},
'should append new props added to object later'
)
})
t.test('set arrays', async t => {
const q = new Queryable({})
q.set('foo[1]', 'b')
t.strictSame(
q.toJSON(),
{
foo: [
undefined,
'b',
],
},
'should be able to set items in an array using index references'
)
q.set('foo[0]', 'a')
t.strictSame(
q.toJSON(),
{
foo: [
'a',
'b',
],
},
'should be able to set a previously missing item to an array'
)
q.set('foo[2]', 'c')
t.strictSame(
q.toJSON(),
{
foo: [
'a',
'b',
'c',
],
},
'should be able to append more items to an array'
)
q.set('foo[2]', 'C')
t.strictSame(
q.toJSON(),
{
foo: [
'a',
'b',
'C',
],
},
'should be able to override array items'
)
t.throws(
() => q.set('foo[2].bar', 'bar'),
{ code: 'EOVERRIDEVALUE' },
'should throw if trying to override an array literal item with an obj'
)
q.set('foo[2].bar', 'bar', { force: true })
t.strictSame(
q.toJSON(),
{
foo: [
'a',
'b',
{ bar: 'bar' },
],
},
'should be able to override an array string item with an obj'
)
q.set('foo[3].foo', 'surprise surprise, another foo')
t.strictSame(
q.toJSON(),
{
foo: [
'a',
'b',
{ bar: 'bar' },
{
foo: 'surprise surprise, another foo',
},
],
},
'should be able to append more items to an array'
)
q.set('foo[3].foo', 'FOO')
t.strictSame(
q.toJSON(),
{
foo: [
'a',
'b',
{ bar: 'bar' },
{
foo: 'FOO',
},
],
},
'should be able to override property of an obj inside an array'
)
const qq = new Queryable({})
qq.set('foo[0].bar[1].baz.bario[0][0][0]', 'something')
t.strictSame(
qq.toJSON(),
{
foo: [
{
bar: [
undefined,
{
baz: {
bario: [[['something']]],
},
},
],
},
],
},
'should append as many arrays as necessary'
)
qq.set('foo[0].bar[1].baz.bario[0][1][0]', 'something else')
t.strictSame(
qq.toJSON(),
{
foo: [
{
bar: [
undefined,
{
baz: {
bario: [[
['something'],
['something else'],
]],
},
},
],
},
],
},
'should append as many arrays as necessary'
)
qq.set('foo', null)
t.strictSame(
qq.toJSON(),
{
foo: null,
},
'should be able to set a value to null'
)
qq.set('foo.bar', 'bar')
t.strictSame(
qq.toJSON(),
{
foo: {
bar: 'bar',
},
},
'should be able to replace a null value with properties'
)
const qqq = new Queryable({
arr: [
'a',
'b',
],
})
qqq.set('arr[]', 'c')
t.strictSame(
qqq.toJSON(),
{
arr: [
'a',
'b',
'c',
],
},
'should be able to append to array using empty bracket notation'
)
qqq.set('arr[].foo', 'foo')
t.strictSame(
qqq.toJSON(),
{
arr: [
'a',
'b',
'c',
{
foo: 'foo',
},
],
},
'should be able to append objects to array using empty bracket notation'
)
qqq.set('arr[].bar.name', 'BAR')
t.strictSame(
qqq.toJSON(),
{
arr: [
'a',
'b',
'c',
{
foo: 'foo',
},
{
bar: {
name: 'BAR',
},
},
],
},
'should be able to append more objects to array using empty brackets'
)
qqq.set('foo.bar.baz[].lorem.ipsum', 'something')
t.strictSame(
qqq.toJSON(),
{
arr: [
'a',
'b',
'c',
{
foo: 'foo',
},
{
bar: {
name: 'BAR',
},
},
],
foo: {
bar: {
baz: [
{
lorem: {
ipsum: 'something',
},
},
],
},
},
},
'should be able to append to array using empty brackets in nested objs'
)
qqq.set('foo.bar.baz[].lorem.array[]', 'new item')
t.strictSame(
qqq.toJSON(),
{
arr: [
'a',
'b',
'c',
{
foo: 'foo',
},
{
bar: {
name: 'BAR',
},
},
],
foo: {
bar: {
baz: [
{
lorem: {
ipsum: 'something',
},
},
{
lorem: {
array: [
'new item',
],
},
},
],
},
},
},
'should be able to append to array using empty brackets in nested objs'
)
const qqqq = new Queryable({
arr: [
'a',
'b',
],
})
t.throws(
() => qqqq.set('arr.foo', 'foo'),
{ code: 'ENOADDPROP' },
'should throw an override error'
)
qqqq.set('arr.foo', 'foo', { force: true })
t.strictSame(
qqqq.toJSON(),
{
arr: {
0: 'a',
1: 'b',
foo: 'foo',
},
},
'should be able to override arrays with objects when using force=true'
)
qqqq.set('bar[]', 'item', { force: true })
t.strictSame(
qqqq.toJSON(),
{
arr: {
0: 'a',
1: 'b',
foo: 'foo',
},
bar: [
'item',
],
},
'should be able to create new array with item when using force=true'
)
qqqq.set('bar[]', 'something else', { force: true })
t.strictSame(
qqqq.toJSON(),
{
arr: {
0: 'a',
1: 'b',
foo: 'foo',
},
bar: [
'item',
'something else',
],
},
'should be able to append items to arrays when using force=true'
)
const qqqqq = new Queryable({
arr: [
null,
],
})
qqqqq.set('arr[]', 'b')
t.strictSame(
qqqqq.toJSON(),
{
arr: [
null,
'b',
],
},
'should be able to append items with empty items'
)
qqqqq.set('arr[0]', 'a')
t.strictSame(
qqqqq.toJSON(),
{
arr: [
'a',
'b',
],
},
'should be able to replace empty items in an array'
)
qqqqq.set('lorem.ipsum', 3)
t.strictSame(
qqqqq.toJSON(),
{
arr: [
'a',
'b',
],
lorem: {
ipsum: 3,
},
},
'should be able to replace empty items in an array'
)
t.throws(
() => qqqqq.set('lorem[]', 4),
{ code: 'ENOAPPEND' },
'should throw error if using empty square bracket in an non-array item'
)
qqqqq.set('lorem[0]', 3)
t.strictSame(
qqqqq.toJSON(),
{
arr: [
'a',
'b',
],
lorem: {
0: 3,
ipsum: 3,
},
},
'should be able add indexes as props when finding an object'
)
qqqqq.set('lorem.1', 3)
t.strictSame(
qqqqq.toJSON(),
{
arr: [
'a',
'b',
],
lorem: {
0: 3,
1: 3,
ipsum: 3,
},
},
'should be able add numeric props to an obj'
)
})
t.test('delete values', async t => {
const q = new Queryable({
foo: {
bar: {
lorem: 'lorem',
},
},
})
q.delete('foo.bar.lorem')
t.strictSame(
q.toJSON(),
{
foo: {
bar: {},
},
},
'should delete queried item'
)
q.delete('foo')
t.strictSame(
q.toJSON(),
{},
'should delete nested items'
)
q.set('foo.a.b.c[0]', 'value')
q.delete('foo.a.b.c[0]')
t.strictSame(
q.toJSON(),
{
foo: {
a: {
b: {
c: [],
},
},
},
},
'should delete array item'
)
// creates an array that has an implicit empty first item
q.set('foo.a.b.c[1][0].foo.bar[0][0]', 'value')
q.delete('foo.a.b.c[1]')
t.strictSame(
q.toJSON(),
{
foo: {
a: {
b: {
c: [null],
},
},
},
},
'should delete array item'
)
})
t.test('logger', async t => {
const q = new Queryable({})
q.set('foo.bar[0].baz', 'baz')
t.strictSame(
inspect(q, { depth: 10 }),
inspect({
foo: {
bar: [
{
baz: 'baz',
},
],
},
}, { depth: 10 }),
'should retrieve expected data'
)
})
t.test('bracket lovers', async t => {
const q = new Queryable({})
q.set('[iLoveBrackets]', 'seriously?')
t.strictSame(
q.toJSON(),
{
'[iLoveBrackets]': 'seriously?',
},
'should be able to set top-level props using square brackets notation'
)
t.equal(q.get('[iLoveBrackets]'), 'seriously?',
'should bypass square bracket in top-level properties')
q.set('[0]', '-.-')
t.strictSame(
q.toJSON(),
{
'[iLoveBrackets]': 'seriously?',
'[0]': '-.-',
},
'any top-level item can not be parsed with square bracket notation'
)
})

View File

@ -0,0 +1,120 @@
const t = require('tap')
const procLog = require('proc-log')
const tmock = require('../../fixtures/tmock')
let readOpts = null
let readResult = null
let logMsg = null
const readUserInfo = tmock(t, '{LIB}/utils/read-user-info.js', {
read: {
read: async (opts) => {
readOpts = opts
return readResult
},
},
'proc-log': {
...procLog,
log: {
...procLog.log,
warn: (msg) => logMsg = msg,
},
input: {
...procLog.input,
read: (fn) => fn(),
},
},
'npm-user-validate': {
username: (username) => {
if (username === 'invalid') {
return new Error('invalid username')
}
return null
},
email: (email) => {
if (email.startsWith('invalid')) {
return new Error('invalid email')
}
return null
},
},
})
t.beforeEach(() => {
logMsg = null
})
t.test('otp', async (t) => {
readResult = '1234'
t.teardown(() => {
readResult = null
readOpts = null
})
const result = await readUserInfo.otp()
t.equal(result, '1234', 'received the otp')
})
t.test('password', async (t) => {
readResult = 'password'
t.teardown(() => {
readResult = null
readOpts = null
})
const result = await readUserInfo.password()
t.equal(result, 'password', 'received the password')
t.match(readOpts, {
silent: true,
}, 'got the correct options')
})
t.test('username', async (t) => {
readResult = 'username'
t.teardown(() => {
readResult = null
readOpts = null
})
const result = await readUserInfo.username()
t.equal(result, 'username', 'received the username')
})
t.test('username - invalid warns and retries', async (t) => {
readResult = 'invalid'
t.teardown(() => {
readResult = null
readOpts = null
})
const pResult = readUserInfo.username(null, null)
// have to swap it to a valid username after execution starts
// or it will loop forever
readResult = 'valid'
const result = await pResult
t.equal(result, 'valid', 'received the username')
t.equal(logMsg, 'invalid username')
})
t.test('email', async (t) => {
readResult = 'foo@bar.baz'
t.teardown(() => {
readResult = null
readOpts = null
})
const result = await readUserInfo.email()
t.equal(result, 'foo@bar.baz', 'received the email')
})
t.test('email - invalid warns and retries', async (t) => {
readResult = 'invalid@bar.baz'
t.teardown(() => {
readResult = null
readOpts = null
})
const pResult = readUserInfo.email(null, null)
readResult = 'foo@bar.baz'
const result = await pResult
t.equal(result, 'foo@bar.baz', 'received the email')
t.equal(logMsg, 'invalid email')
})

95
deps/npm/test/lib/utils/reify-finish.js vendored Normal file
View File

@ -0,0 +1,95 @@
const t = require('tap')
const fs = require('node:fs/promises')
const { join } = require('node:path')
const { cleanNewlines } = require('../../fixtures/clean-snapshot')
const tmock = require('../../fixtures/tmock')
const mockNpm = require('../../fixtures/mock-npm')
// windowwwwwwssss!!!!!
const readRc = async (dir) => {
const res = await fs.readFile(join(dir, 'npmrc'), 'utf8').catch(() => '')
return cleanNewlines(res).trim()
}
const mockReififyFinish = async (t, { actualTree = {}, otherDirs = {}, ...config }) => {
const mock = await mockNpm(t, {
npm: ({ other }) => ({
npmRoot: other,
}),
otherDirs: {
npmrc: `key=value`,
...otherDirs,
},
config,
})
const reifyFinish = tmock(t, '{LIB}/utils/reify-finish.js', {
'{LIB}/utils/reify-output.js': () => {},
})
await reifyFinish(mock.npm, {
options: { global: mock.npm.global },
actualTree: typeof actualTree === 'function' ? actualTree(mock) : actualTree,
})
const builtinRc = {
raw: await readRc(mock.other),
data: Object.fromEntries(Object.entries(mock.npm.config.data.get('builtin').data)),
}
return {
builtinRc,
...mock,
}
}
t.test('ok by default', async t => {
const mock = await mockReififyFinish(t, {
global: false,
})
t.same(mock.builtinRc.raw, 'key=value')
t.strictSame(mock.builtinRc.data, { key: 'value' })
})
t.test('should not write if no global npm module', async t => {
const mock = await mockReififyFinish(t, {
global: true,
actualTree: {
inventory: new Map(),
},
})
t.same(mock.builtinRc.raw, 'key=value')
t.strictSame(mock.builtinRc.data, { key: 'value' })
})
t.test('should not write if builtin conf had load error', async t => {
const mock = await mockReififyFinish(t, {
global: true,
otherDirs: {
npmrc: {},
},
actualTree: {
inventory: new Map([['node_modules/npm', {}]]),
},
})
t.same(mock.builtinRc.raw, '')
t.strictSame(mock.builtinRc.data, {})
})
t.test('should write if everything above passes', async t => {
const mock = await mockReififyFinish(t, {
global: true,
otherDirs: {
'new-npm': {},
},
actualTree: ({ other }) => ({
inventory: new Map([['node_modules/npm', { path: join(other, 'new-npm') }]]),
}),
})
t.same(mock.builtinRc.raw, 'key=value')
t.strictSame(mock.builtinRc.data, { key: 'value' })
const newFile = await readRc(join(mock.other, 'new-npm'))
t.equal(mock.builtinRc.raw, newFile)
})

450
deps/npm/test/lib/utils/reify-output.js vendored Normal file
View File

@ -0,0 +1,450 @@
const t = require('tap')
const mockNpm = require('../../fixtures/mock-npm')
const reifyOutput = require('../../../lib/utils/reify-output.js')
t.cleanSnapshot = str => str.replace(/in [0-9]+m?s/g, 'in {TIME}')
const mockReify = async (t, reify, { command, ...config } = {}) => {
const mock = await mockNpm(t, {
command,
config,
})
// Hack to adapt existing fake test. Make npm.command
// return whatever was passed in to this function.
// What it should be doing is npm.exec(command) but that
// breaks most of these tests because they dont expect
// a command to actually run.
Object.defineProperty(mock.npm, 'command', {
get () {
return command
},
enumerable: true,
})
reifyOutput(mock.npm, reify)
mock.npm.finish()
return mock.joinedOutput()
}
t.test('missing info', async t => {
const out = await mockReify(t, {
actualTree: {
children: [],
},
diff: {
children: [],
},
})
t.notMatch(
out,
'looking for funding',
'should not print fund message if missing info'
)
})
t.test('even more missing info', async t => {
const out = await mockReify(t, {
actualTree: {
children: [],
},
})
t.notMatch(
out,
'looking for funding',
'should not print fund message if missing info'
)
})
t.test('single package', async t => {
const out = await mockReify(t, {
// a report with an error is the same as no report at all, if
// the command is not 'audit'
auditReport: {
error: {
message: 'no audit for youuuuu',
},
},
actualTree: {
name: 'foo',
package: {
name: 'foo',
version: '1.0.0',
},
edgesOut: new Map([
['bar', {
to: {
name: 'bar',
package: {
name: 'bar',
version: '1.0.0',
funding: { type: 'foo', url: 'http://example.com' },
},
},
}],
]),
},
diff: {
children: [],
},
})
t.match(
out,
'1 package is looking for funding',
'should print single package message'
)
})
t.test('no message when funding config is false', async t => {
const out = await mockReify(t, {
actualTree: {
name: 'foo',
package: {
name: 'foo',
version: '1.0.0',
},
edgesOut: new Map([
['bar', {
to: {
name: 'bar',
package: {
name: 'bar',
version: '1.0.0',
funding: { type: 'foo', url: 'http://example.com' },
},
},
}],
]),
},
diff: {
children: [],
},
}, { fund: false })
t.notMatch(out, 'looking for funding', 'should not print funding info')
})
t.test('print appropriate message for many packages', async t => {
const out = await mockReify(t, {
actualTree: {
name: 'foo',
package: {
name: 'foo',
version: '1.0.0',
},
edgesOut: new Map([
['bar', {
to: {
name: 'bar',
package: {
name: 'bar',
version: '1.0.0',
funding: { type: 'foo', url: 'http://example.com' },
},
},
}],
['lorem', {
to: {
name: 'lorem',
package: {
name: 'lorem',
version: '1.0.0',
funding: { type: 'foo', url: 'http://example.com' },
},
},
}],
['ipsum', {
to: {
name: 'ipsum',
package: {
name: 'ipsum',
version: '1.0.0',
funding: { type: 'foo', url: 'http://example.com' },
},
},
}],
]),
},
diff: {
children: [],
},
})
t.match(
out,
'3 packages are looking for funding',
'should print single package message'
)
})
t.test('showing and not showing audit report', async t => {
const auditReport = {
toJSON: () => auditReport,
auditReportVersion: 2,
vulnerabilities: {
minimist: {
name: 'minimist',
severity: 'low',
via: [
{
id: 1179,
url: 'https://npmjs.com/advisories/1179',
title: 'Prototype Pollution',
severity: 'low',
vulnerable_versions: '<0.2.1 || >=1.0.0 <1.2.3',
},
],
effects: [],
range: '<0.2.1 || >=1.0.0 <1.2.3',
nodes: [
'node_modules/minimist',
],
fixAvailable: true,
},
},
metadata: {
vulnerabilities: {
info: 0,
low: 1,
moderate: 0,
high: 0,
critical: 0,
total: 1,
},
dependencies: {
prod: 1,
dev: 0,
optional: 0,
peer: 0,
peerOptional: 0,
total: 1,
},
},
}
t.test('no output when silent', async t => {
const out = await mockReify(t, {
actualTree: { inventory: { size: 999 }, children: [] },
auditReport,
diff: {
children: [
{ action: 'ADD', ideal: { location: 'loc' } },
],
},
}, { silent: true })
t.equal(out, '', 'should not get output when silent')
})
t.test('output when not silent', async t => {
const out = await mockReify(t, {
actualTree: { inventory: new Map(), children: [] },
auditReport,
diff: {
children: [
{ action: 'ADD', ideal: { location: 'loc' } },
],
},
})
t.match(out, /Run `npm audit` for details\.$/, 'got audit report')
})
for (const json of [true, false]) {
t.test(`json=${json}`, async t => {
t.test('set exit code when cmd is audit', async t => {
await mockReify(t, {
actualTree: { inventory: new Map(), children: [] },
auditReport,
diff: {
children: [
{ action: 'ADD', ideal: { location: 'loc' } },
],
},
}, { command: 'audit', 'audit-level': 'low' })
t.equal(process.exitCode, 1, 'set exit code')
})
t.test('do not set exit code when cmd is install', async t => {
await mockReify(t, {
actualTree: { inventory: new Map(), children: [] },
auditReport,
diff: {
children: [
{ action: 'ADD', ideal: { location: 'loc' } },
],
},
}, { command: 'install', 'audit-level': 'low' })
t.notOk(process.exitCode, 'did not set exit code')
})
})
}
})
t.test('packages changed message', async t => {
// return a test function that builds up the mock and snapshots output
const testCase = async (t, added, removed, changed, audited, json, command) => {
const mock = {
actualTree: {
inventory: { size: audited, has: () => true },
children: [],
},
auditReport: audited ? {
toJSON: () => mock.auditReport,
vulnerabilities: {},
metadata: {
vulnerabilities: {
total: 0,
},
},
} : null,
diff: {
children: [
{ action: 'some random unexpected junk' },
],
},
}
for (let i = 0; i < added; i++) {
mock.diff.children.push({ action: 'ADD', ideal: { location: 'loc' } })
}
for (let i = 0; i < removed; i++) {
mock.diff.children.push({ action: 'REMOVE', actual: { location: 'loc' } })
}
for (let i = 0; i < changed; i++) {
const actual = { location: 'loc' }
const ideal = { location: 'loc' }
mock.diff.children.push({ action: 'CHANGE', actual, ideal })
}
const out = await mockReify(t, mock, { json, command })
t.matchSnapshot(out, JSON.stringify({
added,
removed,
changed,
audited,
json,
}))
}
const cases = []
for (const added of [0, 1, 2]) {
for (const removed of [0, 1, 2]) {
for (const changed of [0, 1, 2]) {
for (const audited of [0, 1, 2]) {
for (const json of [true, false]) {
cases.push([added, removed, changed, audited, json, 'install'])
}
}
}
}
}
// add case for when audit is the command
cases.push([0, 0, 0, 2, true, 'audit'])
cases.push([0, 0, 0, 2, false, 'audit'])
for (const c of cases) {
await t.test('', t => testCase(t, ...c))
}
})
t.test('added packages should be looked up within returned tree', async t => {
t.test('has added pkg in inventory', async t => {
const out = await mockReify(t, {
actualTree: {
name: 'foo',
inventory: {
has: () => true,
},
},
diff: {
children: [
{ action: 'ADD', ideal: { name: 'baz' } },
],
},
})
t.matchSnapshot(out)
})
t.test('missing added pkg in inventory', async t => {
const out = await mockReify(t, {
actualTree: {
name: 'foo',
inventory: {
has: () => false,
},
},
diff: {
children: [
{ action: 'ADD', ideal: { name: 'baz' } },
],
},
})
t.matchSnapshot(out)
})
})
t.test('prints dedupe difference on dry-run', async t => {
const mock = {
actualTree: {
name: 'foo',
inventory: {
has: () => false,
},
},
diff: {
children: [
{ action: 'ADD', ideal: { name: 'foo', package: { version: '1.0.0' } } },
{ action: 'REMOVE', actual: { name: 'bar', package: { version: '1.0.0' } } },
{
action: 'CHANGE',
actual: { name: 'bar', package: { version: '1.0.0' } },
ideal: { package: { version: '2.1.0' } },
},
],
},
}
const out = await mockReify(t, mock, {
'dry-run': true,
})
t.matchSnapshot(out, 'diff table')
})
t.test('prints dedupe difference on long', async t => {
const mock = {
actualTree: {
name: 'foo',
inventory: {
has: () => false,
},
},
diff: {
children: [
{ action: 'ADD', ideal: { name: 'foo', package: { version: '1.0.0' } } },
{ action: 'REMOVE', actual: { name: 'bar', package: { version: '1.0.0' } } },
{
action: 'CHANGE',
actual: { name: 'bar', package: { version: '1.0.0' } },
ideal: { package: { version: '2.1.0' } },
},
],
},
}
const out = await mockReify(t, mock, {
long: true,
})
t.matchSnapshot(out, 'diff table')
})

View File

@ -0,0 +1,272 @@
const t = require('tap')
const Ajv = require('ajv')
const applyFormats = require('ajv-formats')
const applyDraftFormats = require('ajv-formats-draft2019')
const { cyclonedxOutput } = require('../../../lib/utils/sbom-cyclonedx.js')
const FAKE_UUID = 'urn:uuid:00000000-0000-0000-0000-000000000000'
t.cleanSnapshot = s => {
let sbom
try {
sbom = JSON.parse(s)
} catch (e) {
return s
}
sbom.serialNumber = FAKE_UUID
if (sbom.metadata) {
sbom.metadata.timestamp = '2020-01-01T00:00:00.000Z'
}
return JSON.stringify(sbom, null, 2)
}
const npm = { version: '10.0.0 ' }
const rootPkg = {
author: 'Author',
}
const root = {
name: 'root',
packageName: 'root',
version: '1.0.0',
pkgid: 'root@1.0.0',
isRoot: true,
package: rootPkg,
location: '',
edgesOut: [],
}
const dep1 = {
name: 'dep1',
packageName: 'dep1',
version: '0.0.1',
pkgid: 'dep1@0.0.1',
package: {},
location: 'node_modules/dep1',
edgesOut: [],
}
const dep2 = {
name: 'dep2',
packageName: 'dep2',
version: '0.0.2',
pkgid: 'npm@npm:dep2@0.0.2',
package: {},
location: 'node_modules/dep2',
edgesOut: [{ to: dep1 }],
}
const dep2Link = {
name: 'dep2',
packageName: 'dep2',
version: '0.0.2',
pkgid: 'dep2@0.0.2',
package: {},
location: 'node_modules/dep2',
edgesOut: [],
isLink: true,
target: dep2,
}
t.test('single node - application package type', t => {
const res = cyclonedxOutput({ npm, nodes: [root], packageType: 'application' })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - package lock only', t => {
const res = cyclonedxOutput({ npm, nodes: [root], packageLockOnly: true })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - optional ', t => {
const node = { ...root, optional: true }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with description', t => {
const pkg = { ...rootPkg, description: 'Package description' }
const node = { ...root, package: pkg }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with author object', t => {
const pkg = { ...rootPkg, author: { name: 'Arthur' } }
const node = { ...root, package: pkg }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with integrity', t => {
const node = { ...root, integrity: 'sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==' }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - development', t => {
const node = { ...root, dev: true }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - extraneous', t => {
const node = { ...root, extraneous: true }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - bundled', t => {
const node = { ...root, inBundle: true }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - private', t => {
const pkg = { ...rootPkg, private: true }
const node = { ...root, package: pkg }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with repository url', t => {
const pkg = { ...rootPkg, repository: { url: 'https://foo.bar' } }
const node = { ...root, package: pkg }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with homepage', t => {
const pkg = { ...rootPkg, homepage: 'https://foo.bar/README.md' }
const node = { ...root, package: pkg }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with issue tracker', t => {
const pkg = { ...rootPkg, bugs: { url: 'https://foo.bar/issues' } }
const node = { ...root, package: pkg }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with distribution url', t => {
const node = { ...root, resolved: 'https://registry.npmjs.org/root/-/root-1.0.0.tgz' }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with single license', t => {
const pkg = { ...rootPkg, license: 'ISC' }
const node = { ...root, package: pkg }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with license expression', t => {
const pkg = { ...rootPkg, license: '(MIT OR Apache-2.0)' }
const node = { ...root, package: pkg }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with license object', t => {
const pkg = {
...rootPkg,
license: {
type: 'MIT',
url: 'http://github.com/kriskowal/q/raw/master/LICENSE',
},
}
const node = { ...root, package: pkg }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - from git url', t => {
const node = { ...root, type: 'git', resolved: 'https://github.com/foo/bar#1234' }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - no package info', t => {
const node = { ...root, package: undefined }
const res = cyclonedxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('node - with deps', t => {
const node = {
...root,
edgesOut: [
{ to: dep1 },
{ to: dep2 },
{ to: undefined },
{ to: { pkgid: 'foo' } },
],
}
const res = cyclonedxOutput({ npm, nodes: [node, dep1, dep2, dep2Link] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('node - with duplicate deps', t => {
const node = {
...root,
edgesOut: [
{ to: dep1 },
],
}
const res = cyclonedxOutput({ npm, nodes: [node, dep1, dep1] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
// Check that all of the generated test snapshots validate against the CycloneDX schema
t.test('schema validation', t => {
// Load schemas
const cdxSchema = require('../../schemas/cyclonedx/bom-1.5.schema.json')
const spdxLicenseSchema = require('../../schemas/cyclonedx/spdx.schema.json')
const jsfSchema = require('../../schemas/cyclonedx/jsf-0.82.schema.json')
const ajv = new Ajv({
strict: false,
schemas: [spdxLicenseSchema, jsfSchema, cdxSchema],
})
applyFormats(ajv)
applyDraftFormats(ajv)
// Retrieve compiled schema
const validate = ajv.getSchema('http://cyclonedx.org/schema/bom-1.5.schema.json')
// Load snapshots for all tests in this file
const sboms = require('../../../tap-snapshots/test/lib/utils/sbom-cyclonedx.js.test.cjs')
// Check that all snapshots validate against the CycloneDX schema
Object.entries(sboms).forEach(([name, sbom]) => {
t.ok(validate(JSON.parse(sbom)), { snapshot: name, error: validate.errors?.[0] })
})
t.end()
})

228
deps/npm/test/lib/utils/sbom-spdx.js vendored Normal file
View File

@ -0,0 +1,228 @@
const t = require('tap')
const Ajv = require('ajv')
const { spdxOutput } = require('../../../lib/utils/sbom-spdx.js')
t.cleanSnapshot = s => {
let sbom
try {
sbom = JSON.parse(s)
} catch (e) {
return s
}
sbom.documentNamespace = 'docns'
if (sbom.creationInfo) {
sbom.creationInfo.created = '2020-01-01T00:00:00.000Z'
}
return JSON.stringify(sbom, null, 2)
}
const npm = { version: '10.0.0 ' }
const rootPkg = {
author: 'Author',
}
const root = {
packageName: 'root',
version: '1.0.0',
pkgid: 'root@1.0.0',
isRoot: true,
package: rootPkg,
location: '',
edgesOut: [],
}
const dep1 = {
packageName: 'dep1',
version: '0.0.1',
pkgid: 'dep1@0.0.1',
package: {},
location: 'node_modules/dep1',
edgesOut: [],
}
const dep2 = {
packageName: 'dep2',
version: '0.0.2',
pkgid: 'dep2@0.0.2',
package: {},
location: 'node_modules/dep2',
edgesOut: [],
}
const dep3 = {
packageName: 'dep3',
version: '0.0.3',
pkgid: 'dep3@0.0.3',
package: {},
location: 'node_modules/dep3',
edgesOut: [],
}
const dep5 = {
packageName: 'dep5',
version: '0.0.5',
pkgid: 'dep5@0.0.5',
package: {},
location: 'node_modules/dep5',
edgesOut: [],
}
const dep4 = {
packageName: 'dep4',
version: '0.0.4',
pkgid: 'npm@npm:dep4@0.0.4',
package: {},
location: 'dep4',
isWorkspace: true,
edgesOut: [{ to: dep5 }],
}
const dep4Link = {
packageName: 'dep4',
version: '0.0.4',
pkgid: 'dep4@0.0.4',
package: {},
location: 'node_modules/dep4',
isLink: true,
target: dep4,
}
dep4.linksIn = new Set([dep4Link])
const dep6 = {
packageName: 'dep6',
version: '0.0.6',
pkgid: 'dep6@0.0.6',
extraneous: true,
package: {},
location: 'node_modules/dep6',
edgesOut: [],
}
t.test('single node - application package type', t => {
const res = spdxOutput({ npm, nodes: [root], packageType: 'application' })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with single license', t => {
const pkg = { ...rootPkg, license: 'ISC' }
const node = { ...root, package: pkg }
const res = spdxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with license object', t => {
const pkg = {
...rootPkg,
license: {
type: 'MIT',
url: 'http://github.com/kriskowal/q/raw/master/LICENSE',
},
}
const node = { ...root, package: pkg }
const res = spdxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with license expression', t => {
const pkg = { ...rootPkg, license: '(MIT OR Apache-2.0)' }
const node = { ...root, package: pkg }
const res = spdxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with description', t => {
const pkg = { ...rootPkg, description: 'Package description' }
const node = { ...root, package: pkg }
const res = spdxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with distribution url', t => {
const node = { ...root, resolved: 'https://registry.npmjs.org/root/-/root-1.0.0.tgz' }
const res = spdxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with homepage', t => {
const pkg = { ...rootPkg, homepage: 'https://foo.bar/README.md' }
const node = { ...root, package: pkg }
const res = spdxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - with integrity', t => {
const node = { ...root, integrity: 'sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==' }
const res = spdxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - from git url', t => {
const node = { ...root, type: 'git', resolved: 'https://github.com/foo/bar#1234' }
const res = spdxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('single node - linked', t => {
const node = { ...root, isLink: true, target: { edgesOut: [] } }
const res = spdxOutput({ npm, nodes: [node] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('node - with deps', t => {
const node = { ...root,
edgesOut: [
{ to: dep1, type: 'peer' },
{ to: dep2, type: 'optional' },
{ to: dep3, type: 'dev' },
{ to: dep4 },
{ to: undefined },
{ to: { packageName: 'foo' } },
] }
const res = spdxOutput({ npm, nodes: [node, dep1, dep2, dep3, dep4Link, dep4, dep5, dep6] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
t.test('node - with duplicate deps', t => {
const node = { ...root,
edgesOut: [
{ to: dep1 },
{ to: dep2 },
] }
const res = spdxOutput({ npm, nodes: [node, dep1, dep2, dep1, dep2] })
t.matchSnapshot(JSON.stringify(res))
t.end()
})
// Check that all of the generated test snapshots validate against the SPDX schema
t.test('schema validation', t => {
const ajv = new Ajv()
// Compile schema
const spdxSchema = require('../../schemas/spdx/spdx-2.3.schema.json')
const validate = ajv.compile(spdxSchema)
// Load snapshots for all tests in this file
const sboms = require('../../../tap-snapshots/test/lib/utils/sbom-spdx.js.test.cjs')
// Check that all snapshots validate against the SPDX schema
Object.entries(sboms).forEach(([name, sbom]) => {
t.ok(validate(JSON.parse(sbom)), { snapshot: name, error: validate.errors?.[0] })
})
t.end()
})

225
deps/npm/test/lib/utils/tar.js vendored Normal file
View File

@ -0,0 +1,225 @@
const t = require('tap')
const tar = require('tar')
const pack = require('libnpmpack')
const ssri = require('ssri')
const { readFile } = require('fs/promises')
const tmock = require('../../fixtures/tmock')
const { cleanZlib } = require('../../fixtures/clean-snapshot')
const { getContents } = require('../../../lib/utils/tar.js')
t.cleanSnapshot = data => cleanZlib(data)
const mockTar = ({ notice }) => tmock(t, '{LIB}/utils/tar.js', {
'proc-log': {
log: {
notice,
},
},
})
const printLogs = (tarball, options) => {
const logs = []
const { logTar } = mockTar({
notice: (...args) => logs.push(...args),
})
logTar(tarball, options)
return logs.join('\n')
}
t.test('should log tarball contents', async (t) => {
const testDir = t.testdir({
'package.json': JSON.stringify({
name: 'my-cool-pkg',
version: '1.0.0',
bundleDependencies: [
'bundle-dep',
],
dependencies: {
'bundle-dep': '1.0.0',
},
}),
cat: 'meow',
chai: 'blub',
dog: 'woof',
node_modules: {
'bundle-dep': {
'package.json': '',
},
},
})
const tarball = await pack(testDir)
const tarballContents = await getContents({
_id: '1',
name: 'my-cool-pkg',
version: '1.0.0',
}, tarball)
t.matchSnapshot(printLogs(tarballContents))
})
t.test('should log tarball contents of a scoped package', async (t) => {
const testDir = t.testdir({
'package.json': JSON.stringify({
name: '@myscope/my-cool-pkg',
version: '1.0.0',
bundleDependencies: [
'bundle-dep',
],
dependencies: {
'bundle-dep': '1.0.0',
},
}),
cat: 'meow',
chai: 'blub',
dog: 'woof',
node_modules: {
'bundle-dep': {
'package.json': '',
},
},
})
const tarball = await pack(testDir)
const tarballContents = await getContents({
_id: '1',
name: '@myscope/my-cool-pkg',
version: '1.0.0',
}, tarball)
t.matchSnapshot(printLogs(tarballContents))
})
t.test('should log tarball contents with unicode', async (t) => {
const { logTar } = mockTar({
notice: (str) => {
t.ok(true, 'defaults to proc-log')
return str
},
})
logTar({
files: [],
bundled: [],
size: 0,
unpackedSize: 0,
integrity: '',
}, { unicode: true })
t.end()
})
t.test('should getContents of a tarball with only a package.json', async (t) => {
const testDir = t.testdir({
'package.json': JSON.stringify({
name: 'my-cool-pkg',
version: '1.0.0',
}, null, 2),
})
const tarball = await pack(testDir)
const tarballContents = await getContents({
name: 'my-cool-pkg',
version: '1.0.0',
}, tarball)
const integrity = await ssri.fromData(tarball, {
algorithms: ['sha1', 'sha512'],
})
// zlib is nondeterministic
t.match(tarballContents.shasum, /^[0-9a-f]{40}$/)
delete tarballContents.shasum
t.strictSame(tarballContents, {
id: 'my-cool-pkg@1.0.0',
name: 'my-cool-pkg',
version: '1.0.0',
size: tarball.length,
unpackedSize: 49,
integrity: ssri.parse(integrity.sha512[0]),
filename: 'my-cool-pkg-1.0.0.tgz',
files: [{ path: 'package.json', size: 49, mode: 420 }],
entryCount: 1,
bundled: [],
}, 'contents are correct')
t.end()
})
t.test('should getContents of a tarball with a node_modules directory included', async (t) => {
const testDir = t.testdir({
package: {
'package.json': JSON.stringify({
name: 'my-cool-pkg',
version: '1.0.0',
}, null, 2),
node_modules: {
'bundle-dep': {
'package.json': JSON.stringify({
name: 'bundle-dep',
version: '1.0.0',
}, null, 2),
},
},
},
})
await tar.c({
gzip: true,
file: 'npm-example-v1.tgz',
C: testDir,
}, ['package'])
const tarball = await readFile(`npm-example-v1.tgz`)
const tarballContents = await getContents({
name: 'my-cool-pkg',
version: '1.0.0',
}, tarball)
const integrity = ssri.fromData(tarball, {
algorithms: ['sha1', 'sha512'],
})
// zlib is nondeterministic
t.match(tarballContents.shasum, /^[0-9a-f]{40}$/)
delete tarballContents.shasum
// assert mode differently according to platform
if (process.platform === 'win32') {
tarballContents.files[0].mode = 511
tarballContents.files[1].mode = 511
tarballContents.files[2].mode = 511
tarballContents.files[3].mode = 438
tarballContents.files[4].mode = 438
} else {
tarballContents.files[0].mode = 493
tarballContents.files[1].mode = 493
tarballContents.files[2].mode = 493
tarballContents.files[3].mode = 420
tarballContents.files[4].mode = 420
}
tarballContents.files.forEach((file) => {
delete file.mode
})
t.same(tarballContents, {
id: 'my-cool-pkg@1.0.0',
name: 'my-cool-pkg',
version: '1.0.0',
size: tarball.length,
unpackedSize: 97,
integrity: ssri.parse(integrity.sha512[0]),
filename: 'my-cool-pkg-1.0.0.tgz',
files: [
{ path: '', size: 0 },
{ path: 'node_modules/', size: 0 },
{ path: 'node_modules/bundle-dep/', size: 0 },
{ path: 'node_modules/bundle-dep/package.json', size: 48 },
{ path: 'package.json', size: 49 },
],
entryCount: 5,
bundled: ['bundle-dep'],
}, 'contents are correct')
t.end()
})

78
deps/npm/test/lib/utils/timers.js vendored Normal file
View File

@ -0,0 +1,78 @@
const t = require('tap')
const { resolve, join } = require('node:path')
const fs = require('graceful-fs')
const { log, time } = require('proc-log')
const tmock = require('../../fixtures/tmock')
const mockTimers = (t) => {
const logs = log.LEVELS.reduce((acc, l) => {
acc[l] = []
return acc
}, {})
const logHandler = (level, ...args) => {
logs[level].push(args.join(' '))
}
process.on('log', logHandler)
const Timers = tmock(t, '{LIB}/utils/timers')
const timers = new Timers()
t.teardown(() => {
timers.off()
process.off('log', logHandler)
})
return { timers, logs }
}
t.test('logs timing events', async (t) => {
const { timers, logs } = mockTimers(t)
time.start('foo')
time.start('bar')
time.end('bar')
timers.off()
time.end('foo')
t.equal(logs.timing.length, 1)
t.match(logs.timing[0], /^bar Completed in [0-9]+m?s/)
})
t.test('finish unstarted timer', async (t) => {
const { logs } = mockTimers(t)
time.end('foo')
t.match(logs.silly, ["timing Tried to end timer that doesn't exist: foo"])
})
t.test('writes file', async (t) => {
const { timers } = mockTimers(t)
const dir = t.testdir()
time.start('foo')
time.end('foo')
time.start('ohno')
timers.load({ timing: true, path: resolve(dir, `TIMING_FILE-`) })
timers.finish({ some: 'data' })
const data = JSON.parse(fs.readFileSync(resolve(dir, 'TIMING_FILE-timing.json')))
t.match(data, {
metadata: { some: 'data' },
timers: { foo: Number, npm: Number },
unfinishedTimers: {
ohno: [Number, Number],
},
})
})
t.test('fails to write file', async (t) => {
const { logs, timers } = mockTimers(t)
const dir = t.testdir()
timers.load({ timing: true, path: join(dir, 'does', 'not', 'exist') })
timers.finish()
t.match(logs.warn, ['timing could not write timing file:'])
})
t.test('no dir and no file', async (t) => {
const { logs, timers } = mockTimers(t)
timers.load()
timers.finish()
t.strictSame(logs.warn, [])
t.strictSame(logs.silly, [])
})

View File

@ -0,0 +1,82 @@
const t = require('tap')
const validateLockfile = require('../../../lib/utils/validate-lockfile.js')
t.test('identical inventory for both idealTree and virtualTree', async t => {
t.matchSnapshot(
validateLockfile(
new Map([
['foo', { name: 'foo', version: '1.0.0' }],
['bar', { name: 'bar', version: '2.0.0' }],
]),
new Map([
['foo', { name: 'foo', version: '1.0.0' }],
['bar', { name: 'bar', version: '2.0.0' }],
])
),
'should have no errors on identical inventories'
)
})
t.test('extra inventory items on idealTree', async t => {
t.matchSnapshot(
validateLockfile(
new Map([
['foo', { name: 'foo', version: '1.0.0' }],
['bar', { name: 'bar', version: '2.0.0' }],
]),
new Map([
['foo', { name: 'foo', version: '1.0.0' }],
['bar', { name: 'bar', version: '2.0.0' }],
['baz', { name: 'baz', version: '3.0.0' }],
])
),
'should have missing entries error'
)
})
t.test('extra inventory items on virtualTree', async t => {
t.matchSnapshot(
validateLockfile(
new Map([
['foo', { name: 'foo', version: '1.0.0' }],
['bar', { name: 'bar', version: '2.0.0' }],
['baz', { name: 'baz', version: '3.0.0' }],
]),
new Map([
['foo', { name: 'foo', version: '1.0.0' }],
['bar', { name: 'bar', version: '2.0.0' }],
])
),
'should have no errors if finding virtualTree extra items'
)
})
t.test('mismatching versions on inventory', async t => {
t.matchSnapshot(
validateLockfile(
new Map([
['foo', { name: 'foo', version: '1.0.0' }],
['bar', { name: 'bar', version: '2.0.0' }],
]),
new Map([
['foo', { name: 'foo', version: '2.0.0' }],
['bar', { name: 'bar', version: '3.0.0' }],
])
),
'should have errors for each mismatching version'
)
})
t.test('missing virtualTree inventory', async t => {
t.matchSnapshot(
validateLockfile(
new Map([]),
new Map([
['foo', { name: 'foo', version: '1.0.0' }],
['bar', { name: 'bar', version: '2.0.0' }],
['baz', { name: 'baz', version: '3.0.0' }],
])
),
'should have errors for each mismatching version'
)
})