From b0393fa2ed90d5d4dce0f8da65d20441382e8c64 Mon Sep 17 00:00:00 2001 From: Myles Borins Date: Wed, 16 Dec 2020 14:12:26 -0500 Subject: [PATCH] deps: upgrade npm to 7.2.0 PR-URL: https://github.com/nodejs/node/pull/36543 Reviewed-By: Luigi Pinca Reviewed-By: Anna Henningsen Reviewed-By: Ruy Adorno Reviewed-By: Joyee Cheung --- deps/npm/CHANGELOG.md | 59 ++ deps/npm/docs/output/commands/npm-ls.html | 2 +- deps/npm/docs/output/commands/npm.html | 2 +- deps/npm/lib/config.js | 15 +- deps/npm/lib/edit.js | 72 +-- deps/npm/lib/help-search.js | 27 +- deps/npm/lib/help.js | 7 +- deps/npm/lib/org.js | 4 +- deps/npm/lib/rebuild.js | 13 +- deps/npm/lib/utils/child-path.js | 9 - .../lib/utils/completion/file-completion.js | 23 - deps/npm/lib/utils/deep-sort-object.js | 14 - deps/npm/lib/utils/git.js | 55 -- deps/npm/lib/utils/module-name.js | 38 -- deps/npm/lib/utils/split-package-names.js | 23 + deps/npm/man/man1/npm-ls.1 | 2 +- deps/npm/man/man1/npm.1 | 2 +- .../arborist/lib/arborist/build-ideal-tree.js | 8 +- .../@npmcli/arborist/package.json | 2 +- .../node_modules/@npmcli/config/package.json | 4 +- deps/npm/node_modules/editor/LICENSE | 21 - deps/npm/node_modules/editor/README.markdown | 54 -- .../npm/node_modules/editor/example/beep.json | 5 - deps/npm/node_modules/editor/example/edit.js | 4 - deps/npm/node_modules/editor/index.js | 20 - deps/npm/node_modules/editor/package.json | 34 - deps/npm/node_modules/ini/ini.js | 178 +++--- deps/npm/node_modules/ini/package.json | 7 +- .../node_modules/sorted-object/LICENSE.txt | 47 -- .../sorted-object/lib/sorted-object.js | 11 - .../node_modules/sorted-object/package.json | 25 - deps/npm/package.json | 14 +- deps/npm/test/lib/config.js | 44 +- deps/npm/test/lib/edit.js | 123 ++++ deps/npm/test/lib/help-search.js | 181 ++++++ deps/npm/test/lib/help.js | 362 +++++++++++ deps/npm/test/lib/hook.js | 589 ++++++++++++++++++ deps/npm/test/lib/org.js | 585 +++++++++++++++++ deps/npm/test/lib/rebuild.js | 44 +- deps/npm/test/lib/set.js | 33 + .../npm/test/lib/utils/split-package-names.js | 17 + 41 files changed, 2217 insertions(+), 562 deletions(-) delete mode 100644 deps/npm/lib/utils/child-path.js delete mode 100644 deps/npm/lib/utils/completion/file-completion.js delete mode 100644 deps/npm/lib/utils/deep-sort-object.js delete mode 100644 deps/npm/lib/utils/git.js delete mode 100644 deps/npm/lib/utils/module-name.js create mode 100644 deps/npm/lib/utils/split-package-names.js delete mode 100644 deps/npm/node_modules/editor/LICENSE delete mode 100644 deps/npm/node_modules/editor/README.markdown delete mode 100644 deps/npm/node_modules/editor/example/beep.json delete mode 100644 deps/npm/node_modules/editor/example/edit.js delete mode 100644 deps/npm/node_modules/editor/index.js delete mode 100644 deps/npm/node_modules/editor/package.json delete mode 100644 deps/npm/node_modules/sorted-object/LICENSE.txt delete mode 100644 deps/npm/node_modules/sorted-object/lib/sorted-object.js delete mode 100644 deps/npm/node_modules/sorted-object/package.json create mode 100644 deps/npm/test/lib/edit.js create mode 100644 deps/npm/test/lib/help-search.js create mode 100644 deps/npm/test/lib/help.js create mode 100644 deps/npm/test/lib/hook.js create mode 100644 deps/npm/test/lib/org.js create mode 100644 deps/npm/test/lib/set.js create mode 100644 deps/npm/test/lib/utils/split-package-names.js diff --git a/deps/npm/CHANGELOG.md b/deps/npm/CHANGELOG.md index 0922176c6bacd3..923c565ab31fc9 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 3963629acc90b9..125bbb61939644 100644 --- a/deps/npm/docs/output/commands/npm-ls.html +++ b/deps/npm/docs/output/commands/npm-ls.html @@ -159,7 +159,7 @@

Description

the results to only the paths to the packages named. Note that nested packages will also show the paths to the specified packages. For example, running npm ls promzard in npm’s source tree will show:

-
npm@7.1.2 /path/to/npm
+
npm@7.2.0 /path/to/npm
 └─┬ init-package-json@0.0.4
   └── promzard@0.1.5
 
diff --git a/deps/npm/docs/output/commands/npm.html b/deps/npm/docs/output/commands/npm.html index 92ed591092a0a1..03c784976123df 100644 --- a/deps/npm/docs/output/commands/npm.html +++ b/deps/npm/docs/output/commands/npm.html @@ -148,7 +148,7 @@

Table of contents

npm <command> [args]
 

Version

-

7.1.2

+

7.2.0

Description

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 ad8db4ff534964..561c31e0b3ec54 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 43f86dfe70ed6b..9ae6349262c2d9 100644 --- a/deps/npm/lib/edit.js +++ b/deps/npm/lib/edit.js @@ -1,52 +1,36 @@ // npm edit // open the package folder in the $EDITOR -module.exports = edit -edit.usage = 'npm edit [/...]' +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 [/...]') +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 ae4302e8cc2f46..c1814b4e53fc3e 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 5f9563e1cfe062..171c52704df6c7 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 d430131f837506..b7af3f3a3022a0 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 e02c89bd79f28a..ab34b7f3dfb518 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 38b9df13802670..00000000000000 --- 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 b32eda52df93d1..00000000000000 --- 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 56f1854f1e2f65..00000000000000 --- 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 299302b277eea1..00000000000000 --- 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 c58050e55e574e..00000000000000 --- 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 00000000000000..bb6e449bac243e --- /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 0f486c67cc4bca..9db81f72900f42 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 59655ec71e8bac..e18720c1ed51de 100644 --- a/deps/npm/man/man1/npm.1 +++ b/deps/npm/man/man1/npm.1 @@ -10,7 +10,7 @@ npm [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 731b78518775ce..c1f18af7e43dc7 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 2f3ccb6131982e..b8b27c29fdf3c2 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 f8334ab51dbb58..26581f385c386a 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 8b856bc4a47f14..00000000000000 --- 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 c1121cad2f9d26..00000000000000 --- 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 ac07d2da8703aa..00000000000000 --- 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 ee117287107050..00000000000000 --- 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 3774edbe37b74e..00000000000000 --- 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 d956a680247ccf..00000000000000 --- 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 b576f08d7a6bbb..7d05a719b066c7 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 c830a3556ef824..59b7d5d0ad99b5 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 (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 2edd064bf5507a..00000000000000 --- 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 - -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 - - 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 - -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 1b3fe81a6be930..00000000000000 --- 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 2e81f36d6efc75..00000000000000 --- 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 (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 89cb0e6aad7f00..8e2917c5edca2a 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 8a11a40c813370..74cd530c68270e 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 00000000000000..0d3bbb4c57e71e --- /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 00000000000000..5ecf5db0614ac6 --- /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 00000000000000..17018acc61620e --- /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 ', + }, + }, + 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 '], '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 00000000000000..3599042021f38c --- /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 00000000000000..68e3c9f0d6c8b8 --- /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 dbc37d57af5665..d9df048d9057ef 100644 --- a/deps/npm/test/lib/rebuild.js +++ b/deps/npm/test/lib/rebuild.js @@ -174,8 +174,48 @@ t.test('filter by pkg@', 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 00000000000000..aeb239e9c4e39c --- /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 00000000000000..c69bb2a3dab8cb --- /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() +})