diff --git a/lib/config.js b/lib/config.js index e4da296de8f88..54462ee712b78 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,4 +1,3 @@ -const npm = require('./npm.js') const { defaults, types } = require('./utils/config.js') const usageUtil = require('./utils/usage.js') const output = require('./utils/output.js') @@ -13,165 +12,156 @@ const { spawn } = require('child_process') const { EOL } = require('os') const ini = require('ini') -const usage = usageUtil( - 'config', - 'npm config set = [= ...]' + - '\nnpm config get [ [ ...]]' + - '\nnpm config delete [ ...]' + - '\nnpm config list [--json]' + - '\nnpm config edit' + - '\nnpm set = [= ...]' + - '\nnpm get [ [ ...]]' -) - -const cmd = (args, cb) => config(args).then(() => cb()).catch(cb) - -const completion = async (opts) => { - const argv = opts.conf.argv.remain - if (argv[1] !== 'config') - argv.unshift('config') - - if (argv.length === 2) { - const cmds = ['get', 'set', 'delete', 'ls', 'rm', 'edit'] - if (opts.partialWord !== 'l') - cmds.push('list') - - return cmds +class Config { + constructor (npm) { + this.npm = npm } - const action = argv[2] - switch (action) { - case 'set': - // todo: complete with valid values, if possible. - if (argv.length > 3) - return [] - - // fallthrough - /* eslint no-fallthrough:0 */ - case 'get': - case 'delete': - case 'rm': - return Object.keys(types) - case 'edit': - case 'list': - case 'ls': - default: - return [] + get usage () { + return usageUtil( + 'config', + 'npm config set = [= ...]' + + '\nnpm config get [ [ ...]]' + + '\nnpm config delete [ ...]' + + '\nnpm config list [--json]' + + '\nnpm config edit' + + '\nnpm set = [= ...]' + + '\nnpm get [ [ ...]]' + ) } -} -const UsageError = () => - Object.assign(new Error(usage), { code: 'EUSAGE' }) + async completion (opts) { + const argv = opts.conf.argv.remain + if (argv[1] !== 'config') + argv.unshift('config') -const config = async ([action, ...args]) => { - npm.log.disableProgress() - try { + if (argv.length === 2) { + const cmds = ['get', 'set', 'delete', 'ls', 'rm', 'edit'] + if (opts.partialWord !== 'l') + cmds.push('list') + + return cmds + } + + const action = argv[2] switch (action) { case 'set': - await set(args) - break + // todo: complete with valid values, if possible. + if (argv.length > 3) + return [] + + // fallthrough + /* eslint no-fallthrough:0 */ case 'get': - await get(args) - break case 'delete': case 'rm': - case 'del': - await del(args) - break + return Object.keys(types) + case 'edit': case 'list': case 'ls': - await (npm.flatOptions.json ? listJson() : list()) - break - case 'edit': - await edit() - break default: - throw UsageError() + return [] } - } finally { - npm.log.enableProgress() } -} -// take an array of `[key, value, k2=v2, k3, v3, ...]` and turn into -// { key: value, k2: v2, k3: v3 } -const keyValues = args => { - const kv = {} - for (let i = 0; i < args.length; i++) { - const arg = args[i].split('=') - const key = arg.shift() - const val = arg.length ? arg.join('=') - : i < args.length - 1 ? args[++i] - : '' - kv[key.trim()] = val.trim() + exec (args, cb) { + this.config(args).then(() => cb()).catch(cb) } - return kv -} - -const set = async (args) => { - if (!args.length) - throw UsageError() - const where = npm.flatOptions.global ? 'global' : 'user' - for (const [key, val] of Object.entries(keyValues(args))) { - npm.log.info('config', 'set %j %j', key, val) - npm.config.set(key, val || '', where) - if (!npm.config.validate(where)) - npm.log.warn('config', 'omitting invalid config values') + async config ([action, ...args]) { + this.npm.log.disableProgress() + try { + switch (action) { + case 'set': + await this.set(args) + break + case 'get': + await this.get(args) + break + case 'delete': + case 'rm': + case 'del': + await this.del(args) + break + case 'list': + case 'ls': + await (this.npm.flatOptions.json ? this.listJson() : this.list()) + break + case 'edit': + await this.edit() + break + default: + throw this.usageError() + } + } finally { + this.npm.log.enableProgress() + } } - await npm.config.save(where) -} + async set (args) { + if (!args.length) + throw this.usageError() + + const where = this.npm.flatOptions.global ? 'global' : 'user' + for (const [key, val] of Object.entries(keyValues(args))) { + this.npm.log.info('config', 'set %j %j', key, val) + this.npm.config.set(key, val || '', where) + if (!this.npm.config.validate(where)) + this.npm.log.warn('config', 'omitting invalid config values') + } + + await this.npm.config.save(where) + } -const get = async keys => { - if (!keys.length) - return list() + async get (keys) { + if (!keys.length) + return this.list() - const out = [] - for (const key of keys) { - if (!publicVar(key)) - throw `The ${key} option is protected, and cannot be retrieved in this way` + const out = [] + for (const key of keys) { + if (!publicVar(key)) + throw `The ${key} option is protected, and cannot be retrieved in this way` - const pref = keys.length > 1 ? `${key}=` : '' - out.push(pref + npm.config.get(key)) + const pref = keys.length > 1 ? `${key}=` : '' + out.push(pref + this.npm.config.get(key)) + } + output(out.join('\n')) } - output(out.join('\n')) -} -const del = async keys => { - if (!keys.length) - throw UsageError() + async del (keys) { + if (!keys.length) + throw this.usageError() - const where = npm.flatOptions.global ? 'global' : 'user' - for (const key of keys) - npm.config.delete(key, where) - await npm.config.save(where) -} + const where = this.npm.flatOptions.global ? 'global' : 'user' + for (const key of keys) + this.npm.config.delete(key, where) + await this.npm.config.save(where) + } -const edit = async () => { - const { editor: e, global } = npm.flatOptions - const where = global ? 'global' : 'user' - const file = npm.config.data.get(where).source - - // save first, just to make sure it's synced up - // this also removes all the comments from the last time we edited it. - await npm.config.save(where) - - const data = ( - await readFile(file, 'utf8').catch(() => '') - ).replace(/\r\n/g, '\n') - const defData = Object.entries(defaults).reduce((str, [key, val]) => { - const obj = { [key]: val } - const i = ini.stringify(obj) - .replace(/\r\n/g, '\n') // normalizes output from ini.stringify - .replace(/\n$/m, '') - .replace(/^/g, '; ') - .replace(/\n/g, '\n; ') - .split('\n') - return str + '\n' + i - }, '') - - const tmpData = `;;;; + async edit () { + const { editor: e, global } = this.npm.flatOptions + const where = global ? 'global' : 'user' + const file = this.npm.config.data.get(where).source + + // save first, just to make sure it's synced up + // this also removes all the comments from the last time we edited it. + await this.npm.config.save(where) + + const data = ( + await readFile(file, 'utf8').catch(() => '') + ).replace(/\r\n/g, '\n') + const defData = Object.entries(defaults).reduce((str, [key, val]) => { + const obj = { [key]: val } + const i = ini.stringify(obj) + .replace(/\r\n/g, '\n') // normalizes output from ini.stringify + .replace(/\n$/m, '') + .replace(/^/g, '; ') + .replace(/\n/g, '\n; ') + .split('\n') + return str + '\n' + i + }, '') + + const tmpData = `;;;; ; npm ${where}config file: ${file} ; this is a simple ini-formatted file ; lines that start with semi-colons are comments @@ -190,64 +180,84 @@ ${data.split('\n').sort((a, b) => a.localeCompare(b)).join('\n').trim()} ${defData} `.split('\n').join(EOL) - await mkdirp(dirname(file)) - await writeFile(file, tmpData, 'utf8') - 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() + await mkdirp(dirname(file)) + await writeFile(file, tmpData, 'utf8') + 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) + } -const list = async () => { - const msg = [] - const { long } = npm.flatOptions - for (const [where, { data, source }] of npm.config.data.entries()) { - if (where === 'default' && !long) - continue + async list () { + const msg = [] + const { long } = this.npm.flatOptions + for (const [where, { data, source }] of this.npm.config.data.entries()) { + if (where === 'default' && !long) + continue + + const keys = Object.keys(data).sort((a, b) => a.localeCompare(b)) + if (!keys.length) + continue + + msg.push(`; "${where}" config from ${source}`, '') + for (const k of keys) { + const v = publicVar(k) ? JSON.stringify(data[k]) : '(protected)' + const src = this.npm.config.find(k) + const overridden = src !== where + msg.push((overridden ? '; ' : '') + + `${k} = ${v} ${overridden ? `; overridden by ${src}` : ''}`) + } + msg.push('') + } - const keys = Object.keys(data).sort((a, b) => a.localeCompare(b)) - if (!keys.length) - continue - - msg.push(`; "${where}" config from ${source}`, '') - for (const k of keys) { - const v = publicVar(k) ? JSON.stringify(data[k]) : '(protected)' - const src = npm.config.find(k) - const overridden = src !== where - msg.push((overridden ? '; ' : '') + - `${k} = ${v} ${overridden ? `; overridden by ${src}` : ''}`) + if (!long) { + msg.push( + `; node bin location = ${process.execPath}`, + `; cwd = ${process.cwd()}`, + `; HOME = ${process.env.HOME}`, + '; Run `npm config ls -l` to show all defaults.' + ) } - msg.push('') + + output(msg.join('\n').trim()) } - if (!long) { - msg.push( - `; node bin location = ${process.execPath}`, - `; cwd = ${process.cwd()}`, - `; HOME = ${process.env.HOME}`, - '; Run `npm config ls -l` to show all defaults.' - ) + async listJson () { + const publicConf = {} + for (const key in this.npm.config.list[0]) { + if (!publicVar(key)) + continue + + publicConf[key] = this.npm.config.get(key) + } + output(JSON.stringify(publicConf, null, 2)) } - output(msg.join('\n').trim()) + usageError () { + return Object.assign(new Error(this.usage), { code: 'EUSAGE' }) + } } -const listJson = async () => { - const publicConf = {} - for (const key in npm.config.list[0]) { - if (!publicVar(key)) - continue - - publicConf[key] = npm.config.get(key) +// take an array of `[key, value, k2=v2, k3, v3, ...]` and turn into +// { key: value, k2: v2, k3: v3 } +const keyValues = args => { + const kv = {} + for (let i = 0; i < args.length; i++) { + const arg = args[i].split('=') + const key = arg.shift() + const val = arg.length ? arg.join('=') + : i < args.length - 1 ? args[++i] + : '' + kv[key.trim()] = val.trim() } - output(JSON.stringify(publicConf, null, 2)) + return kv } -module.exports = Object.assign(cmd, { usage, completion }) +const publicVar = k => !/^(\/\/[^:]+:)?_/.test(k) + +module.exports = Config diff --git a/lib/dedupe.js b/lib/dedupe.js index 2211fcac8b481..f4abf48926b86 100644 --- a/lib/dedupe.js +++ b/lib/dedupe.js @@ -1,29 +1,38 @@ // dedupe duplicated packages, or find them in the tree -const npm = require('./npm.js') const Arborist = require('@npmcli/arborist') const usageUtil = require('./utils/usage.js') const reifyFinish = require('./utils/reify-finish.js') -const usage = usageUtil('dedupe', 'npm dedupe') +class Dedupe { + constructor (npm) { + this.npm = npm + } -const cmd = (args, cb) => dedupe(args).then(() => cb()).catch(cb) + get usage () { + return usageUtil('dedupe', 'npm dedupe') + } -const dedupe = async (args) => { - if (npm.flatOptions.global) { - const er = new Error('`npm dedupe` does not work in global mode.') - er.code = 'EDEDUPEGLOBAL' - throw er + exec (args, cb) { + this.dedupe(args).then(() => cb()).catch(cb) } - const dryRun = (args && args.dryRun) || npm.flatOptions.dryRun - const where = npm.prefix - const arb = new Arborist({ - ...npm.flatOptions, - path: where, - dryRun, - }) - await arb.dedupe(npm.flatOptions) - await reifyFinish(arb) + async dedupe (args) { + if (this.npm.flatOptions.global) { + const er = new Error('`npm dedupe` does not work in global mode.') + er.code = 'EDEDUPEGLOBAL' + throw er + } + + const dryRun = (args && args.dryRun) || this.npm.flatOptions.dryRun + const where = this.npm.prefix + const arb = new Arborist({ + ...this.npm.flatOptions, + path: where, + dryRun, + }) + await arb.dedupe(this.npm.flatOptions) + await reifyFinish(arb) + } } -module.exports = Object.assign(cmd, { usage }) +module.exports = Dedupe diff --git a/lib/deprecate.js b/lib/deprecate.js index 42d099b544e31..6de9cefe3f2d0 100644 --- a/lib/deprecate.js +++ b/lib/deprecate.js @@ -1,4 +1,3 @@ -const npm = require('./npm.js') const fetch = require('npm-registry-fetch') const otplease = require('./utils/otplease.js') const npa = require('npm-package-arg') @@ -7,67 +6,77 @@ const getIdentity = require('./utils/get-identity.js') const libaccess = require('libnpmaccess') const usageUtil = require('./utils/usage.js') -const UsageError = () => - Object.assign(new Error(`\nUsage: ${usage}`), { - code: 'EUSAGE', - }) +class Deprecate { + constructor (npm) { + this.npm = npm + } -const usage = usageUtil( - 'deprecate', - 'npm deprecate [@] ' -) + get usage () { + return usageUtil( + 'deprecate', + 'npm deprecate [@] ' + ) + } -const completion = async (opts) => { - if (opts.conf.argv.remain.length > 1) - return [] + async completion (opts) { + if (opts.conf.argv.remain.length > 1) + return [] - const username = await getIdentity(npm.flatOptions) - const packages = await libaccess.lsPackages(username, npm.flatOptions) - return Object.keys(packages) - .filter((name) => - packages[name] === 'write' && - (opts.conf.argv.remain.length === 0 || - name.startsWith(opts.conf.argv.remain[0]))) -} - -const cmd = (args, cb) => - deprecate(args) - .then(() => cb()) - .catch(err => cb(err.code === 'EUSAGE' ? err.message : err)) + const username = await getIdentity(this.npm.flatOptions) + const packages = await libaccess.lsPackages(username, this.npm.flatOptions) + return Object.keys(packages) + .filter((name) => + packages[name] === 'write' && + (opts.conf.argv.remain.length === 0 || + name.startsWith(opts.conf.argv.remain[0]))) + } -const deprecate = async ([pkg, msg]) => { - if (!pkg || !msg) - throw UsageError() + exec (args, cb) { + this.deprecate(args) + .then(() => cb()) + .catch(err => cb(err.code === 'EUSAGE' ? err.message : err)) + } - // fetch the data and make sure it exists. - const p = npa(pkg) - // npa makes the default spec "latest", but for deprecation - // "*" is the appropriate default. - const spec = p.rawSpec === '' ? '*' : p.fetchSpec + async deprecate ([pkg, msg]) { + if (!pkg || !msg) + throw this.usageError() - if (semver.validRange(spec, true) === null) - throw new Error(`invalid version range: ${spec}`) + // fetch the data and make sure it exists. + const p = npa(pkg) + // npa makes the default spec "latest", but for deprecation + // "*" is the appropriate default. + const spec = p.rawSpec === '' ? '*' : p.fetchSpec - const uri = '/' + p.escapedName - const packument = await fetch.json(uri, { - ...npm.flatOptions, - spec: p, - query: { write: true }, - }) + if (semver.validRange(spec, true) === null) + throw new Error(`invalid version range: ${spec}`) - Object.keys(packument.versions) - .filter(v => semver.satisfies(v, spec, { includePrerelease: true })) - .forEach(v => { - packument.versions[v].deprecated = msg + const uri = '/' + p.escapedName + const packument = await fetch.json(uri, { + ...this.npm.flatOptions, + spec: p, + query: { write: true }, }) - return otplease(npm.flatOptions, opts => fetch(uri, { - ...opts, - spec: p, - method: 'PUT', - body: packument, - ignoreBody: true, - })) + Object.keys(packument.versions) + .filter(v => semver.satisfies(v, spec, { includePrerelease: true })) + .forEach(v => { + packument.versions[v].deprecated = msg + }) + + return otplease(this.npm.flatOptions, opts => fetch(uri, { + ...opts, + spec: p, + method: 'PUT', + body: packument, + ignoreBody: true, + })) + } + + usageError () { + return Object.assign(new Error(`\nUsage: ${this.usage}`), { + code: 'EUSAGE', + }) + } } -module.exports = Object.assign(cmd, { completion, usage }) +module.exports = Deprecate diff --git a/lib/diff.js b/lib/diff.js index 9ef5a78a20ce9..ea0340a4909d2 100644 --- a/lib/diff.js +++ b/lib/diff.js @@ -8,258 +8,270 @@ const npmlog = require('npmlog') const pacote = require('pacote') const pickManifest = require('npm-pick-manifest') -const npm = require('./npm.js') const usageUtil = require('./utils/usage.js') const output = require('./utils/output.js') const readLocalPkg = require('./utils/read-local-package.js') -const usage = usageUtil( - 'diff', - 'npm diff [...]' + - '\nnpm diff --diff= [...]' + - '\nnpm diff --diff= [--diff=] [...]' + - '\nnpm diff --diff= [--diff=] [...]' + - '\nnpm diff [--diff-ignore-all-space] [--diff-name-only] [...] [...]' -) - -const cmd = (args, cb) => diff(args).then(() => cb()).catch(cb) - -const where = () => { - const globalTop = resolve(npm.globalDir, '..') - const { global } = npm.flatOptions - return global ? globalTop : npm.prefix -} +class Diff { + constructor (npm) { + this.npm = npm + } -const diff = async (args) => { - const specs = npm.flatOptions.diff.filter(d => d) - if (specs.length > 2) { - throw new TypeError( - 'Can\'t use more than two --diff arguments.\n\n' + - `Usage:\n${usage}` + get usage () { + return usageUtil( + 'diff', + 'npm diff [...]' + + '\nnpm diff --diff= [...]' + + '\nnpm diff --diff= [--diff=] [...]' + + '\nnpm diff --diff= [--diff=] [...]' + + '\nnpm diff [--diff-ignore-all-space] [--diff-name-only] [...] [...]' ) } - const [a, b] = await retrieveSpecs(specs) - npmlog.info('diff', { src: a, dst: b }) - - const res = await libdiff([a, b], { ...npm.flatOptions, diffFiles: args }) - return output(res) -} - -const retrieveSpecs = ([a, b]) => { - // no arguments, defaults to comparing cwd - // to its latest published registry version - if (!a) - return defaultSpec() + get where () { + const globalTop = resolve(this.npm.globalDir, '..') + const { global } = this.npm.flatOptions + return global ? globalTop : this.npm.prefix + } - // single argument, used to compare wanted versions of an - // installed dependency or to compare the cwd to a published version - if (!b) - return transformSingleSpec(a) + exec (args, cb) { + this.diff(args).then(() => cb()).catch(cb) + } - return convertVersionsToSpecs([a, b]) - .then(findVersionsByPackageName) -} + async diff (args) { + const specs = this.npm.flatOptions.diff.filter(d => d) + if (specs.length > 2) { + throw new TypeError( + 'Can\'t use more than two --diff arguments.\n\n' + + `Usage:\n${this.usage}` + ) + } -const defaultSpec = async () => { - let noPackageJson - let pkgName - try { - pkgName = await readLocalPkg() - } catch (e) { - npmlog.verbose('diff', 'could not read project dir package.json') - noPackageJson = true - } + const [a, b] = await this.retrieveSpecs(specs) + npmlog.info('diff', { src: a, dst: b }) - if (!pkgName || noPackageJson) { - throw new Error( - 'Needs multiple arguments to compare or run from a project dir.\n\n' + - `Usage:\n${usage}` + const res = await libdiff( + [a, b], + { ...this.npm.flatOptions, diffFiles: args } ) + return output(res) } - return [ - `${pkgName}@${npm.flatOptions.defaultTag}`, - `file:${npm.prefix}`, - ] -} + async retrieveSpecs ([a, b]) { + // no arguments, defaults to comparing cwd + // to its latest published registry version + if (!a) + return this.defaultSpec() -const transformSingleSpec = async (a) => { - let noPackageJson - let pkgName - try { - pkgName = await readLocalPkg() - } catch (e) { - npmlog.verbose('diff', 'could not read project dir package.json') - noPackageJson = true - } - const missingPackageJson = new Error( - 'Needs multiple arguments to compare or run from a project dir.\n\n' + - `Usage:\n${usage}` - ) + // single argument, used to compare wanted versions of an + // installed dependency or to compare the cwd to a published version + if (!b) + return this.transformSingleSpec(a) - const specSelf = () => { - if (noPackageJson) - throw missingPackageJson - - return `file:${npm.prefix}` + const specs = await this.convertVersionsToSpecs([a, b]) + return this.findVersionsByPackageName(specs) } - // using a valid semver range, that means it should just diff - // the cwd against a published version to the registry using the - // same project name and the provided semver range - if (semver.validRange(a)) { - if (!pkgName) - throw missingPackageJson + async defaultSpec () { + let noPackageJson + let pkgName + try { + pkgName = await readLocalPkg(this.npm) + } catch (e) { + npmlog.verbose('diff', 'could not read project dir package.json') + noPackageJson = true + } + + if (!pkgName || noPackageJson) { + throw new Error( + 'Needs multiple arguments to compare or run from a project dir.\n\n' + + `Usage:\n${this.usage}` + ) + } return [ - `${pkgName}@${a}`, - specSelf(), + `${pkgName}@${this.npm.flatOptions.defaultTag}`, + `file:${this.npm.prefix}`, ] } - // when using a single package name as arg and it's part of the current - // install tree, then retrieve the current installed version and compare - // it against the same value `npm outdated` would suggest you to update to - const spec = npa(a) - if (spec.registry) { - let actualTree - let node + async transformSingleSpec (a) { + let noPackageJson + let pkgName try { - const opts = { - ...npm.flatOptions, - path: where(), - } - const arb = new Arborist(opts) - actualTree = await arb.loadActual(opts) - node = actualTree && - actualTree.inventory.query('name', spec.name) - .values().next().value + pkgName = await readLocalPkg(this.npm) } catch (e) { - npmlog.verbose('diff', 'failed to load actual install tree') + npmlog.verbose('diff', 'could not read project dir package.json') + noPackageJson = true + } + const missingPackageJson = new Error( + 'Needs multiple arguments to compare or run from a project dir.\n\n' + + `Usage:\n${this.usage}` + ) + + const specSelf = () => { + if (noPackageJson) + throw missingPackageJson + + return `file:${this.npm.prefix}` } - if (!node || !node.name || !node.package || !node.package.version) { + // using a valid semver range, that means it should just diff + // the cwd against a published version to the registry using the + // same project name and the provided semver range + if (semver.validRange(a)) { + if (!pkgName) + throw missingPackageJson + return [ - `${spec.name}@${spec.fetchSpec}`, + `${pkgName}@${a}`, specSelf(), ] } - const tryRootNodeSpec = () => - (actualTree && actualTree.edgesOut.get(spec.name) || {}).spec - - const tryAnySpec = () => { - for (const edge of node.edgesIn) - return edge.spec - } + // when using a single package name as arg and it's part of the current + // install tree, then retrieve the current installed version and compare + // it against the same value `npm outdated` would suggest you to update to + const spec = npa(a) + if (spec.registry) { + let actualTree + let node + try { + const opts = { + ...this.npm.flatOptions, + path: this.where, + } + const arb = new Arborist(opts) + actualTree = await arb.loadActual(opts) + node = actualTree && + actualTree.inventory.query('name', spec.name) + .values().next().value + } catch (e) { + npmlog.verbose('diff', 'failed to load actual install tree') + } - const aSpec = `file:${node.realpath}` - - // finds what version of the package to compare against, if a exact - // version or tag was passed than it should use that, otherwise - // work from the top of the arborist tree to find the original semver - // range declared in the package that depends on the package. - let bSpec - if (spec.rawSpec) - bSpec = spec.rawSpec - else { - const bTargetVersion = - tryRootNodeSpec() - || tryAnySpec() - - // figure out what to compare against, - // follows same logic to npm outdated "Wanted" results - const packument = await pacote.packument(spec, { - ...npm.flatOptions, - preferOnline: true, - }) - bSpec = pickManifest( - packument, - bTargetVersion, - { ...npm.flatOptions } - ).version - } + if (!node || !node.name || !node.package || !node.package.version) { + return [ + `${spec.name}@${spec.fetchSpec}`, + specSelf(), + ] + } - return [ - `${spec.name}@${aSpec}`, - `${spec.name}@${bSpec}`, - ] - } else if (spec.type === 'directory') { - return [ - `file:${spec.fetchSpec}`, - specSelf(), - ] - } else { - throw new Error( - 'Spec type not supported.\n\n' + - `Usage:\n${usage}` - ) - } -} + const tryRootNodeSpec = () => + (actualTree && actualTree.edgesOut.get(spec.name) || {}).spec -const convertVersionsToSpecs = async ([a, b]) => { - const semverA = semver.validRange(a) - const semverB = semver.validRange(b) + const tryAnySpec = () => { + for (const edge of node.edgesIn) + return edge.spec + } - // both specs are semver versions, assume current project dir name - if (semverA && semverB) { - let pkgName - try { - pkgName = await readLocalPkg() - } catch (e) { - npmlog.verbose('diff', 'could not read project dir package.json') - } + const aSpec = `file:${node.realpath}` + + // finds what version of the package to compare against, if a exact + // version or tag was passed than it should use that, otherwise + // work from the top of the arborist tree to find the original semver + // range declared in the package that depends on the package. + let bSpec + if (spec.rawSpec) + bSpec = spec.rawSpec + else { + const bTargetVersion = + tryRootNodeSpec() + || tryAnySpec() + + // figure out what to compare against, + // follows same logic to npm outdated "Wanted" results + const packument = await pacote.packument(spec, { + ...this.npm.flatOptions, + preferOnline: true, + }) + bSpec = pickManifest( + packument, + bTargetVersion, + { ...this.npm.flatOptions } + ).version + } - if (!pkgName) { + return [ + `${spec.name}@${aSpec}`, + `${spec.name}@${bSpec}`, + ] + } else if (spec.type === 'directory') { + return [ + `file:${spec.fetchSpec}`, + specSelf(), + ] + } else { throw new Error( - 'Needs to be run from a project dir in order to diff two versions.\n\n' + - `Usage:\n${usage}` + 'Spec type not supported.\n\n' + + `Usage:\n${this.usage}` ) } - return [`${pkgName}@${a}`, `${pkgName}@${b}`] } - // otherwise uses the name from the other arg to - // figure out the spec.name of what to compare - if (!semverA && semverB) - return [a, `${npa(a).name}@${b}`] + async convertVersionsToSpecs ([a, b]) { + const semverA = semver.validRange(a) + const semverB = semver.validRange(b) + + // both specs are semver versions, assume current project dir name + if (semverA && semverB) { + let pkgName + try { + pkgName = await readLocalPkg(this.npm) + } catch (e) { + npmlog.verbose('diff', 'could not read project dir package.json') + } + + if (!pkgName) { + throw new Error( + 'Needs to be run from a project dir in order to diff two versions.\n\n' + + `Usage:\n${this.usage}` + ) + } + return [`${pkgName}@${a}`, `${pkgName}@${b}`] + } - if (semverA && !semverB) - return [`${npa(b).name}@${a}`, b] + // otherwise uses the name from the other arg to + // figure out the spec.name of what to compare + if (!semverA && semverB) + return [a, `${npa(a).name}@${b}`] - // no valid semver ranges used - return [a, b] -} + if (semverA && !semverB) + return [`${npa(b).name}@${a}`, b] -const findVersionsByPackageName = async (specs) => { - let actualTree - try { - const opts = { - ...npm.flatOptions, - path: where(), - } - const arb = new Arborist(opts) - actualTree = await arb.loadActual(opts) - } catch (e) { - npmlog.verbose('diff', 'failed to load actual install tree') + // no valid semver ranges used + return [a, b] } - return specs.map(i => { - const spec = npa(i) - if (spec.rawSpec) - return i + async findVersionsByPackageName (specs) { + let actualTree + try { + const opts = { + ...this.npm.flatOptions, + path: this.where, + } + const arb = new Arborist(opts) + actualTree = await arb.loadActual(opts) + } catch (e) { + npmlog.verbose('diff', 'failed to load actual install tree') + } + + return specs.map(i => { + const spec = npa(i) + if (spec.rawSpec) + return i - const node = actualTree - && actualTree.inventory.query('name', spec.name) - .values().next().value + const node = actualTree + && actualTree.inventory.query('name', spec.name) + .values().next().value - const res = !node || !node.package || !node.package.version - ? spec.fetchSpec - : `file:${node.realpath}` + const res = !node || !node.package || !node.package.version + ? spec.fetchSpec + : `file:${node.realpath}` - return `${spec.name}@${res}` - }) + return `${spec.name}@${res}` + }) + } } -module.exports = Object.assign(cmd, { usage }) +module.exports = Diff diff --git a/lib/utils/read-local-package.js b/lib/utils/read-local-package.js index 7ab130c1f31b0..c31bca994704c 100644 --- a/lib/utils/read-local-package.js +++ b/lib/utils/read-local-package.js @@ -1,8 +1,6 @@ const { resolve } = require('path') const readJson = require('read-package-json-fast') -const npm = require('../npm.js') - -async function readLocalPackageName (cb) { +async function readLocalPackageName (npm) { if (npm.flatOptions.global) return diff --git a/test/lib/config.js b/test/lib/config.js index edaa6486cdc95..c2420aefb4a00 100644 --- a/test/lib/config.js +++ b/test/lib/config.js @@ -68,17 +68,17 @@ const usageUtil = () => 'usage instructions' const mocks = { '../../lib/utils/config.js': { defaults, types }, - '../../lib/npm.js': npm, '../../lib/utils/output.js': msg => { result = msg }, '../../lib/utils/usage.js': usageUtil, } -const config = requireInject('../../lib/config.js', mocks) +const Config = requireInject('../../lib/config.js', mocks) +const config = new Config(npm) t.test('config no args', t => { - config([], (err) => { + config.exec([], (err) => { t.match(err, /usage instructions/, 'should not error out on empty locations') t.end() }) @@ -94,7 +94,7 @@ t.test('config list', t => { delete npm.config.find }) - config(['list'], (err) => { + config.exec(['list'], (err) => { t.ifError(err, 'npm config list') t.matchSnapshot(result, 'should list configs') }) @@ -120,7 +120,7 @@ t.test('config list overrides', t => { delete npm.config.find }) - config(['list'], (err) => { + config.exec(['list'], (err) => { t.ifError(err, 'npm config list') t.matchSnapshot(result, 'should list overridden configs') }) @@ -138,7 +138,7 @@ t.test('config list --long', t => { result = '' }) - config(['list'], (err) => { + config.exec(['list'], (err) => { t.ifError(err, 'npm config list --long') t.matchSnapshot(result, 'should list all configs') }) @@ -163,7 +163,7 @@ t.test('config list --json', t => { result = '' }) - config(['list'], (err) => { + config.exec(['list'], (err) => { t.ifError(err, 'npm config list --json') t.deepEqual( JSON.parse(result), @@ -179,7 +179,7 @@ t.test('config list --json', t => { }) t.test('config delete no args', t => { - config(['delete'], (err) => { + config.exec(['delete'], (err) => { t.equal( err.message, 'usage instructions', @@ -202,7 +202,7 @@ t.test('config delete key', t => { t.equal(where, 'user', 'should save user config post-delete') } - config(['delete', 'foo'], (err) => { + config.exec(['delete', 'foo'], (err) => { t.ifError(err, 'npm config delete key') }) @@ -229,7 +229,7 @@ t.test('config delete multiple key', t => { t.equal(where, 'user', 'should save user config post-delete') } - config(['delete', 'foo', 'bar'], (err) => { + config.exec(['delete', 'foo', 'bar'], (err) => { t.ifError(err, 'npm config delete keys') }) @@ -252,7 +252,7 @@ t.test('config delete key --global', t => { } flatOptions.global = true - config(['delete', 'foo'], (err) => { + config.exec(['delete', 'foo'], (err) => { t.ifError(err, 'npm config delete key --global') }) @@ -264,7 +264,7 @@ t.test('config delete key --global', t => { }) t.test('config set no args', t => { - config(['set'], (err) => { + config.exec(['set'], (err) => { t.equal( err.message, 'usage instructions', @@ -287,7 +287,7 @@ t.test('config set key', t => { t.equal(where, 'user', 'should save user config') } - config(['set', 'foo', 'bar'], (err) => { + config.exec(['set', 'foo', 'bar'], (err) => { t.ifError(err, 'npm config set key') }) @@ -310,7 +310,7 @@ t.test('config set key=val', t => { t.equal(where, 'user', 'should save user config') } - config(['set', 'foo=bar'], (err) => { + config.exec(['set', 'foo=bar'], (err) => { t.ifError(err, 'npm config set key') }) @@ -341,7 +341,7 @@ t.test('config set multiple keys', t => { t.equal(where, 'user', 'should save user config') } - config(['set', ...args], (err) => { + config.exec(['set', ...args], (err) => { t.ifError(err, 'npm config set key') }) @@ -364,7 +364,7 @@ t.test('config set key to empty value', t => { t.equal(where, 'user', 'should save user config') } - config(['set', 'foo'], (err) => { + config.exec(['set', 'foo'], (err) => { t.ifError(err, 'npm config set key to empty value') }) @@ -392,7 +392,7 @@ t.test('config set invalid key', t => { delete npm.log.warn }) - config(['set', 'foo', 'bar'], (err) => { + config.exec(['set', 'foo', 'bar'], (err) => { t.ifError(err, 'npm config set invalid key') }) }) @@ -411,7 +411,7 @@ t.test('config set key --global', t => { } flatOptions.global = true - config(['set', 'foo', 'bar'], (err) => { + config.exec(['set', 'foo', 'bar'], (err) => { t.ifError(err, 'npm config set key --global') }) @@ -432,7 +432,7 @@ t.test('config get no args', t => { delete npm.config.find }) - config(['get'], (err) => { + config.exec(['get'], (err) => { t.ifError(err, 'npm config get no args') t.matchSnapshot(result, 'should list configs on config get no args') }) @@ -451,7 +451,7 @@ t.test('config get key', t => { throw new Error('should not save') } - config(['get', 'foo'], (err) => { + config.exec(['get', 'foo'], (err) => { t.ifError(err, 'npm config get key') }) @@ -479,7 +479,7 @@ t.test('config get multiple keys', t => { throw new Error('should not save') } - config(['get', 'foo', 'bar'], (err) => { + config.exec(['get', 'foo', 'bar'], (err) => { t.ifError(err, 'npm config get multiple keys') t.equal(result, 'foo=asdf\nbar=asdf') }) @@ -492,7 +492,7 @@ t.test('config get multiple keys', t => { }) t.test('config get private key', t => { - config(['get', '//private-reg.npmjs.org/:_authThoken'], (err) => { + config.exec(['get', '//private-reg.npmjs.org/:_authThoken'], (err) => { t.match( err, /The \/\/private-reg.npmjs.org\/:_authThoken option is protected, and cannot be retrieved in this way/, @@ -538,16 +538,19 @@ sign-git-commit=true` }, }, } - const config = requireInject('../../lib/config.js', editMocks) - config(['edit'], (err) => { + const Config = requireInject('../../lib/config.js', editMocks) + const config = new Config(npm) + + config.exec(['edit'], (err) => { t.ifError(err, 'npm config edit') // test no config file result editMocks.fs.readFile = (p, e, cb) => { cb(new Error('ERR')) } - const config = requireInject('../../lib/config.js', editMocks) - config(['edit'], (err) => { + const Config = requireInject('../../lib/config.js', editMocks) + const config = new Config(npm) + config.exec(['edit'], (err) => { t.ifError(err, 'npm config edit') }) }) @@ -594,8 +597,9 @@ t.test('config edit --global', t => { }, }, } - const config = requireInject('../../lib/config.js', editMocks) - config(['edit'], (err) => { + const Config = requireInject('../../lib/config.js', editMocks) + const config = new Config(npm) + config.exec(['edit'], (err) => { t.match(err, /exited with code: 137/, 'propagated exit code from editor') }) diff --git a/test/lib/dedupe.js b/test/lib/dedupe.js index b14185525bbea..27cd583dd2c9a 100644 --- a/test/lib/dedupe.js +++ b/test/lib/dedupe.js @@ -2,28 +2,17 @@ const { test } = require('tap') const requireInject = require('require-inject') test('should throw in global mode', (t) => { - const dedupe = requireInject('../../lib/dedupe.js', { - '../../lib/npm.js': { - flatOptions: { - global: true, - }, - }, - }) + const Dedupe = requireInject('../../lib/dedupe.js') + const dedupe = new Dedupe({ flatOptions: { global: true } }) - dedupe([], er => { + dedupe.exec([], er => { t.match(er, { code: 'EDEDUPEGLOBAL' }, 'throws EDEDUPEGLOBAL') t.end() }) }) test('should remove dupes using Arborist', (t) => { - const dedupe = requireInject('../../lib/dedupe.js', { - '../../lib/npm.js': { - prefix: 'foo', - flatOptions: { - dryRun: 'false', - }, - }, + const Dedupe = requireInject('../../lib/dedupe.js', { '@npmcli/arborist': function (args) { t.ok(args, 'gets options object') t.ok(args.path, 'gets path option') @@ -36,7 +25,13 @@ test('should remove dupes using Arborist', (t) => { t.ok(arb, 'gets arborist tree') }, }) - dedupe({ dryRun: true }, er => { + const dedupe = new Dedupe({ + prefix: 'foo', + flatOptions: { + dryRun: 'false', + }, + }) + dedupe.exec({ dryRun: true }, er => { if (er) throw er t.ok(true, 'callback is called') @@ -45,20 +40,20 @@ test('should remove dupes using Arborist', (t) => { }) test('should remove dupes using Arborist - no arguments', (t) => { - const dedupe = requireInject('../../lib/dedupe.js', { - '../../lib/npm.js': { - prefix: 'foo', - flatOptions: { - dryRun: 'true', - }, - }, + const Dedupe = requireInject('../../lib/dedupe.js', { '@npmcli/arborist': function (args) { t.ok(args.dryRun, 'gets dryRun from flatOptions') this.dedupe = () => {} }, '../../lib/utils/reify-output.js': () => {}, }) - dedupe(null, () => { + const dedupe = new Dedupe({ + prefix: 'foo', + flatOptions: { + dryRun: 'true', + }, + }) + dedupe.exec(null, () => { t.end() }) }) diff --git a/test/lib/deprecate.js b/test/lib/deprecate.js index fd563de1209dd..03100166a012c 100644 --- a/test/lib/deprecate.js +++ b/test/lib/deprecate.js @@ -18,10 +18,7 @@ npmFetch.json = async (uri, opts) => { } } -const deprecate = requireInject('../../lib/deprecate.js', { - '../../lib/npm.js': { - flatOptions: { registry: 'https://registry.npmjs.org' }, - }, +const Deprecate = requireInject('../../lib/deprecate.js', { '../../lib/utils/get-identity.js': async () => getIdentityImpl(), '../../lib/utils/otplease.js': async (opts, fn) => fn(opts), libnpmaccess: { @@ -30,16 +27,19 @@ const deprecate = requireInject('../../lib/deprecate.js', { 'npm-registry-fetch': npmFetch, }) +const deprecate = new Deprecate({ + flatOptions: { registry: 'https://registry.npmjs.org' }, +}) + test('completion', async t => { const defaultIdentityImpl = getIdentityImpl t.teardown(() => { getIdentityImpl = defaultIdentityImpl }) - const { completion } = deprecate - const testComp = async (argv, expect) => { - const res = await completion({ conf: { argv: { remain: argv } } }) + const res = + await deprecate.completion({ conf: { argv: { remain: argv } } }) t.strictSame(res, expect, `completion: ${argv}`) } @@ -59,21 +59,21 @@ test('completion', async t => { }) test('no args', t => { - deprecate([], (err) => { + deprecate.exec([], (err) => { t.match(err, /Usage: npm deprecate/, 'logs usage') t.end() }) }) test('only one arg', t => { - deprecate(['foo'], (err) => { + deprecate.exec(['foo'], (err) => { t.match(err, /Usage: npm deprecate/, 'logs usage') t.end() }) }) test('invalid semver range', t => { - deprecate(['foo@notaversion', 'this will fail'], (err) => { + deprecate.exec(['foo@notaversion', 'this will fail'], (err) => { t.match(err, /invalid version range/, 'logs semver error') t.end() }) @@ -84,7 +84,7 @@ test('deprecates given range', t => { npmFetchBody = null }) - deprecate(['foo@1.0.0', 'this version is deprecated'], (err) => { + deprecate.exec(['foo@1.0.0', 'this version is deprecated'], (err) => { if (err) throw err @@ -110,7 +110,7 @@ test('deprecates all versions when no range is specified', t => { npmFetchBody = null }) - deprecate(['foo', 'this version is deprecated'], (err) => { + deprecate.exec(['foo', 'this version is deprecated'], (err) => { if (err) throw err diff --git a/test/lib/diff.js b/test/lib/diff.js index 926c54fdf1848..5e60f125cec3d 100644 --- a/test/lib/diff.js +++ b/test/lib/diff.js @@ -28,7 +28,6 @@ const mocks = { npmlog: { info: noop, verbose: noop }, libnpmdiff: (...args) => libnpmdiff(...args), 'npm-registry-fetch': async () => ({}), - '../../lib/npm.js': npm, '../../lib/utils/output.js': noop, '../../lib/utils/read-local-package.js': async () => rlp(), '../../lib/utils/usage.js': () => 'usage instructions', @@ -42,7 +41,8 @@ t.afterEach(cb => { cb() }) -const diff = requireInject('../../lib/diff.js', mocks) +const Diff = requireInject('../../lib/diff.js', mocks) +const diff = new Diff(npm) t.test('no args', t => { t.test('in a project dir', t => { @@ -56,7 +56,7 @@ t.test('no args', t => { } npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -65,7 +65,7 @@ t.test('no args', t => { t.test('no args, missing package.json name in cwd', t => { rlp = () => undefined - diff([], err => { + diff.exec([], err => { t.match( err, /Needs multiple arguments to compare or run from a project dir./, @@ -80,7 +80,7 @@ t.test('no args', t => { throw new Error('ERR') } - diff([], err => { + diff.exec([], err => { t.match( err, /Needs multiple arguments to compare or run from a project dir./, @@ -106,7 +106,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['foo@1.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -120,7 +120,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['foo@1.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { t.match( err, /Needs multiple arguments to compare or run from a project dir./, @@ -142,7 +142,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['foo@~1.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -160,7 +160,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['2.1.4'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -172,7 +172,7 @@ t.test('single arg', t => { } npm.flatOptions.diff = ['2.1.4'] - diff([], err => { + diff.exec([], err => { t.match( err, /Needs multiple arguments to compare or run from a project dir./, @@ -200,7 +200,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['2.1.4'] npm.flatOptions.prefix = path - diff(['./foo.js', './bar.js'], err => { + diff.exec(['./foo.js', './bar.js'], err => { if (err) throw err }) @@ -224,7 +224,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['bar@1.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -250,7 +250,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['simple-output'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -264,7 +264,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['bar'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { t.match( err, /Needs multiple arguments to compare or run from a project dir./, @@ -297,7 +297,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['bar'] npm.flatOptions.prefix = path - const diff = requireInject('../../lib/diff.js', { + const Diff = requireInject('../../lib/diff.js', { ...mocks, pacote: { packument: (spec) => { @@ -313,8 +313,9 @@ t.test('single arg', t => { t.equal(b, 'bar@1.8.10', 'should have possible semver range spec') }, }) + const diff = new Diff(npm) - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -359,7 +360,7 @@ t.test('single arg', t => { npm.flatOptions.prefix = resolve(path, 'project') npm.globalDir = resolve(path, 'globalDir/lib/node_modules') - const diff = requireInject('../../lib/diff.js', { + const Diff = requireInject('../../lib/diff.js', { ...mocks, pacote: { packument: (spec) => { @@ -375,8 +376,9 @@ t.test('single arg', t => { t.equal(b, 'lorem@2.1.0', 'should have possible semver range spec') }, }) + const diff = new Diff(npm) - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -410,7 +412,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['bar@2.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -445,7 +447,7 @@ t.test('single arg', t => { }), }) - const diff = requireInject('../../lib/diff.js', { + const Diff = requireInject('../../lib/diff.js', { ...mocks, '../../lib/utils/read-local-package.js': async () => 'my-project', pacote: { @@ -462,11 +464,12 @@ t.test('single arg', t => { t.equal(b, 'lorem@2.2.2', 'should have expected target spec') }, }) + const diff = new Diff(npm) npm.flatOptions.diff = ['lorem'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -481,7 +484,7 @@ t.test('single arg', t => { }), }) - const diff = requireInject('../../lib/diff.js', { + const Diff = requireInject('../../lib/diff.js', { ...mocks, '../../lib/utils/read-local-package.js': async () => 'my-project', '@npmcli/arborist': class { @@ -494,11 +497,12 @@ t.test('single arg', t => { t.equal(b, `file:${path}`, 'should target current cwd') }, }) + const diff = new Diff(npm) npm.flatOptions.diff = ['lorem'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -517,7 +521,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['bar'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -535,7 +539,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['my-project'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -553,7 +557,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['/path/to/other-dir'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -564,7 +568,7 @@ t.test('single arg', t => { npm.flatOptions.diff = ['git+https://github.com/user/foo'] - diff([], err => { + diff.exec([], err => { t.match( err, /Spec type not supported./, @@ -588,7 +592,7 @@ t.test('first arg is a qualified spec', t => { } npm.flatOptions.diff = ['bar@1.0.0', 'bar@^2.0.0'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -622,7 +626,7 @@ t.test('first arg is a qualified spec', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['bar@2.0.0', 'bar'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -638,7 +642,7 @@ t.test('first arg is a qualified spec', t => { t.equal(b, 'bar@2.0.0', 'should use name from first arg') } - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -653,7 +657,7 @@ t.test('first arg is a qualified spec', t => { } npm.flatOptions.diff = ['bar@1.0.0', 'bar-fork'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -691,7 +695,7 @@ t.test('first arg is a known dependency name', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['bar', 'bar@2.0.0'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -731,7 +735,7 @@ t.test('first arg is a known dependency name', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['bar', 'bar-fork'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -765,7 +769,7 @@ t.test('first arg is a known dependency name', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['bar', '2.0.0'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -799,7 +803,7 @@ t.test('first arg is a known dependency name', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['bar', 'bar-fork'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -819,7 +823,7 @@ t.test('first arg is a valid semver range', t => { t.equal(b, 'bar@2.0.0', 'should use expected spec') } - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -853,7 +857,7 @@ t.test('first arg is a valid semver range', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['1.0.0', 'bar'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -869,7 +873,7 @@ t.test('first arg is a valid semver range', t => { } npm.flatOptions.diff = ['1.0.0', '2.0.0'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -883,7 +887,7 @@ t.test('first arg is a valid semver range', t => { npm.flatOptions.diff = ['1.0.0', '2.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { t.match( err, /Needs to be run from a project dir in order to diff two versions./, @@ -903,7 +907,7 @@ t.test('first arg is a valid semver range', t => { } npm.flatOptions.diff = ['1.0.0', 'bar'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -918,7 +922,7 @@ t.test('first arg is a valid semver range', t => { }), }) - const diff = requireInject('../../lib/diff.js', { + const Diff = requireInject('../../lib/diff.js', { ...mocks, '../../lib/utils/read-local-package.js': async () => 'my-project', '@npmcli/arborist': class { @@ -931,11 +935,12 @@ t.test('first arg is a valid semver range', t => { t.equal(b, 'lorem@2.0.0', 'should target expected spec') }, }) + const diff = new Diff(npm) npm.flatOptions.diff = ['1.0.0', 'lorem@2.0.0'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -955,7 +960,7 @@ t.test('first arg is an unknown dependency name', t => { } npm.flatOptions.diff = ['bar', 'bar@2.0.0'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -989,7 +994,7 @@ t.test('first arg is an unknown dependency name', t => { npm.flatOptions.prefix = path npm.flatOptions.diff = ['bar-fork', 'bar'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -1004,7 +1009,7 @@ t.test('first arg is an unknown dependency name', t => { } npm.flatOptions.diff = ['bar', '^1.0.0'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -1019,7 +1024,7 @@ t.test('first arg is an unknown dependency name', t => { } npm.flatOptions.diff = ['bar', 'bar-fork'] - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -1040,7 +1045,7 @@ t.test('first arg is an unknown dependency name', t => { npm.flatOptions.diff = ['bar', 'bar-fork'] npm.flatOptions.prefix = path - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -1062,7 +1067,7 @@ t.test('various options', t => { }, 'should forward nameOnly=true option') } - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -1085,7 +1090,7 @@ t.test('various options', t => { }, 'should forward diffFiles values') } - diff(['./foo.js', './bar.js'], err => { + diff.exec(['./foo.js', './bar.js'], err => { if (err) throw err }) @@ -1109,7 +1114,7 @@ t.test('various options', t => { } npm.flatOptions.prefix = path - diff(['./foo.js', './bar.js'], err => { + diff.exec(['./foo.js', './bar.js'], err => { if (err) throw err }) @@ -1137,7 +1142,7 @@ t.test('various options', t => { }, 'should forward diff options') } - diff([], err => { + diff.exec([], err => { if (err) throw err }) @@ -1148,7 +1153,7 @@ t.test('various options', t => { t.test('too many args', t => { npm.flatOptions.diff = ['a', 'b', 'c'] - diff([], err => { + diff.exec([], err => { t.match( err, /Can't use more than two --diff arguments./,