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

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'
)
})