diff options
author | Myles Borins <mylesborins@github.com> | 2020-12-16 14:12:26 -0500 |
---|---|---|
committer | Node.js GitHub Bot <github-bot@iojs.org> | 2020-12-17 01:05:51 +0000 |
commit | d0b1b135b2b0197d93f4fb12649e0970ab68eeb0 (patch) | |
tree | 42b54c0e93b6483dc990e7e25c3e81be674dc148 /deps | |
parent | 0e96dc1f95be48a70de0beaef20b56cbb510365c (diff) | |
download | ios-node-v8-d0b1b135b2b0197d93f4fb12649e0970ab68eeb0.tar.gz ios-node-v8-d0b1b135b2b0197d93f4fb12649e0970ab68eeb0.tar.bz2 ios-node-v8-d0b1b135b2b0197d93f4fb12649e0970ab68eeb0.zip |
deps: upgrade npm to 7.2.0
PR-URL: https://github.com/nodejs/node/pull/36543
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ruy Adorno <ruyadorno@github.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Diffstat (limited to 'deps')
41 files changed, 2217 insertions, 562 deletions
diff --git a/deps/npm/CHANGELOG.md b/deps/npm/CHANGELOG.md index 0922176c6b..923c565ab3 100644 --- a/deps/npm/CHANGELOG.md +++ b/deps/npm/CHANGELOG.md @@ -1,3 +1,62 @@ +## 7.2.0 (2020-12-15) + +### FEATURES + +* [`a9c4b158c`](https://github.com/npm/cli/commit/a9c4b158c46dd0d0c8d8744a97750ffd0c30cc09) + [#2342](https://github.com/npm/cli/issues/2342) + allow npm rebuild to accept a path to a module + ([@nlf](https://github.com/nlf)) + +### DEPENDENCIES + +* [`beb371800`](https://github.com/npm/cli/commit/beb371800292140bf3882253c447168a378bc154) + [#2334](https://github.com/npm/cli/issues/2334) + remove unused top level dep tough-cookie + ([@darcyclarke](https://github.com/darcyclarke)) +* [`d45e181d1`](https://github.com/npm/cli/commit/d45e181d17dd88d82b3a97f8d9cd5fa5b6230e48) + [#2335](https://github.com/npm/cli/issues/2335) + `ini@2.0.0`, `@npmcli/config@1.2.7` + ([@isaacs](https://github.com/isaacs)) +* [`ef4b18b5a`](https://github.com/npm/cli/commit/ef4b18b5a70381b264d234817cff32eeb6848a73) + [#2309](https://github.com/npm/cli/issues/2309) + `@npmcli/arborist@2.0.2` + * properly remove deps when no lockfile and package.json is present +* [`c6c013e6e`](https://github.com/npm/cli/commit/c6c013e6ebc4fe036695db1fd491eb68f3b57c68) + `readdir-scoped-modules@1.1.0` +* [`a1a2134aa`](https://github.com/npm/cli/commit/a1a2134aa9a1092493db6d6c9a729ff5203f0dd4) + remove unused sorted-object dep + ([@nlf](https://github.com/nlf)) +* [`85c2a2d31`](https://github.com/npm/cli/commit/85c2a2d318ae066fb2c161174f5aea97e18bc9c5) + [#2344](https://github.com/npm/cli/issues/2344) + remove editor dependency + ([@nlf](https://github.com/nlf)) + +### TESTING + +* [`3a6dd511c`](https://github.com/npm/cli/commit/3a6dd511c944c5f2699825a99bba1dde333a45ef) + npm edit + ([@nlf](https://github.com/nlf)) +* [`3ba5de4e7`](https://github.com/npm/cli/commit/3ba5de4e7f6c5c0f995a29844926d6ed2833addd) + [#2347](https://github.com/npm/cli/issues/2347) + npm help-search + ([@nlf](https://github.com/nlf)) +* [`6caf19f49`](https://github.com/npm/cli/commit/6caf19f491e144be3e2a1a50f492dad48b01f361) + [#2348](https://github.com/npm/cli/issues/2348) + npm help + ([@nlf](https://github.com/nlf)) +* [`cb5847e32`](https://github.com/npm/cli/commit/cb5847e3203c52062485b5de68e4f6d29b33c361) + [#2349](https://github.com/npm/cli/issues/2349) + npm hook + ([@nlf](https://github.com/nlf)) +* [`996a2f6b1`](https://github.com/npm/cli/commit/996a2f6b130d6678998a2f6a5ec97d75534d5f66) + [#2353](https://github.com/npm/cli/issues/2353) + npm org + ([@nlf](https://github.com/nlf)) +* [`8c67c38a4`](https://github.com/npm/cli/commit/8c67c38a4f476ff5be938db6b6b3ee9ac6b44db5) + [#2354](https://github.com/npm/cli/issues/2354) + npm set + ([@nlf](https://github.com/nlf)) + ## 7.1.2 (2020-12-11) ### DEPENDENCIES diff --git a/deps/npm/docs/output/commands/npm-ls.html b/deps/npm/docs/output/commands/npm-ls.html index 3963629acc..125bbb6193 100644 --- a/deps/npm/docs/output/commands/npm-ls.html +++ b/deps/npm/docs/output/commands/npm-ls.html @@ -159,7 +159,7 @@ tree at all, use <a href="../commands/npm-explain.html"><code>npm explain</code> the results to only the paths to the packages named. Note that nested packages will <em>also</em> show the paths to the specified packages. For example, running <code>npm ls promzard</code> in npm’s source tree will show:</p> -<pre lang="bash"><code>npm@7.1.2 /path/to/npm +<pre lang="bash"><code>npm@7.2.0 /path/to/npm └─┬ init-package-json@0.0.4 └── promzard@0.1.5 </code></pre> diff --git a/deps/npm/docs/output/commands/npm.html b/deps/npm/docs/output/commands/npm.html index 92ed591092..03c7849761 100644 --- a/deps/npm/docs/output/commands/npm.html +++ b/deps/npm/docs/output/commands/npm.html @@ -148,7 +148,7 @@ npm command-line interface <pre lang="bash"><code>npm <command> [args] </code></pre> <h3 id="version">Version</h3> -<p>7.1.2</p> +<p>7.2.0</p> <h3 id="description">Description</h3> <p>npm is the package manager for the Node JavaScript platform. It puts modules in place so that node can find them, and manages dependency diff --git a/deps/npm/lib/config.js b/deps/npm/lib/config.js index ad8db4ff53..561c31e0b3 100644 --- a/deps/npm/lib/config.js +++ b/deps/npm/lib/config.js @@ -9,7 +9,7 @@ const { promisify } = require('util') const fs = require('fs') const readFile = promisify(fs.readFile) const writeFile = promisify(fs.writeFile) -const editor = promisify(require('editor')) +const { spawn } = require('child_process') const { EOL } = require('os') const ini = require('ini') @@ -138,9 +138,6 @@ const del = async key => { const edit = async () => { const { editor: e, global } = npm.flatOptions - if (!e) - throw new Error('No `editor` config or EDITOR environment variable set') - const where = global ? 'global' : 'user' const file = npm.config.data.get(where).source @@ -183,7 +180,15 @@ ${defData} `.split('\n').join(EOL) await mkdirp(dirname(file)) await writeFile(file, tmpData, 'utf8') - await editor(file, { editor: e }) + await new Promise((resolve, reject) => { + const [bin, ...args] = e.split(/\s+/) + const editor = spawn(bin, [...args, file], { stdio: 'inherit' }) + editor.on('exit', (code) => { + if (code) + return reject(new Error(`editor process exited with code: ${code}`)) + return resolve() + }) + }) } const publicVar = k => !/^(\/\/[^:]+:)?_/.test(k) diff --git a/deps/npm/lib/edit.js b/deps/npm/lib/edit.js index 43f86dfe70..9ae6349262 100644 --- a/deps/npm/lib/edit.js +++ b/deps/npm/lib/edit.js @@ -1,52 +1,36 @@ // npm edit <pkg> // open the package folder in the $EDITOR -module.exports = edit -edit.usage = 'npm edit <pkg>[/<subpkg>...]' +const { resolve } = require('path') +const fs = require('graceful-fs') +const { spawn } = require('child_process') +const npm = require('./npm.js') +const usageUtil = require('./utils/usage.js') +const splitPackageNames = require('./utils/split-package-names.js') -edit.completion = require('./utils/completion/installed-shallow.js') - -var npm = require('./npm.js') -var path = require('path') -var fs = require('graceful-fs') -var editor = require('editor') -var noProgressTillDone = require('./utils/no-progress-while-running').tillDone +const usage = usageUtil('edit', 'npm edit <pkg>[/<subpkg>...]') +const completion = require('./utils/completion/installed-shallow.js') function edit (args, cb) { - var p = args[0] - if (args.length !== 1 || !p) - return cb(edit.usage) - var e = npm.config.get('editor') - if (!e) { - return cb(new Error( - "No editor set. Set the 'editor' config, or $EDITOR environ." - )) - } - p = p.split('/') - // combine scoped parts - .reduce(function (parts, part) { - if (parts.length === 0) - return [part] - - var lastPart = parts[parts.length - 1] - // check if previous part is the first part of a scoped package - if (lastPart[0] === '@' && !lastPart.includes('/')) - parts[parts.length - 1] += '/' + part - else - parts.push(part) - - return parts - }, []) - .join('/node_modules/') - .replace(/(\/node_modules)+/, '/node_modules') - var f = path.resolve(npm.dir, p) - fs.lstat(f, function (er) { - if (er) - return cb(er) - editor(f, { editor: e }, noProgressTillDone(function (er) { - if (er) - return cb(er) - npm.commands.rebuild(args, cb) - })) + if (args.length !== 1) + return cb(usage) + + const path = splitPackageNames(args[0]) + const dir = resolve(npm.dir, path) + + fs.lstat(dir, (err) => { + if (err) + return cb(err) + + const [bin, ...args] = npm.config.get('editor').split(/\s+/) + const editor = spawn(bin, [...args, dir], { stdio: 'inherit' }) + editor.on('exit', (code) => { + if (code) + return cb(new Error(`editor process exited with code: ${code}`)) + + npm.commands.rebuild([dir], cb) + }) }) } + +module.exports = Object.assign(edit, { completion, usage }) diff --git a/deps/npm/lib/help-search.js b/deps/npm/lib/help-search.js index ae4302e8cc..c1814b4e53 100644 --- a/deps/npm/lib/help-search.js +++ b/deps/npm/lib/help-search.js @@ -1,11 +1,11 @@ const fs = require('fs') const path = require('path') const npm = require('./npm.js') -const glob = require('glob') const color = require('ansicolors') const output = require('./utils/output.js') const usageUtil = require('./utils/usage.js') const { promisify } = require('util') +const glob = promisify(require('glob')) const readFile = promisify(fs.readFile) const didYouMean = require('./utils/did-you-mean.js') const { cmdList } = require('./utils/cmd-list.js') @@ -23,12 +23,17 @@ const helpSearch = async args => { const docPath = path.resolve(__dirname, '..', 'docs/content') - // XXX: make glob return a promise and remove this wrapping - const files = await new Promise((res, rej) => - glob(`${docPath}/*/*.md`, (er, files) => er ? rej(er) : res(files))) - + const files = await glob(`${docPath}/*/*.md`) const data = await readFiles(files) const results = await searchFiles(args, data, files) + // if only one result, then just show that help section. + if (results.length === 1) { + return npm.commands.help([path.basename(results[0].file, '.md')], er => { + if (er) + throw er + }) + } + const formatted = formatResults(args, results) if (!formatted.trim()) npmUsage(false) @@ -125,15 +130,6 @@ const searchFiles = async (args, data, files) => { }) } - // if only one result, then just show that help section. - if (results.length === 1) { - npm.commands.help([results[0].file.replace(/\.md$/, '')], er => { - if (er) - throw er - }) - return [] - } - // sort results by number of results found, then by number of hits // then by number of matching lines return results.sort((a, b) => @@ -147,9 +143,6 @@ const searchFiles = async (args, data, files) => { } const formatResults = (args, results) => { - if (!results) - return 'No results for ' + args.map(JSON.stringify).join(' ') - const cols = Math.min(process.stdout.columns || Infinity, 80) + 1 const out = results.map(res => { diff --git a/deps/npm/lib/help.js b/deps/npm/lib/help.js index 5f9563e1cf..171c52704d 100644 --- a/deps/npm/lib/help.js +++ b/deps/npm/lib/help.js @@ -133,7 +133,12 @@ function viewMan (man, cb) { break case 'browser': - openUrl(htmlMan(man), 'help available at the following URL', cb) + try { + var url = htmlMan(man) + } catch (err) { + return cb(err) + } + openUrl(url, 'help available at the following URL', cb) break default: diff --git a/deps/npm/lib/org.js b/deps/npm/lib/org.js index d430131f83..b7af3f3a30 100644 --- a/deps/npm/lib/org.js +++ b/deps/npm/lib/org.js @@ -73,9 +73,9 @@ function orgSet (org, user, role, opts) { memDeets.org.size, memDeets.user, memDeets.role, - ]) + ].join('\t')) } else if (!opts.silent && opts.loglevel !== 'silent') - output(`Added ${memDeets.user} as ${memDeets.role} to ${memDeets.org.name}. You now ${memDeets.org.size} member${memDeets.org.size === 1 ? '' : 's'} in this org.`) + output(`Added ${memDeets.user} as ${memDeets.role} to ${memDeets.org.name}. You now have ${memDeets.org.size} member${memDeets.org.size === 1 ? '' : 's'} in this org.`) return memDeets }) diff --git a/deps/npm/lib/rebuild.js b/deps/npm/lib/rebuild.js index e02c89bd79..ab34b7f3df 100644 --- a/deps/npm/lib/rebuild.js +++ b/deps/npm/lib/rebuild.js @@ -39,16 +39,25 @@ const getFilterFn = args => { const spec = npa(arg) if (spec.type === 'tag' && spec.rawSpec === '') return spec - if (spec.type !== 'range' && spec.type !== 'version') + + if (spec.type !== 'range' && spec.type !== 'version' && spec.type !== 'directory') throw new Error('`npm rebuild` only supports SemVer version/range specifiers') + return spec }) + return node => specs.some(spec => { - const { version } = node.package + if (spec.type === 'directory') + return node.path === spec.fetchSpec + if (spec.name !== node.name) return false + if (spec.rawSpec === '' || spec.rawSpec === '*') return true + + const { version } = node.package + // TODO: add tests for a package with missing version return semver.satisfies(version, spec.fetchSpec) }) } diff --git a/deps/npm/lib/utils/child-path.js b/deps/npm/lib/utils/child-path.js deleted file mode 100644 index 38b9df1380..0000000000 --- a/deps/npm/lib/utils/child-path.js +++ /dev/null @@ -1,9 +0,0 @@ -var path = require('path') -var validate = require('aproba') -var moduleName = require('../utils/module-name.js') - -module.exports = childPath -function childPath (parentPath, child) { - validate('SO', arguments) - return path.join(parentPath, 'node_modules', moduleName(child)) -} diff --git a/deps/npm/lib/utils/completion/file-completion.js b/deps/npm/lib/utils/completion/file-completion.js deleted file mode 100644 index b32eda52df..0000000000 --- a/deps/npm/lib/utils/completion/file-completion.js +++ /dev/null @@ -1,23 +0,0 @@ -module.exports = fileCompletion - -const mkdir = require('mkdirp') -const glob = require('glob') - -function fileCompletion (root, req, depth, cb) { - if (typeof cb !== 'function') { - cb = depth - depth = Infinity - } - mkdir(root).catch(cb).then(() => { - // can be either exactly the req, or a descendent - var pattern = root + '/{' + req + ',' + req + '/**/*}' - var opts = { mark: true, dot: true, maxDepth: depth } - glob(pattern, opts, function (er, files) { - if (er) - return cb(er) - return cb(null, (files || []).map(function (f) { - return f.substr(root.length + 1).replace(/^\/|\/$/g, '') - })) - }) - }) -} diff --git a/deps/npm/lib/utils/deep-sort-object.js b/deps/npm/lib/utils/deep-sort-object.js deleted file mode 100644 index 56f1854f1e..0000000000 --- a/deps/npm/lib/utils/deep-sort-object.js +++ /dev/null @@ -1,14 +0,0 @@ -var sortedObject = require('sorted-object') - -module.exports = function deepSortObject (obj) { - if (obj == null || typeof obj !== 'object') - return obj - if (obj instanceof Array) - return obj.map(deepSortObject) - - obj = sortedObject(obj) - Object.keys(obj).forEach(function (key) { - obj[key] = deepSortObject(obj[key]) - }) - return obj -} diff --git a/deps/npm/lib/utils/git.js b/deps/npm/lib/utils/git.js deleted file mode 100644 index 299302b277..0000000000 --- a/deps/npm/lib/utils/git.js +++ /dev/null @@ -1,55 +0,0 @@ -const exec = require('child_process').execFile -const spawn = require('./spawn') -const npm = require('../npm.js') -const which = require('which') -const git = npm.config.get('git') -const log = require('npmlog') -const noProgressTillDone = require('./no-progress-while-running.js').tillDone -const { promisify } = require('util') - -exports.spawn = spawnGit -exports.exec = promisify(execGit) -exports.chainableExec = chainableExec -exports.whichAndExec = whichAndExec - -function prefixGitArgs () { - return process.platform === 'win32' ? ['-c', 'core.longpaths=true'] : [] -} - -function execGit (args, options, cb) { - log.info('git', args) - const fullArgs = prefixGitArgs().concat(args || []) - return exec(git, fullArgs, options, noProgressTillDone(cb)) -} - -function spawnGit (args, options) { - log.info('git', args) - // If we're already in a git command (eg, running test as an exec - // line in an interactive rebase) then these environment variables - // will force git to operate on the current project, instead of - // checking out/fetching/etc. whatever the user actually intends. - options.env = options.env || Object.keys(process.env) - .filter(k => !/^GIT/.test(k)) - .reduce((set, k) => { - set[k] = process.env[k] - return set - }, {}) - return spawn(git, prefixGitArgs().concat(args || []), options) -} - -function chainableExec () { - var args = Array.prototype.slice.call(arguments) - return [execGit].concat(args) -} - -function whichAndExec (args, options, cb) { - // check for git - which(git, function (err) { - if (err) { - err.code = 'ENOGIT' - return cb(err) - } - - execGit(args, options, cb) - }) -} diff --git a/deps/npm/lib/utils/module-name.js b/deps/npm/lib/utils/module-name.js deleted file mode 100644 index c58050e55e..0000000000 --- a/deps/npm/lib/utils/module-name.js +++ /dev/null @@ -1,38 +0,0 @@ -var path = require('path') - -module.exports = moduleName -module.exports.test = {} - -module.exports.test.pathToPackageName = pathToPackageName -function pathToPackageName (dir) { - if (dir == null) - return '' - if (dir === '') - return '' - var name = path.relative(path.resolve(dir, '..'), dir) - var scoped = path.relative(path.resolve(dir, '../..'), dir) - if (scoped[0] === '@') - return scoped.replace(/\\/g, '/') - return name.trim() -} - -module.exports.test.isNotEmpty = isNotEmpty -function isNotEmpty (str) { - return str != null && str !== '' -} - -var unknown = 0 -function moduleName (tree) { - if (tree.name) - return tree.name - var pkg = tree.package || tree - if (isNotEmpty(pkg.name) && typeof pkg.name === 'string') - return pkg.name.trim() - var pkgName = pathToPackageName(tree.path) - if (pkgName !== '') - return pkgName - if (tree._invalidName != null) - return tree._invalidName - tree._invalidName = '!invalid#' + (++unknown) - return tree._invalidName -} diff --git a/deps/npm/lib/utils/split-package-names.js b/deps/npm/lib/utils/split-package-names.js new file mode 100644 index 0000000000..bb6e449bac --- /dev/null +++ b/deps/npm/lib/utils/split-package-names.js @@ -0,0 +1,23 @@ +'use strict' + +const splitPackageNames = (path) => { + return path.split('/') + // combine scoped parts + .reduce((parts, part) => { + if (parts.length === 0) + return [part] + + const lastPart = parts[parts.length - 1] + // check if previous part is the first part of a scoped package + if (lastPart[0] === '@' && !lastPart.includes('/')) + parts[parts.length - 1] += '/' + part + else + parts.push(part) + + return parts + }, []) + .join('/node_modules/') + .replace(/(\/node_modules)+/, '/node_modules') +} + +module.exports = splitPackageNames diff --git a/deps/npm/man/man1/npm-ls.1 b/deps/npm/man/man1/npm-ls.1 index 0f486c67cc..9db81f7290 100644 --- a/deps/npm/man/man1/npm-ls.1 +++ b/deps/npm/man/man1/npm-ls.1 @@ -26,7 +26,7 @@ example, running \fBnpm ls promzard\fP in npm's source tree will show: .P .RS 2 .nf -npm@7\.1\.2 /path/to/npm +npm@7\.2\.0 /path/to/npm └─┬ init\-package\-json@0\.0\.4 └── promzard@0\.1\.5 .fi diff --git a/deps/npm/man/man1/npm.1 b/deps/npm/man/man1/npm.1 index 59655ec71e..e18720c1ed 100644 --- a/deps/npm/man/man1/npm.1 +++ b/deps/npm/man/man1/npm.1 @@ -10,7 +10,7 @@ npm <command> [args] .RE .SS Version .P -7\.1\.2 +7\.2\.0 .SS Description .P npm is the package manager for the Node JavaScript platform\. It puts diff --git a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js index 731b785187..c1f18af7e4 100644 --- a/deps/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js +++ b/deps/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js @@ -312,8 +312,14 @@ module.exports = cls => class IdealTreeBuilder extends cls { // Load on a new Arborist object, so the Nodes aren't the same, // or else it'll get super confusing when we change them! .then(async root => { - if (!this[_updateAll] && !this[_global] && !root.meta.loadedFromDisk) + if (!this[_updateAll] && !this[_global] && !root.meta.loadedFromDisk) { await new this.constructor(this.options).loadActual({ root }) + // even though we didn't load it from a package-lock.json FILE, + // we still loaded it "from disk", meaning we have to reset + // dep flags before assuming that any mutations were reflected. + if (root.children.size) + root.meta.loadedFromDisk = true + } return root }) diff --git a/deps/npm/node_modules/@npmcli/arborist/package.json b/deps/npm/node_modules/@npmcli/arborist/package.json index 2f3ccb6131..b8b27c29fd 100644 --- a/deps/npm/node_modules/@npmcli/arborist/package.json +++ b/deps/npm/node_modules/@npmcli/arborist/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/arborist", - "version": "2.0.1", + "version": "2.0.2", "description": "Manage node_modules trees", "dependencies": { "@npmcli/installed-package-contents": "^1.0.5", diff --git a/deps/npm/node_modules/@npmcli/config/package.json b/deps/npm/node_modules/@npmcli/config/package.json index f8334ab51d..26581f385c 100644 --- a/deps/npm/node_modules/@npmcli/config/package.json +++ b/deps/npm/node_modules/@npmcli/config/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/config", - "version": "1.2.6", + "version": "1.2.7", "files": [ "lib" ], @@ -27,7 +27,7 @@ "tap": "^14.10.8" }, "dependencies": { - "ini": "^1.3.5", + "ini": "^2.0.0", "mkdirp-infer-owner": "^2.0.0", "nopt": "^5.0.0", "semver": "^7.3.4", diff --git a/deps/npm/node_modules/editor/LICENSE b/deps/npm/node_modules/editor/LICENSE deleted file mode 100644 index 8b856bc4a4..0000000000 --- a/deps/npm/node_modules/editor/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Copyright 2013 James Halliday (mail@substack.net) - -This project is free software released under the MIT license: - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/deps/npm/node_modules/editor/README.markdown b/deps/npm/node_modules/editor/README.markdown deleted file mode 100644 index c1121cad2f..0000000000 --- a/deps/npm/node_modules/editor/README.markdown +++ /dev/null @@ -1,54 +0,0 @@ -editor -====== - -Launch $EDITOR in your program. - -example -======= - -``` js -var editor = require('editor'); -editor('beep.json', function (code, sig) { - console.log('finished editing with code ' + code); -}); -``` - -*** - -``` -$ node edit.js -``` - -![editor](http://substack.net/images/screenshots/editor.png) - -``` -finished editing with code 0 -``` - -methods -======= - -``` js -var editor = require('editor') -``` - -editor(file, opts={}, cb) -------------------------- - -Launch the `$EDITOR` (or `opts.editor`) for `file`. - -When the editor exits, `cb(code, sig)` fires. - -install -======= - -With [npm](http://npmjs.org) do: - -``` -npm install editor -``` - -license -======= - -MIT diff --git a/deps/npm/node_modules/editor/example/beep.json b/deps/npm/node_modules/editor/example/beep.json deleted file mode 100644 index ac07d2da87..0000000000 --- a/deps/npm/node_modules/editor/example/beep.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "a" : 3, - "b" : 4, - "c" : 5 -} diff --git a/deps/npm/node_modules/editor/example/edit.js b/deps/npm/node_modules/editor/example/edit.js deleted file mode 100644 index ee11728710..0000000000 --- a/deps/npm/node_modules/editor/example/edit.js +++ /dev/null @@ -1,4 +0,0 @@ -var editor = require('../'); -editor(__dirname + '/beep.json', function (code, sig) { - console.log('finished editing with code ' + code); -}); diff --git a/deps/npm/node_modules/editor/index.js b/deps/npm/node_modules/editor/index.js deleted file mode 100644 index 3774edbe37..0000000000 --- a/deps/npm/node_modules/editor/index.js +++ /dev/null @@ -1,20 +0,0 @@ -var spawn = require('child_process').spawn; - -module.exports = function (file, opts, cb) { - if (typeof opts === 'function') { - cb = opts; - opts = {}; - } - if (!opts) opts = {}; - - var ed = /^win/.test(process.platform) ? 'notepad' : 'vim'; - var editor = opts.editor || process.env.VISUAL || process.env.EDITOR || ed; - var args = editor.split(/\s+/); - var bin = args.shift(); - - var ps = spawn(bin, args.concat([ file ]), { stdio: 'inherit' }); - - ps.on('exit', function (code, sig) { - if (typeof cb === 'function') cb(code, sig) - }); -}; diff --git a/deps/npm/node_modules/editor/package.json b/deps/npm/node_modules/editor/package.json deleted file mode 100644 index d956a68024..0000000000 --- a/deps/npm/node_modules/editor/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name" : "editor", - "version" : "1.0.0", - "description" : "launch $EDITOR in your program", - "main" : "index.js", - "directories" : { - "example" : "example", - "test" : "test" - }, - "dependencies" : {}, - "devDependencies" : { - "tap" : "~0.4.4" - }, - "scripts" : { - "test" : "tap test/*.js" - }, - "repository" : { - "type" : "git", - "url" : "git://github.com/substack/node-editor.git" - }, - "homepage" : "https://github.com/substack/node-editor", - "keywords" : [ - "text", - "edit", - "shell" - ], - "author" : { - "name" : "James Halliday", - "email" : "mail@substack.net", - "url" : "http://substack.net" - }, - "license" : "MIT", - "engine" : { "node" : ">=0.6" } -} diff --git a/deps/npm/node_modules/ini/ini.js b/deps/npm/node_modules/ini/ini.js index b576f08d7a..7d05a719b0 100644 --- a/deps/npm/node_modules/ini/ini.js +++ b/deps/npm/node_modules/ini/ini.js @@ -1,16 +1,11 @@ -exports.parse = exports.decode = decode +const { hasOwnProperty } = Object.prototype -exports.stringify = exports.encode = encode - -exports.safe = safe -exports.unsafe = unsafe - -var eol = typeof process !== 'undefined' && +const eol = typeof process !== 'undefined' && process.platform === 'win32' ? '\r\n' : '\n' -function encode (obj, opt) { - var children = [] - var out = '' +const encode = (obj, opt) => { + const children = [] + let out = '' if (typeof opt === 'string') { opt = { @@ -18,93 +13,90 @@ function encode (obj, opt) { whitespace: false, } } else { - opt = opt || {} + opt = opt || Object.create(null) opt.whitespace = opt.whitespace === true } - var separator = opt.whitespace ? ' = ' : '=' + const separator = opt.whitespace ? ' = ' : '=' - Object.keys(obj).forEach(function (k, _, __) { - var val = obj[k] + for (const k of Object.keys(obj)) { + const val = obj[k] if (val && Array.isArray(val)) { - val.forEach(function (item) { + for (const item of val) out += safe(k + '[]') + separator + safe(item) + '\n' - }) } else if (val && typeof val === 'object') children.push(k) else out += safe(k) + separator + safe(val) + eol - }) + } if (opt.section && out.length) out = '[' + safe(opt.section) + ']' + eol + out - children.forEach(function (k, _, __) { - var nk = dotSplit(k).join('\\.') - var section = (opt.section ? opt.section + '.' : '') + nk - var child = encode(obj[k], { - section: section, - whitespace: opt.whitespace, + for (const k of children) { + const nk = dotSplit(k).join('\\.') + const section = (opt.section ? opt.section + '.' : '') + nk + const { whitespace } = opt + const child = encode(obj[k], { + section, + whitespace, }) if (out.length && child.length) out += eol out += child - }) + } return out } -function dotSplit (str) { - return str.replace(/\1/g, '\u0002LITERAL\\1LITERAL\u0002') +const dotSplit = str => + str.replace(/\1/g, '\u0002LITERAL\\1LITERAL\u0002') .replace(/\\\./g, '\u0001') - .split(/\./).map(function (part) { - return part.replace(/\1/g, '\\.') - .replace(/\2LITERAL\\1LITERAL\2/g, '\u0001') - }) -} - -function decode (str) { - var out = {} - var p = out - var section = null + .split(/\./) + .map(part => + part.replace(/\1/g, '\\.') + .replace(/\2LITERAL\\1LITERAL\2/g, '\u0001')) + +const decode = str => { + const out = Object.create(null) + let p = out + let section = null // section |key = value - var re = /^\[([^\]]*)\]$|^([^=]+)(=(.*))?$/i - var lines = str.split(/[\r\n]+/g) + const re = /^\[([^\]]*)\]$|^([^=]+)(=(.*))?$/i + const lines = str.split(/[\r\n]+/g) - lines.forEach(function (line, _, __) { + for (const line of lines) { if (!line || line.match(/^\s*[;#]/)) - return - var match = line.match(re) + continue + const match = line.match(re) if (!match) - return + continue if (match[1] !== undefined) { section = unsafe(match[1]) if (section === '__proto__') { // not allowed // keep parsing the section, but don't attach it. - p = {} - return + p = Object.create(null) + continue } - p = out[section] = out[section] || {} - return + p = out[section] = out[section] || Object.create(null) + continue } - var key = unsafe(match[2]) + const keyRaw = unsafe(match[2]) + const isArray = keyRaw.length > 2 && keyRaw.slice(-2) === '[]' + const key = isArray ? keyRaw.slice(0, -2) : keyRaw if (key === '__proto__') - return - var value = match[3] ? unsafe(match[4]) : true - switch (value) { - case 'true': - case 'false': - case 'null': value = JSON.parse(value) - } + continue + const valueRaw = match[3] ? unsafe(match[4]) : true + const value = valueRaw === 'true' || + valueRaw === 'false' || + valueRaw === 'null' ? JSON.parse(valueRaw) + : valueRaw // Convert keys with '[]' suffix to an array - if (key.length > 2 && key.slice(-2) === '[]') { - key = key.substring(0, key.length - 2) - if (key === '__proto__') - return - if (!p[key]) + if (isArray) { + if (!hasOwnProperty.call(p, key)) p[key] = [] else if (!Array.isArray(p[key])) p[key] = [p[key]] @@ -116,48 +108,48 @@ function decode (str) { p[key].push(value) else p[key] = value - }) + } // {a:{y:1},"a.b":{x:2}} --> {a:{y:1,b:{x:2}}} // use a filter to return the keys that have to be deleted. - Object.keys(out).filter(function (k, _, __) { - if (!out[k] || - typeof out[k] !== 'object' || - Array.isArray(out[k])) - return false + const remove = [] + for (const k of Object.keys(out)) { + if (!hasOwnProperty.call(out, k) || + typeof out[k] !== 'object' || + Array.isArray(out[k])) + continue // see if the parent section is also an object. // if so, add it to that, and mark this one for deletion - var parts = dotSplit(k) - var p = out - var l = parts.pop() - var nl = l.replace(/\\\./g, '.') - parts.forEach(function (part, _, __) { + const parts = dotSplit(k) + let p = out + const l = parts.pop() + const nl = l.replace(/\\\./g, '.') + for (const part of parts) { if (part === '__proto__') - return - if (!p[part] || typeof p[part] !== 'object') - p[part] = {} + continue + if (!hasOwnProperty.call(p, part) || typeof p[part] !== 'object') + p[part] = Object.create(null) p = p[part] - }) + } if (p === out && nl === l) - return false + continue p[nl] = out[k] - return true - }).forEach(function (del, _, __) { + remove.push(k) + } + for (const del of remove) delete out[del] - }) return out } -function isQuoted (val) { - return (val.charAt(0) === '"' && val.slice(-1) === '"') || +const isQuoted = val => + (val.charAt(0) === '"' && val.slice(-1) === '"') || (val.charAt(0) === "'" && val.slice(-1) === "'") -} -function safe (val) { - return (typeof val !== 'string' || +const safe = val => + (typeof val !== 'string' || val.match(/[=\r\n]/) || val.match(/^\[/) || (val.length > 1 && @@ -165,9 +157,8 @@ function safe (val) { val !== val.trim()) ? JSON.stringify(val) : val.replace(/;/g, '\\;').replace(/#/g, '\\#') -} -function unsafe (val, doUnesc) { +const unsafe = (val, doUnesc) => { val = (val || '').trim() if (isQuoted(val)) { // remove the single quotes before calling JSON.parse @@ -179,10 +170,10 @@ function unsafe (val, doUnesc) { } catch (_) {} } else { // walk the val to find the first not-escaped ; character - var esc = false - var unesc = '' - for (var i = 0, l = val.length; i < l; i++) { - var c = val.charAt(i) + let esc = false + let unesc = '' + for (let i = 0, l = val.length; i < l; i++) { + const c = val.charAt(i) if (esc) { if ('\\;#'.indexOf(c) !== -1) unesc += c @@ -204,3 +195,12 @@ function unsafe (val, doUnesc) { } return val } + +module.exports = { + parse: decode, + decode, + stringify: encode, + encode, + safe, + unsafe, +} diff --git a/deps/npm/node_modules/ini/package.json b/deps/npm/node_modules/ini/package.json index c830a3556e..59b7d5d0ad 100644 --- a/deps/npm/node_modules/ini/package.json +++ b/deps/npm/node_modules/ini/package.json @@ -2,7 +2,7 @@ "author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)", "name": "ini", "description": "An ini encoder/decoder for node", - "version": "1.3.8", + "version": "2.0.0", "repository": { "type": "git", "url": "git://github.com/isaacs/ini.git" @@ -29,5 +29,8 @@ "license": "ISC", "files": [ "ini.js" - ] + ], + "engines": { + "node": ">=10" + } } diff --git a/deps/npm/node_modules/sorted-object/LICENSE.txt b/deps/npm/node_modules/sorted-object/LICENSE.txt deleted file mode 100644 index 2edd064bf5..0000000000 --- a/deps/npm/node_modules/sorted-object/LICENSE.txt +++ /dev/null @@ -1,47 +0,0 @@ -Dual licensed under WTFPL and MIT: - ---- - -Copyright © 2014–2016 Domenic Denicola <d@domenic.me> - -This work is free. You can redistribute it and/or modify it under the -terms of the Do What The Fuck You Want To Public License, Version 2, -as published by Sam Hocevar. See below for more details. - - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - Version 2, December 2004 - - Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> - - Everyone is permitted to copy and distribute verbatim or modified - copies of this license document, and changing it is allowed as long - as the name is changed. - - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. You just DO WHAT THE FUCK YOU WANT TO. - ---- - -The MIT License (MIT) - -Copyright © 2014–2016 Domenic Denicola <d@domenic.me> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/deps/npm/node_modules/sorted-object/lib/sorted-object.js b/deps/npm/node_modules/sorted-object/lib/sorted-object.js deleted file mode 100644 index 1b3fe81a6b..0000000000 --- a/deps/npm/node_modules/sorted-object/lib/sorted-object.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -module.exports = function (input) { - var output = {}; - - Object.keys(input).sort().forEach(function (key) { - output[key] = input[key]; - }); - - return output; -}; diff --git a/deps/npm/node_modules/sorted-object/package.json b/deps/npm/node_modules/sorted-object/package.json deleted file mode 100644 index 2e81f36d6e..0000000000 --- a/deps/npm/node_modules/sorted-object/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "sorted-object", - "description": "Returns a copy of an object with its keys sorted", - "keywords": [ - "sort", - "keys", - "object" - ], - "version": "2.0.1", - "author": "Domenic Denicola <d@domenic.me> (https://domenic.me/)", - "license": "(WTFPL OR MIT)", - "repository": "domenic/sorted-object", - "main": "lib/sorted-object.js", - "files": [ - "lib/" - ], - "scripts": { - "test": "tape test/tests.js", - "lint": "eslint ." - }, - "devDependencies": { - "eslint": "^2.4.0", - "tape": "^4.5.1" - } -} diff --git a/deps/npm/package.json b/deps/npm/package.json index 89cb0e6aad..8e2917c5ed 100644 --- a/deps/npm/package.json +++ b/deps/npm/package.json @@ -1,5 +1,5 @@ { - "version": "7.1.2", + "version": "7.2.0", "name": "npm", "description": "a package manager for JavaScript", "keywords": [ @@ -42,9 +42,9 @@ "./package.json": "./package.json" }, "dependencies": { - "@npmcli/arborist": "^2.0.1", + "@npmcli/arborist": "^2.0.2", "@npmcli/ci-detect": "^1.2.0", - "@npmcli/config": "^1.2.6", + "@npmcli/config": "^1.2.7", "@npmcli/run-script": "^1.8.1", "abbrev": "~1.1.1", "ansicolors": "~0.3.2", @@ -58,11 +58,10 @@ "cli-columns": "^3.1.2", "cli-table3": "^0.6.0", "columnify": "~1.5.4", - "editor": "~1.0.0", "glob": "^7.1.4", "graceful-fs": "^4.2.3", "hosted-git-info": "^3.0.6", - "ini": "^1.3.8", + "ini": "^2.0.0", "init-package-json": "^2.0.1", "is-cidr": "^4.0.2", "leven": "^3.1.0", @@ -97,9 +96,9 @@ "read": "~1.0.7", "read-package-json": "^3.0.0", "read-package-json-fast": "^1.2.1", + "readdir-scoped-modules": "^1.1.0", "rimraf": "^3.0.2", "semver": "^7.3.4", - "sorted-object": "~2.0.1", "ssri": "^8.0.0", "tar": "^6.0.5", "text-table": "~0.2.0", @@ -127,7 +126,6 @@ "cli-columns", "cli-table3", "columnify", - "editor", "glob", "graceful-fs", "hosted-git-info", @@ -167,9 +165,9 @@ "read", "read-package-json", "read-package-json-fast", + "readdir-scoped-modules", "rimraf", "semver", - "sorted-object", "ssri", "tar", "text-table", diff --git a/deps/npm/test/lib/config.js b/deps/npm/test/lib/config.js index 8a11a40c81..74cd530c68 100644 --- a/deps/npm/test/lib/config.js +++ b/deps/npm/test/lib/config.js @@ -1,5 +1,6 @@ const t = require('tap') const requireInject = require('require-inject') +const { EventEmitter } = require('events') const redactCwd = (path) => { const normalizePath = p => p @@ -437,10 +438,16 @@ sign-git-commit=true` cb() }, }, - editor: (file, { editor }, cb) => { - t.equal(file, '~/.npmrc', 'should match user source data') - t.equal(editor, 'vi', 'should use default editor') - cb() + child_process: { + spawn: (bin, args) => { + t.equal(bin, 'vi', 'should use default editor') + t.strictSame(args, ['~/.npmrc'], 'should match user source data') + const ee = new EventEmitter() + process.nextTick(() => { + ee.emit('exit', 0) + }) + return ee + }, }, } const config = requireInject('../../lib/config.js', editMocks) @@ -487,15 +494,21 @@ t.test('config edit --global', t => { cb() }, }, - editor: (file, { editor }, cb) => { - t.equal(file, '/etc/npmrc', 'should match global source data') - t.equal(editor, 'vi', 'should use default editor') - cb() + child_process: { + spawn: (bin, args, cb) => { + t.equal(bin, 'vi', 'should use default editor') + t.strictSame(args, ['/etc/npmrc'], 'should match global source data') + const ee = new EventEmitter() + process.nextTick(() => { + ee.emit('exit', 137) + }) + return ee + }, }, } const config = requireInject('../../lib/config.js', editMocks) config(['edit'], (err) => { - t.ifError(err, 'npm config edit --global') + t.match(err, /exited with code: 137/, 'propagated exit code from editor') }) t.teardown(() => { @@ -505,19 +518,6 @@ t.test('config edit --global', t => { }) }) -t.test('config edit no editor set', t => { - flatOptions.editor = undefined - config(['edit'], (err) => { - t.match( - err, - /No `editor` config or EDITOR environment variable set/, - 'should throw no available editor error' - ) - flatOptions.editor = 'vi' - t.end() - }) -}) - t.test('completion', t => { const { completion } = config diff --git a/deps/npm/test/lib/edit.js b/deps/npm/test/lib/edit.js new file mode 100644 index 0000000000..0d3bbb4c57 --- /dev/null +++ b/deps/npm/test/lib/edit.js @@ -0,0 +1,123 @@ +const { test } = require('tap') +const { resolve } = require('path') +const requireInject = require('require-inject') +const { EventEmitter } = require('events') + +let editorBin = null +let editorArgs = null +let editorOpts = null +let EDITOR_CODE = 0 +const childProcess = { + spawn: (bin, args, opts) => { + // save for assertions + editorBin = bin + editorArgs = args + editorOpts = opts + + const editorEvents = new EventEmitter() + process.nextTick(() => { + editorEvents.emit('exit', EDITOR_CODE) + }) + return editorEvents + }, +} + +let rebuildArgs = null +let EDITOR = 'vim' +const npm = { + config: { + get: () => EDITOR, + }, + dir: resolve(__dirname, '../../node_modules'), + commands: { + rebuild: (args, cb) => { + rebuildArgs = args + return cb() + }, + }, +} + +const gracefulFs = require('graceful-fs') +const edit = requireInject('../../lib/edit.js', { + '../../lib/npm.js': npm, + child_process: childProcess, + 'graceful-fs': gracefulFs, +}) + +test('npm edit', t => { + t.teardown(() => { + rebuildArgs = null + editorBin = null + editorArgs = null + editorOpts = null + }) + + return edit(['semver'], (err) => { + if (err) + throw err + + const path = resolve(__dirname, '../../node_modules/semver') + t.strictSame(editorBin, EDITOR, 'used the correct editor') + t.strictSame(editorArgs, [path], 'edited the correct directory') + t.strictSame(editorOpts, { stdio: 'inherit' }, 'passed the correct opts') + t.strictSame(rebuildArgs, [path], 'passed the correct path to rebuild') + t.end() + }) +}) + +test('npm edit editor has flags', t => { + EDITOR = 'code -w' + t.teardown(() => { + rebuildArgs = null + editorBin = null + editorArgs = null + editorOpts = null + EDITOR = 'vim' + }) + + return edit(['semver'], (err) => { + if (err) + throw err + + const path = resolve(__dirname, '../../node_modules/semver') + t.strictSame(editorBin, 'code', 'used the correct editor') + t.strictSame(editorArgs, ['-w', path], 'edited the correct directory, keeping flags') + t.strictSame(editorOpts, { stdio: 'inherit' }, 'passed the correct opts') + t.strictSame(rebuildArgs, [path], 'passed the correct path to rebuild') + t.end() + }) +}) + +test('npm edit no args', t => { + return edit([], (err) => { + t.match(err, /npm edit/, 'throws usage error') + t.end() + }) +}) + +test('npm edit lstat error propagates', t => { + const _lstat = gracefulFs.lstat + gracefulFs.lstat = (dir, cb) => { + return cb(new Error('lstat failed')) + } + t.teardown(() => { + gracefulFs.lstat = _lstat + }) + + return edit(['semver'], (err) => { + t.match(err, /lstat failed/, 'user received correct error') + t.end() + }) +}) + +test('npm edit editor exit code error propagates', t => { + EDITOR_CODE = 137 + t.teardown(() => { + EDITOR_CODE = 0 + }) + + return edit(['semver'], (err) => { + t.match(err, /exited with code: 137/, 'user received correct error') + t.end() + }) +}) diff --git a/deps/npm/test/lib/help-search.js b/deps/npm/test/lib/help-search.js new file mode 100644 index 0000000000..5ecf5db061 --- /dev/null +++ b/deps/npm/test/lib/help-search.js @@ -0,0 +1,181 @@ +const { test } = require('tap') +const { join } = require('path') +const requireInject = require('require-inject') +const ansicolors = require('ansicolors') + +const OUTPUT = [] +const output = (msg) => { + OUTPUT.push(msg) +} + +let npmHelpArgs = null +let npmHelpErr = null +const npm = { + color: false, + flatOptions: { + long: false, + }, + commands: { + help: (args, cb) => { + npmHelpArgs = args + return cb(npmHelpErr) + }, + }, +} + +let npmUsageArg = null +const npmUsage = (arg) => { + npmUsageArg = arg +} + +let globRoot = null +const globDir = { + 'npm-exec.md': 'the exec command\nhelp has multiple lines of exec help\none of them references exec', + 'npm-something.md': 'another\ncommand you run\nthat\nreferences exec\nand has multiple lines\nwith no matches\nthat will be ignored\nand another line\nthat does have exec as well', + 'npm-run-script.md': 'the scripted run-script command runs scripts\nand has lines\nsome of which dont match the string run\nor script\nscript', + 'npm-install.md': 'does a thing in a script\nif a thing does not exist in a thing you run\nto install it and run it maybe in a script', + 'npm-help.md': 'will run the `help-search` command if you need to run it to help you search', + 'npm-help-search.md': 'is the help search command\nthat you get if you run help-search', + 'npm-useless.md': 'exec\nexec', + 'npm-more-useless.md': 'exec exec', + 'npm-extra-useless.md': 'exec\nexec\nexec', +} +const glob = (p, cb) => cb(null, Object.keys(globDir).map((file) => join(globRoot, file))) + +const helpSearch = requireInject('../../lib/help-search.js', { + '../../lib/npm.js': npm, + '../../lib/utils/npm-usage.js': npmUsage, + '../../lib/utils/output.js': output, + glob, +}) + +test('npm help-search', t => { + globRoot = t.testdir(globDir) + t.teardown(() => { + OUTPUT.length = 0 + globRoot = null + }) + + return helpSearch(['exec'], (err) => { + if (err) + throw err + + t.match(OUTPUT, /Top hits for/, 'outputs results') + t.match(OUTPUT, /Did you mean this\?\n\s+exec/, 'matched command, so suggest it') + t.end() + }) +}) + +test('npm help-search multiple terms', t => { + globRoot = t.testdir(globDir) + t.teardown(() => { + OUTPUT.length = 0 + globRoot = null + }) + + return helpSearch(['run', 'script'], (err) => { + if (err) + throw err + + t.match(OUTPUT, /Top hits for/, 'outputs results') + t.match(OUTPUT, /run:\d+ script:\d+/, 'shows hit counts for both terms') + t.end() + }) +}) + +test('npm help-search single result prints full section', t => { + globRoot = t.testdir(globDir) + t.teardown(() => { + OUTPUT.length = 0 + npmHelpArgs = null + globRoot = null + }) + + return helpSearch(['does not exist in'], (err) => { + if (err) + throw err + + t.strictSame(npmHelpArgs, ['npm-install'], 'identified the correct man page and called help with it') + t.end() + }) +}) + +test('npm help-search single result propagates error', t => { + globRoot = t.testdir(globDir) + npmHelpErr = new Error('help broke') + t.teardown(() => { + OUTPUT.length = 0 + npmHelpArgs = null + npmHelpErr = null + globRoot = null + }) + + return helpSearch(['does not exist in'], (err) => { + t.strictSame(npmHelpArgs, ['npm-install'], 'identified the correct man page and called help with it') + t.match(err, /help broke/, 'propagated the error from help') + t.end() + }) +}) + +test('npm help-search long output', t => { + globRoot = t.testdir(globDir) + npm.flatOptions.long = true + t.teardown(() => { + OUTPUT.length = 0 + npm.flatOptions.long = false + globRoot = null + }) + + return helpSearch(['exec'], (err) => { + if (err) + throw err + + t.match(OUTPUT, /has multiple lines of exec help/, 'outputs detailed results') + t.end() + }) +}) + +test('npm help-search long output with color', t => { + globRoot = t.testdir(globDir) + npm.flatOptions.long = true + npm.color = true + t.teardown(() => { + OUTPUT.length = 0 + npm.flatOptions.long = false + npm.color = false + globRoot = null + }) + + return helpSearch(['help-search'], (err) => { + if (err) + throw err + + const highlightedText = ansicolors.bgBlack(ansicolors.red('help-search')) + t.equal(OUTPUT.some((line) => line.includes(highlightedText)), true, 'returned highlighted search terms') + t.end() + }) +}) + +test('npm help-search no args', t => { + return helpSearch([], (err) => { + t.match(err, /npm help-search/, 'throws usage') + t.end() + }) +}) + +test('npm help-search no matches', t => { + globRoot = t.testdir(globDir) + t.teardown(() => { + OUTPUT.length = 0 + npmUsageArg = null + globRoot = null + }) + + return helpSearch(['asdfasdf'], (err) => { + if (err) + throw err + + t.equal(npmUsageArg, false, 'called npmUsage for no matches') + t.end() + }) +}) diff --git a/deps/npm/test/lib/help.js b/deps/npm/test/lib/help.js new file mode 100644 index 0000000000..17018acc61 --- /dev/null +++ b/deps/npm/test/lib/help.js @@ -0,0 +1,362 @@ +const { test } = require('tap') +const requireInject = require('require-inject') +const { EventEmitter } = require('events') + +let npmUsageArg = null +const npmUsage = (arg) => { + npmUsageArg = arg +} + +const npmConfig = { + usage: false, + viewer: undefined, + loglevel: undefined, +} + +let helpSearchArgs = null +const npm = { + config: { + get: (key) => npmConfig[key], + set: (key, value) => { + npmConfig[key] = value + }, + parsedArgv: { + cooked: [], + }, + }, + commands: { + 'help-search': (args, cb) => { + helpSearchArgs = args + return cb() + }, + help: { + usage: 'npm help <term>', + }, + }, + deref: (cmd) => {}, +} + +const OUTPUT = [] +const output = (msg) => { + OUTPUT.push(msg) +} + +const globDefaults = [ + '/root/man/man1/npm-whoami.1', + '/root/man/man5/npmrc.5', + '/root/man/man7/disputes.7', +] + +let globErr = null +let globResult = globDefaults +const glob = (p, cb) => { + return cb(globErr, globResult) +} + +let spawnBin = null +let spawnArgs = null +const spawn = (bin, args) => { + spawnBin = bin + spawnArgs = args + const spawnEmitter = new EventEmitter() + process.nextTick(() => { + spawnEmitter.emit('close', 0) + }) + return spawnEmitter +} + +let openUrlArg = null +const openUrl = (url, msg, cb) => { + openUrlArg = url + return cb() +} + +const help = requireInject('../../lib/help.js', { + '../../lib/npm.js': npm, + '../../lib/utils/npm-usage.js': npmUsage, + '../../lib/utils/open-url.js': openUrl, + '../../lib/utils/output.js': output, + '../../lib/utils/spawn.js': spawn, + glob, +}) + +test('npm help', t => { + t.teardown(() => { + npmUsageArg = null + }) + + return help([], (err) => { + if (err) + throw err + + t.equal(npmUsageArg, false, 'called npmUsage') + t.end() + }) +}) + +test('npm help completion', async t => { + t.teardown(() => { + globErr = null + }) + const completion = (opts) => new Promise((resolve, reject) => { + help.completion(opts, (err, res) => { + if (err) + return reject(err) + return resolve(res) + }) + }) + + const noArgs = await completion({ conf: { argv: { remain: [] } } }) + t.strictSame(noArgs, ['help', 'whoami', 'npmrc', 'disputes'], 'outputs available help pages') + const threeArgs = await completion({ conf: { argv: { remain: ['one', 'two', 'three'] } } }) + t.strictSame(threeArgs, [], 'outputs no results when more than 2 args are provided') + globErr = new Error('glob failed') + t.rejects(completion({ conf: { argv: { remain: [] } } }), /glob failed/, 'glob errors propagate') +}) + +test('npm help -h', t => { + npmConfig.usage = true + t.teardown(() => { + npmConfig.usage = false + OUTPUT.length = 0 + }) + + return help(['help'], (err) => { + if (err) + throw err + + t.strictSame(OUTPUT, ['npm help <term>'], 'outputs usage information for command') + t.end() + }) +}) + +test('npm help multiple args calls search', t => { + t.teardown(() => { + helpSearchArgs = null + }) + + return help(['run', 'script'], (err) => { + if (err) + throw err + + t.strictSame(helpSearchArgs, ['run', 'script'], 'passed the args to help-search') + t.end() + }) +}) + +test('npm help no matches calls search', t => { + globResult = [] + t.teardown(() => { + helpSearchArgs = null + globResult = globDefaults + }) + + return help(['asdfasdf'], (err) => { + if (err) + throw err + + t.strictSame(helpSearchArgs, ['asdfasdf'], 'passed the args to help-search') + t.end() + }) +}) + +test('npm help glob errors propagate', t => { + globErr = new Error('glob failed') + t.teardown(() => { + globErr = null + spawnBin = null + spawnArgs = null + }) + + return help(['whoami'], (err) => { + t.match(err, /glob failed/, 'glob error propagates') + t.end() + }) +}) + +test('npm help whoami', t => { + globResult = ['/root/man/man1/npm-whoami.1.xz'] + t.teardown(() => { + globResult = globDefaults + spawnBin = null + spawnArgs = null + }) + + return help(['whoami'], (err) => { + if (err) + throw err + + t.equal(spawnBin, 'man', 'calls man by default') + t.strictSame(spawnArgs, ['1', 'npm-whoami'], 'passes the correct arguments') + t.end() + }) +}) + +test('npm help 1 install', t => { + npmConfig.viewer = 'browser' + globResult = [ + '/root/man/man5/install.5', + '/root/man/man1/npm-install.1', + ] + + t.teardown(() => { + npmConfig.viewer = undefined + globResult = globDefaults + spawnBin = null + spawnArgs = null + }) + + return help(['1', 'install'], (err) => { + if (err) + throw err + + t.match(openUrlArg, /commands(\/|\\)npm-install.html$/, 'attempts to open the correct url') + t.end() + }) +}) + +test('npm help 5 install', t => { + npmConfig.viewer = 'browser' + globResult = [ + '/root/man/man5/install.5', + '/root/man/man1/npm-install.1', + ] + + t.teardown(() => { + npmConfig.viewer = undefined + globResult = globDefaults + spawnBin = null + spawnArgs = null + }) + + return help(['5', 'install'], (err) => { + if (err) + throw err + + t.match(openUrlArg, /configuring-npm(\/|\\)install.html$/, 'attempts to open the correct url') + t.end() + }) +}) + +test('npm help 7 config', t => { + npmConfig.viewer = 'browser' + globResult = [ + '/root/man/man1/npm-config.1', + '/root/man/man7/config.7', + ] + t.teardown(() => { + npmConfig.viewer = undefined + globResult = globDefaults + spawnBin = null + spawnArgs = null + }) + + return help(['7', 'config'], (err) => { + if (err) + throw err + + t.match(openUrlArg, /using-npm(\/|\\)config.html$/, 'attempts to open the correct url') + t.end() + }) +}) + +test('npm help with browser viewer and invalid section throws', t => { + npmConfig.viewer = 'browser' + globResult = [ + '/root/man/man1/npm-config.1', + '/root/man/man7/config.7', + '/root/man/man9/config.9', + ] + t.teardown(() => { + npmConfig.viewer = undefined + globResult = globDefaults + spawnBin = null + spawnArgs = null + }) + + return help(['9', 'config'], (err) => { + t.match(err, /invalid man section: 9/, 'throws appropriate error') + t.end() + }) +}) + +test('npm help global redirects to folders', t => { + globResult = ['/root/man/man5/folders.5'] + t.teardown(() => { + globResult = globDefaults + spawnBin = null + spawnArgs = null + }) + + return help(['global'], (err) => { + if (err) + throw err + + t.equal(spawnBin, 'man', 'calls man by default') + t.strictSame(spawnArgs, ['5', 'folders'], 'passes the correct arguments') + t.end() + }) +}) + +test('npm help package.json redirects to package-json', t => { + globResult = ['/root/man/man5/package-json.5'] + t.teardown(() => { + globResult = globDefaults + spawnBin = null + spawnArgs = null + }) + + return help(['package.json'], (err) => { + if (err) + throw err + + t.equal(spawnBin, 'man', 'calls man by default') + t.strictSame(spawnArgs, ['5', 'package-json'], 'passes the correct arguments') + t.end() + }) +}) + +test('npm help ?(un)star', t => { + npmConfig.viewer = 'woman' + globResult = [ + '/root/man/man1/npm-star.1', + '/root/man/man1/npm-unstar.1', + ] + t.teardown(() => { + npmConfig.viewer = undefined + globResult = globDefaults + spawnBin = null + spawnArgs = null + }) + + return help(['?(un)star'], (err) => { + if (err) + throw err + + t.equal(spawnBin, 'emacsclient', 'maps woman to emacs correctly') + t.strictSame(spawnArgs, ['-e', `(woman-find-file '/root/man/man1/npm-unstar.1')`], 'passes the correct arguments') + t.end() + }) +}) + +test('npm help un*', t => { + globResult = [ + '/root/man/man1/npm-unstar.1', + '/root/man/man1/npm-uninstall.1', + '/root/man/man1/npm-unpublish.1', + ] + t.teardown(() => { + globResult = globDefaults + spawnBin = null + spawnArgs = null + }) + + return help(['un*'], (err) => { + if (err) + throw err + + t.equal(spawnBin, 'man', 'calls man by default') + t.strictSame(spawnArgs, ['1', 'npm-unstar'], 'passes the correct arguments') + t.end() + }) +}) diff --git a/deps/npm/test/lib/hook.js b/deps/npm/test/lib/hook.js new file mode 100644 index 0000000000..3599042021 --- /dev/null +++ b/deps/npm/test/lib/hook.js @@ -0,0 +1,589 @@ +const { test } = require('tap') +const requireInject = require('require-inject') + +const npm = { + flatOptions: { + json: false, + parseable: false, + silent: false, + loglevel: 'info', + unicode: false, + }, +} + +const pkgTypes = { + semver: 'package', + '@npmcli': 'scope', + npm: 'owner', +} + +const now = Date.now() +let hookResponse = null +let hookArgs = null +const libnpmhook = { + add: async (pkg, uri, secret, opts) => { + hookArgs = { pkg, uri, secret, opts } + return { id: 1, name: pkg.replace(/^@/, ''), type: pkgTypes[pkg], endpoint: uri } + }, + ls: async (opts) => { + hookArgs = opts + let id = 0 + if (hookResponse) + return hookResponse + + return Object.keys(pkgTypes).map((name) => ({ + id: ++id, + name: name.replace(/^@/, ''), + type: pkgTypes[name], + endpoint: 'https://google.com', + last_delivery: id % 2 === 0 ? now : undefined, + })) + }, + rm: async (id, opts) => { + hookArgs = { id, opts } + const pkg = Object.keys(pkgTypes)[0] + return { id: 1, name: pkg.replace(/^@/, ''), type: pkgTypes[pkg], endpoint: 'https://google.com' } + }, + update: async (id, uri, secret, opts) => { + hookArgs = { id, uri, secret, opts } + const pkg = Object.keys(pkgTypes)[0] + return { id, name: pkg.replace(/^@/, ''), type: pkgTypes[pkg], endpoint: uri } + }, +} + +const output = [] +const hook = requireInject('../../lib/hook.js', { + '../../lib/npm.js': npm, + '../../lib/utils/otplease.js': async (opts, fn) => fn(opts), + '../../lib/utils/output.js': (msg) => { + output.push(msg) + }, + libnpmhook, +}) + +test('npm hook no args', t => { + return hook([], (err) => { + t.match(err, /npm hook add/, 'throws usage with no arguments') + t.end() + }) +}) + +test('npm hook add', t => { + t.teardown(() => { + hookArgs = null + output.length = 0 + }) + + return hook(['add', 'semver', 'https://google.com', 'some-secret'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + pkg: 'semver', + uri: 'https://google.com', + secret: 'some-secret', + opts: npm.flatOptions, + }, 'provided the correct arguments to libnpmhook') + t.strictSame(output, ['+ semver -> https://google.com'], 'prints the correct output') + t.end() + }) +}) + +test('npm hook add - unicode output', t => { + npm.flatOptions.unicode = true + t.teardown(() => { + npm.flatOptions.unicode = false + hookArgs = null + output.length = 0 + }) + + return hook(['add', 'semver', 'https://google.com', 'some-secret'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + pkg: 'semver', + uri: 'https://google.com', + secret: 'some-secret', + opts: npm.flatOptions, + }, 'provided the correct arguments to libnpmhook') + t.strictSame(output, ['+ semver ➜ https://google.com'], 'prints the correct output') + t.end() + }) +}) + +test('npm hook add - json output', t => { + npm.flatOptions.json = true + t.teardown(() => { + npm.flatOptions.json = false + hookArgs = null + output.length = 0 + }) + + return hook(['add', '@npmcli', 'https://google.com', 'some-secret'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + pkg: '@npmcli', + uri: 'https://google.com', + secret: 'some-secret', + opts: npm.flatOptions, + }, 'provided the correct arguments to libnpmhook') + t.strictSame(JSON.parse(output[0]), { + id: 1, + name: 'npmcli', + endpoint: 'https://google.com', + type: 'scope', + }, 'prints the correct json output') + t.end() + }) +}) + +test('npm hook add - parseable output', t => { + npm.flatOptions.parseable = true + t.teardown(() => { + npm.flatOptions.parseable = false + hookArgs = null + output.length = 0 + }) + + return hook(['add', '@npmcli', 'https://google.com', 'some-secret'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + pkg: '@npmcli', + uri: 'https://google.com', + secret: 'some-secret', + opts: npm.flatOptions, + }, 'provided the correct arguments to libnpmhook') + t.strictSame(output[0].split(/\t/), [ + 'id', 'name', 'type', 'endpoint', + ], 'prints the correct parseable output headers') + t.strictSame(output[1].split(/\t/), [ + '1', 'npmcli', 'scope', 'https://google.com', + ], 'prints the correct parseable values') + t.end() + }) +}) + +test('npm hook add - silent output', t => { + npm.flatOptions.silent = true + t.teardown(() => { + npm.flatOptions.silent = false + hookArgs = null + output.length = 0 + }) + + return hook(['add', '@npmcli', 'https://google.com', 'some-secret'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + pkg: '@npmcli', + uri: 'https://google.com', + secret: 'some-secret', + opts: npm.flatOptions, + }, 'provided the correct arguments to libnpmhook') + t.strictSame(output, [], 'printed no output') + t.end() + }) +}) + +test('npm hook ls', t => { + t.teardown(() => { + hookArgs = null + output.length = 0 + }) + + return hook(['ls'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + ...npm.flatOptions, + package: undefined, + }, 'received the correct arguments') + t.equal(output[0], 'You have 3 hooks configured.', 'prints the correct header') + const out = require('../../lib/utils/ansi-trim')(output[1]) + t.match(out, /semver.*https:\/\/google.com.*\n.*\n.*never triggered/, 'prints package hook') + t.match(out, /@npmcli.*https:\/\/google.com.*\n.*\n.*triggered just now/, 'prints scope hook') + t.match(out, /~npm.*https:\/\/google.com.*\n.*\n.*never triggered/, 'prints owner hook') + t.end() + }) +}) + +test('npm hook ls, no results', t => { + hookResponse = [] + t.teardown(() => { + hookResponse = null + hookArgs = null + output.length = 0 + }) + + return hook(['ls'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + ...npm.flatOptions, + package: undefined, + }, 'received the correct arguments') + t.equal(output[0], 'You don\'t have any hooks configured yet.', 'prints the correct result') + t.end() + }) +}) + +test('npm hook ls, single result', t => { + hookResponse = [{ + id: 1, + name: 'semver', + type: 'package', + endpoint: 'https://google.com', + }] + + t.teardown(() => { + hookResponse = null + hookArgs = null + output.length = 0 + }) + + return hook(['ls'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + ...npm.flatOptions, + package: undefined, + }, 'received the correct arguments') + t.equal(output[0], 'You have one hook configured.', 'prints the correct header') + const out = require('../../lib/utils/ansi-trim')(output[1]) + t.match(out, /semver.*https:\/\/google.com.*\n.*\n.*never triggered/, 'prints package hook') + t.end() + }) +}) + +test('npm hook ls - json output', t => { + npm.flatOptions.json = true + t.teardown(() => { + npm.flatOptions.json = false + hookArgs = null + output.length = 0 + }) + + return hook(['ls'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + ...npm.flatOptions, + package: undefined, + }, 'received the correct arguments') + const out = JSON.parse(output[0]) + t.match(out, [{ + id: 1, + name: 'semver', + type: 'package', + endpoint: 'https://google.com', + }, { + id: 2, + name: 'npmcli', + type: 'scope', + endpoint: 'https://google.com', + }, { + id: 3, + name: 'npm', + type: 'owner', + endpoint: 'https://google.com', + }], 'prints the correct output') + t.end() + }) +}) + +test('npm hook ls - parseable output', t => { + npm.flatOptions.parseable = true + t.teardown(() => { + npm.flatOptions.parseable = false + hookArgs = null + output.length = 0 + }) + + return hook(['ls'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + ...npm.flatOptions, + package: undefined, + }, 'received the correct arguments') + t.strictSame(output.map(line => line.split(/\t/)), [ + ['id', 'name', 'type', 'endpoint', 'last_delivery'], + ['1', 'semver', 'package', 'https://google.com', ''], + ['2', 'npmcli', 'scope', 'https://google.com', `${now}`], + ['3', 'npm', 'owner', 'https://google.com', ''], + ], 'prints the correct result') + t.end() + }) +}) + +test('npm hook ls - silent output', t => { + npm.flatOptions.silent = true + t.teardown(() => { + npm.flatOptions.silent = false + hookArgs = null + output.length = 0 + }) + + return hook(['ls'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + ...npm.flatOptions, + package: undefined, + }, 'received the correct arguments') + t.strictSame(output, [], 'printed no output') + t.end() + }) +}) + +test('npm hook rm', t => { + t.teardown(() => { + hookArgs = null + output.length = 0 + }) + + return hook(['rm', '1'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + id: '1', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(output, [ + '- semver X https://google.com', + ], 'printed the correct output') + t.end() + }) +}) + +test('npm hook rm - unicode output', t => { + npm.flatOptions.unicode = true + t.teardown(() => { + npm.flatOptions.unicode = false + hookArgs = null + output.length = 0 + }) + + return hook(['rm', '1'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + id: '1', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(output, [ + '- semver ✘ https://google.com', + ], 'printed the correct output') + t.end() + }) +}) + +test('npm hook rm - silent output', t => { + npm.flatOptions.silent = true + t.teardown(() => { + npm.flatOptions.silent = false + hookArgs = null + output.length = 0 + }) + + return hook(['rm', '1'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + id: '1', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(output, [], 'printed no output') + t.end() + }) +}) + +test('npm hook rm - json output', t => { + npm.flatOptions.json = true + t.teardown(() => { + npm.flatOptions.json = false + hookArgs = null + output.length = 0 + }) + + return hook(['rm', '1'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + id: '1', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(JSON.parse(output[0]), { + id: 1, + name: 'semver', + type: 'package', + endpoint: 'https://google.com', + }, 'printed correct output') + t.end() + }) +}) + +test('npm hook rm - parseable output', t => { + npm.flatOptions.parseable = true + t.teardown(() => { + npm.flatOptions.parseable = false + hookArgs = null + output.length = 0 + }) + + return hook(['rm', '1'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + id: '1', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(output.map(line => line.split(/\t/)), [ + ['id', 'name', 'type', 'endpoint'], + ['1', 'semver', 'package', 'https://google.com'], + ], 'printed correct output') + t.end() + }) +}) + +test('npm hook update', t => { + t.teardown(() => { + hookArgs = null + output.length = 0 + }) + + return hook(['update', '1', 'https://google.com', 'some-secret'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + id: '1', + uri: 'https://google.com', + secret: 'some-secret', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(output, [ + '+ semver -> https://google.com', + ], 'printed the correct output') + t.end() + }) +}) + +test('npm hook update - unicode', t => { + npm.flatOptions.unicode = true + t.teardown(() => { + npm.flatOptions.unicode = false + hookArgs = null + output.length = 0 + }) + + return hook(['update', '1', 'https://google.com', 'some-secret'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + id: '1', + uri: 'https://google.com', + secret: 'some-secret', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(output, [ + '+ semver ➜ https://google.com', + ], 'printed the correct output') + t.end() + }) +}) + +test('npm hook update - json output', t => { + npm.flatOptions.json = true + t.teardown(() => { + npm.flatOptions.json = false + hookArgs = null + output.length = 0 + }) + + return hook(['update', '1', 'https://google.com', 'some-secret'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + id: '1', + uri: 'https://google.com', + secret: 'some-secret', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(JSON.parse(output[0]), { + id: '1', + name: 'semver', + type: 'package', + endpoint: 'https://google.com', + }, 'printed the correct output') + t.end() + }) +}) + +test('npm hook update - parseable output', t => { + npm.flatOptions.parseable = true + t.teardown(() => { + npm.flatOptions.parseable = false + hookArgs = null + output.length = 0 + }) + + return hook(['update', '1', 'https://google.com', 'some-secret'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + id: '1', + uri: 'https://google.com', + secret: 'some-secret', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(output.map(line => line.split(/\t/)), [ + ['id', 'name', 'type', 'endpoint'], + ['1', 'semver', 'package', 'https://google.com'], + ], 'printed the correct output') + t.end() + }) +}) + +test('npm hook update - silent output', t => { + npm.flatOptions.silent = true + t.teardown(() => { + npm.flatOptions.silent = false + hookArgs = null + output.length = 0 + }) + + return hook(['update', '1', 'https://google.com', 'some-secret'], (err) => { + if (err) + throw err + + t.strictSame(hookArgs, { + id: '1', + uri: 'https://google.com', + secret: 'some-secret', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(output, [], 'printed no output') + t.end() + }) +}) diff --git a/deps/npm/test/lib/org.js b/deps/npm/test/lib/org.js new file mode 100644 index 0000000000..68e3c9f0d6 --- /dev/null +++ b/deps/npm/test/lib/org.js @@ -0,0 +1,585 @@ +const { test } = require('tap') +const requireInject = require('require-inject') +const ansiTrim = require('../../lib/utils/ansi-trim.js') + +const npm = { + flatOptions: { + json: false, + parseable: false, + silent: false, + loglevel: 'info', + }, +} + +const output = [] + +let orgSize = 1 +let orgSetArgs = null +let orgRmArgs = null +let orgLsArgs = null +let orgList = {} +const libnpmorg = { + set: async (org, user, role, opts) => { + orgSetArgs = { org, user, role, opts } + return { + org: { + name: org, + size: orgSize, + }, + user, + role, + } + }, + rm: async (org, user, opts) => { + orgRmArgs = { org, user, opts } + }, + ls: async (org, opts) => { + orgLsArgs = { org, opts } + return orgList + }, +} + +const org = requireInject('../../lib/org.js', { + '../../lib/npm.js': npm, + '../../lib/utils/otplease.js': async (opts, fn) => fn(opts), + '../../lib/utils/output.js': (msg) => { + output.push(msg) + }, + libnpmorg, +}) + +test('completion', async t => { + const completion = (argv) => new Promise((resolve, reject) => { + org.completion({ conf: { argv: { remain: argv } } }, (err, res) => { + if (err) + return reject(err) + return resolve(res) + }) + }) + + const assertions = [ + [['npm', 'org'], ['set', 'rm', 'ls']], + [['npm', 'org', 'ls'], []], + [['npm', 'org', 'add'], []], + [['npm', 'org', 'rm'], []], + [['npm', 'org', 'set'], []], + ] + + for (const [argv, expected] of assertions) + t.strictSame(await completion(argv), expected, `completion for: ${argv.join(', ')}`) + + t.rejects(completion(['npm', 'org', 'flurb']), /flurb not recognized/, 'errors for unknown subcommand') +}) + +test('npm org - invalid subcommand', t => { + return org(['foo'], (err) => { + t.match(err, /npm org set/, 'prints usage information') + t.end() + }) +}) + +test('npm org add', t => { + t.teardown(() => { + orgSetArgs = null + output.length = 0 + }) + + return org(['add', 'orgname', 'username'], (err) => { + if (err) + throw err + + t.strictSame(orgSetArgs, { + org: 'orgname', + user: 'username', + role: 'developer', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.equal(output[0], 'Added username as developer to orgname. You now have 1 member in this org.', 'printed the correct output') + t.end() + }) +}) + +test('npm org add - no org', t => { + t.teardown(() => { + orgSetArgs = null + output.length = 0 + }) + + return org(['add', '', 'username'], (err) => { + t.match(err, /`orgname` is required/, 'returns the correct error') + t.end() + }) +}) + +test('npm org add - no user', t => { + t.teardown(() => { + orgSetArgs = null + output.length = 0 + }) + + return org(['add', 'orgname', ''], (err) => { + t.match(err, /`username` is required/, 'returns the correct error') + t.end() + }) +}) + +test('npm org add - invalid role', t => { + t.teardown(() => { + orgSetArgs = null + output.length = 0 + }) + + return org(['add', 'orgname', 'username', 'person'], (err) => { + t.match(err, /`role` must be one of/, 'returns the correct error') + t.end() + }) +}) + +test('npm org add - more users', t => { + orgSize = 5 + t.teardown(() => { + orgSize = 1 + orgSetArgs = null + output.length = 0 + }) + + return org(['add', 'orgname', 'username'], (err) => { + if (err) + throw err + + t.strictSame(orgSetArgs, { + org: 'orgname', + user: 'username', + role: 'developer', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.equal(output[0], 'Added username as developer to orgname. You now have 5 members in this org.', 'printed the correct output') + t.end() + }) +}) + +test('npm org add - json output', t => { + npm.flatOptions.json = true + t.teardown(() => { + npm.flatOptions.json = false + orgSetArgs = null + output.length = 0 + }) + + return org(['add', 'orgname', 'username'], (err) => { + if (err) + throw err + + t.strictSame(orgSetArgs, { + org: 'orgname', + user: 'username', + role: 'developer', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(JSON.parse(output[0]), { + org: { + name: 'orgname', + size: 1, + }, + user: 'username', + role: 'developer', + }, 'printed the correct output') + t.end() + }) +}) + +test('npm org add - parseable output', t => { + npm.flatOptions.parseable = true + t.teardown(() => { + npm.flatOptions.parseable = false + orgSetArgs = null + output.length = 0 + }) + + return org(['add', 'orgname', 'username'], (err) => { + if (err) + throw err + + t.strictSame(orgSetArgs, { + org: 'orgname', + user: 'username', + role: 'developer', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(output.map(line => line.split(/\t/)), [ + ['org', 'orgsize', 'user', 'role'], + ['orgname', '1', 'username', 'developer'], + ], 'printed the correct output') + t.end() + }) +}) + +test('npm org add - silent output', t => { + npm.flatOptions.silent = true + t.teardown(() => { + npm.flatOptions.silent = false + orgSetArgs = null + output.length = 0 + }) + + return org(['add', 'orgname', 'username'], (err) => { + if (err) + throw err + + t.strictSame(orgSetArgs, { + org: 'orgname', + user: 'username', + role: 'developer', + opts: npm.flatOptions, + }, 'received the correct arguments') + t.strictSame(output, [], 'prints no output') + t.end() + }) +}) + +test('npm org rm', t => { + t.teardown(() => { + orgRmArgs = null + orgLsArgs = null + output.length = 0 + }) + + return org(['rm', 'orgname', 'username'], (err) => { + if (err) + throw err + + t.strictSame(orgRmArgs, { + org: 'orgname', + user: 'username', + opts: npm.flatOptions, + }, 'libnpmorg.rm received the correct args') + t.strictSame(orgLsArgs, { + org: 'orgname', + opts: npm.flatOptions, + }, 'libnpmorg.ls received the correct args') + t.equal(output[0], 'Successfully removed username from orgname. You now have 0 members in this org.', 'printed the correct output') + t.end() + }) +}) + +test('npm org rm - no org', t => { + t.teardown(() => { + orgRmArgs = null + orgLsArgs = null + output.length = 0 + }) + + return org(['rm', '', 'username'], (err) => { + t.match(err, /`orgname` is required/, 'threw the correct error') + t.end() + }) +}) + +test('npm org rm - no user', t => { + t.teardown(() => { + orgRmArgs = null + orgLsArgs = null + output.length = 0 + }) + + return org(['rm', 'orgname'], (err) => { + t.match(err, /`username` is required/, 'threw the correct error') + t.end() + }) +}) + +test('npm org rm - one user left', t => { + orgList = { + one: 'developer', + } + + t.teardown(() => { + orgList = {} + orgRmArgs = null + orgLsArgs = null + output.length = 0 + }) + + return org(['rm', 'orgname', 'username'], (err) => { + if (err) + throw err + + t.strictSame(orgRmArgs, { + org: 'orgname', + user: 'username', + opts: npm.flatOptions, + }, 'libnpmorg.rm received the correct args') + t.strictSame(orgLsArgs, { + org: 'orgname', + opts: npm.flatOptions, + }, 'libnpmorg.ls received the correct args') + t.equal(output[0], 'Successfully removed username from orgname. You now have 1 member in this org.', 'printed the correct output') + t.end() + }) +}) + +test('npm org rm - json output', t => { + npm.flatOptions.json = true + t.teardown(() => { + npm.flatOptions.json = false + orgRmArgs = null + orgLsArgs = null + output.length = 0 + }) + + return org(['rm', 'orgname', 'username'], (err) => { + if (err) + throw err + + t.strictSame(orgRmArgs, { + org: 'orgname', + user: 'username', + opts: npm.flatOptions, + }, 'libnpmorg.rm received the correct args') + t.strictSame(orgLsArgs, { + org: 'orgname', + opts: npm.flatOptions, + }, 'libnpmorg.ls received the correct args') + t.strictSame(JSON.parse(output[0]), { + user: 'username', + org: 'orgname', + userCount: 0, + deleted: true, + }, 'printed the correct output') + t.end() + }) +}) + +test('npm org rm - parseable output', t => { + npm.flatOptions.parseable = true + t.teardown(() => { + npm.flatOptions.parseable = false + orgRmArgs = null + orgLsArgs = null + output.length = 0 + }) + + return org(['rm', 'orgname', 'username'], (err) => { + if (err) + throw err + + t.strictSame(orgRmArgs, { + org: 'orgname', + user: 'username', + opts: npm.flatOptions, + }, 'libnpmorg.rm received the correct args') + t.strictSame(orgLsArgs, { + org: 'orgname', + opts: npm.flatOptions, + }, 'libnpmorg.ls received the correct args') + t.strictSame(output.map(line => line.split(/\t/)), [ + ['user', 'org', 'userCount', 'deleted'], + ['username', 'orgname', '0', 'true'], + ], 'printed the correct output') + t.end() + }) +}) + +test('npm org rm - silent output', t => { + npm.flatOptions.silent = true + t.teardown(() => { + npm.flatOptions.silent = false + orgRmArgs = null + orgLsArgs = null + output.length = 0 + }) + + return org(['rm', 'orgname', 'username'], (err) => { + if (err) + throw err + + t.strictSame(orgRmArgs, { + org: 'orgname', + user: 'username', + opts: npm.flatOptions, + }, 'libnpmorg.rm received the correct args') + t.strictSame(orgLsArgs, { + org: 'orgname', + opts: npm.flatOptions, + }, 'libnpmorg.ls received the correct args') + t.strictSame(output, [], 'printed no output') + t.end() + }) +}) + +test('npm org ls', t => { + orgList = { + one: 'developer', + two: 'admin', + three: 'owner', + } + t.teardown(() => { + orgList = {} + orgLsArgs = null + output.length = 0 + }) + + return org(['ls', 'orgname'], (err) => { + if (err) + throw err + + t.strictSame(orgLsArgs, { + org: 'orgname', + opts: npm.flatOptions, + }, 'receieved the correct args') + const out = ansiTrim(output[0]) + t.match(out, /one.*developer/, 'contains the developer member') + t.match(out, /two.*admin/, 'contains the admin member') + t.match(out, /three.*owner/, 'contains the owner member') + t.end() + }) +}) + +test('npm org ls - user filter', t => { + orgList = { + username: 'admin', + missing: 'admin', + } + t.teardown(() => { + orgList = {} + orgLsArgs = null + output.length = 0 + }) + + return org(['ls', 'orgname', 'username'], (err) => { + if (err) + throw err + + t.strictSame(orgLsArgs, { + org: 'orgname', + opts: npm.flatOptions, + }, 'receieved the correct args') + const out = ansiTrim(output[0]) + t.match(out, /username.*admin/, 'contains the filtered member') + t.notMatch(out, /missing.*admin/, 'does not contain other members') + t.end() + }) +}) + +test('npm org ls - user filter, missing user', t => { + orgList = { + missing: 'admin', + } + t.teardown(() => { + orgList = {} + orgLsArgs = null + output.length = 0 + }) + + return org(['ls', 'orgname', 'username'], (err) => { + if (err) + throw err + + t.strictSame(orgLsArgs, { + org: 'orgname', + opts: npm.flatOptions, + }, 'receieved the correct args') + const out = ansiTrim(output[0]) + t.notMatch(out, /username/, 'does not contain the requested member') + t.notMatch(out, /missing.*admin/, 'does not contain other members') + t.end() + }) +}) + +test('npm org ls - no org', t => { + t.teardown(() => { + orgLsArgs = null + output.length = 0 + }) + + return org(['ls'], (err) => { + t.match(err, /`orgname` is required/, 'throws the correct error') + t.end() + }) +}) + +test('npm org ls - json output', t => { + npm.flatOptions.json = true + orgList = { + one: 'developer', + two: 'admin', + three: 'owner', + } + t.teardown(() => { + npm.flatOptions.json = false + orgList = {} + orgLsArgs = null + output.length = 0 + }) + + return org(['ls', 'orgname'], (err) => { + if (err) + throw err + + t.strictSame(orgLsArgs, { + org: 'orgname', + opts: npm.flatOptions, + }, 'receieved the correct args') + t.strictSame(JSON.parse(output[0]), orgList, 'prints the correct output') + t.end() + }) +}) + +test('npm org ls - parseable output', t => { + npm.flatOptions.parseable = true + orgList = { + one: 'developer', + two: 'admin', + three: 'owner', + } + t.teardown(() => { + npm.flatOptions.parseable = false + orgList = {} + orgLsArgs = null + output.length = 0 + }) + + return org(['ls', 'orgname'], (err) => { + if (err) + throw err + + t.strictSame(orgLsArgs, { + org: 'orgname', + opts: npm.flatOptions, + }, 'receieved the correct args') + t.strictSame(output.map(line => line.split(/\t/)), [ + ['user', 'role'], + ['one', 'developer'], + ['two', 'admin'], + ['three', 'owner'], + ], 'printed the correct output') + t.end() + }) +}) + +test('npm org ls - silent output', t => { + npm.flatOptions.silent = true + orgList = { + one: 'developer', + two: 'admin', + three: 'owner', + } + t.teardown(() => { + npm.flatOptions.silent = false + orgList = {} + orgLsArgs = null + output.length = 0 + }) + + return org(['ls', 'orgname'], (err) => { + if (err) + throw err + + t.strictSame(orgLsArgs, { + org: 'orgname', + opts: npm.flatOptions, + }, 'receieved the correct args') + t.strictSame(output, [], 'printed no output') + t.end() + }) +}) diff --git a/deps/npm/test/lib/rebuild.js b/deps/npm/test/lib/rebuild.js index dbc37d57af..d9df048d90 100644 --- a/deps/npm/test/lib/rebuild.js +++ b/deps/npm/test/lib/rebuild.js @@ -174,8 +174,48 @@ t.test('filter by pkg@<range>', t => { }) }) -t.test('filter must be a semver version/range', t => { - rebuild(['b:git+ssh://github.com/npm/arborist'], err => { +t.test('filter by directory', t => { + const path = t.testdir({ + node_modules: { + a: { + 'index.js': '', + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + bin: 'index.js', + }), + }, + b: { + 'index.js': '', + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + bin: 'index.js', + }), + }, + }, + }) + + npm.prefix = path + + const aBinFile = resolve(path, 'node_modules/.bin/a') + const bBinFile = resolve(path, 'node_modules/.bin/b') + t.throws(() => fs.statSync(aBinFile)) + t.throws(() => fs.statSync(bBinFile)) + + rebuild(['file:node_modules/b'], err => { + if (err) + throw err + + t.throws(() => fs.statSync(aBinFile), 'should not link a bin') + t.ok(() => fs.statSync(bBinFile), 'should link filtered pkg bin') + + t.end() + }) +}) + +t.test('filter must be a semver version/range, or directory', t => { + rebuild(['git+ssh://github.com/npm/arborist'], err => { t.match( err, /Error: `npm rebuild` only supports SemVer version\/range specifiers/, diff --git a/deps/npm/test/lib/set.js b/deps/npm/test/lib/set.js new file mode 100644 index 0000000000..aeb239e9c4 --- /dev/null +++ b/deps/npm/test/lib/set.js @@ -0,0 +1,33 @@ +const { test } = require('tap') +const requireInject = require('require-inject') + +let configArgs = null +const npm = { + commands: { + config: (args, cb) => { + configArgs = args + return cb() + }, + }, +} + +const set = requireInject('../../lib/set.js', { + '../../lib/npm.js': npm, +}) + +test('npm set - no args', t => { + return set([], (err) => { + t.match(err, /npm set/, 'prints usage') + t.end() + }) +}) + +test('npm set', t => { + return set(['email', 'me@me.me'], (err) => { + if (err) + throw err + + t.strictSame(configArgs, ['set', 'email', 'me@me.me'], 'passed the correct arguments to config') + t.end() + }) +}) diff --git a/deps/npm/test/lib/utils/split-package-names.js b/deps/npm/test/lib/utils/split-package-names.js new file mode 100644 index 0000000000..c69bb2a3da --- /dev/null +++ b/deps/npm/test/lib/utils/split-package-names.js @@ -0,0 +1,17 @@ +'use strict' + +const { test } = require('tap') +const splitPackageNames = require('../../../lib/utils/split-package-names.js') + +test('splitPackageNames', t => { + const assertions = [ + ['semver', 'semver'], + ['read-pkg/semver', 'read-pkg/node_modules/semver'], + ['@npmcli/one/@npmcli/two', '@npmcli/one/node_modules/@npmcli/two'], + ['@npmcli/one/semver', '@npmcli/one/node_modules/semver'], + ] + + for (const [input, expected] of assertions) + t.equal(splitPackageNames(input), expected, `split ${input} correctly`) + t.end() +}) |