diff --git a/bin/cli.js b/bin/cli.js index 82994bf..802db84 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -2,6 +2,7 @@ 'use strict' +const config = require('../lib/config/npm-config.js') const yargs = require('yargs') const Installer = require('../index.js') @@ -12,13 +13,18 @@ if (require.main === module) { } function cliMain () { - return new Installer(parseArgs()).run().then(details => { - console.error(`added ${details.pkgCount} packages in ${ + parseArgs() + return config.fromNpm(process.argv) + .then(c => new Installer({ + config: c, + log: require('npmlog') + }).run()) + .then( + details => console.error(`added ${details.pkgCount} packages in ${ details.runTime / 1000 - }s`) - }, err => { - console.error(`Error!\n${err.message}\n${err.stack}`) - }) + }s`), + err => console.error(`cipm failed:\n${err.message}\n${err.stack}`) + ) } function parseArgs () { diff --git a/index.js b/index.js index df6e529..8a4863f 100644 --- a/index.js +++ b/index.js @@ -3,23 +3,23 @@ const BB = require('bluebird') const binLink = require('bin-links') -const config = require('./lib/config.js') const extract = require('./lib/extract.js') const fs = require('graceful-fs') const getPrefix = require('find-npm-prefix') const lifecycle = require('npm-lifecycle') const lockVerify = require('lock-verify') const logi = require('npm-logical-tree') -const npmlog = require('npmlog') const path = require('path') const readPkgJson = BB.promisify(require('read-package-json')) const rimraf = BB.promisify(require('rimraf')) const readFileAsync = BB.promisify(fs.readFile) +const statAsync = BB.promisify(fs.stat) class Installer { constructor (opts) { this.opts = opts + this.config = opts.config // Stats this.startTime = Date.now() @@ -27,35 +27,36 @@ class Installer { this.pkgCount = 0 // Misc - this.log = npmlog + this.log = this.opts.log || require('npmlog') this.pkg = null this.tree = null this.failedDeps = new Set() } run () { + const prefix = this.config.get('prefix') return this.prepare() .then(() => this.extractTree(this.tree)) .then(() => this.buildTree(this.tree)) .then(() => this.garbageCollect(this.tree)) - .then(() => this.runScript('prepublish', this.pkg, this.prefix)) - .then(() => this.runScript('prepare', this.pkg, this.prefix)) - .then(() => { - extract.stopWorkers() - this.runTime = Date.now() - this.startTime - return this - }, e => { - extract.stopWorkers() - throw e - }) + .then(() => this.runScript('prepublish', this.pkg, prefix)) + .then(() => this.runScript('prepare', this.pkg, prefix)) + .then(() => this.teardown()) + .then(() => { this.runTime = Date.now() - this.startTime }) + .catch(err => { this.teardown(); throw err }) + .then(() => this) } prepare () { extract.startWorkers() return ( - this.opts.prefix - ? BB.resolve(this.opts.prefix) + this.config.get('prefix') && this.config.get('global') + ? BB.resolve(this.config.get('prefix')) + // There's some Specialâ„¢ logic around the `--prefix` config when it + // comes from a config file or env vs when it comes from the CLI + : process.argv.some(arg => arg.match(/--prefix/i)) + ? this.config.get('prefix') : getPrefix(process.cwd()) ) .then(prefix => { @@ -70,12 +71,13 @@ class Installer { } ) }) - .then(() => config(this.prefix, process.argv, this.pkg)) - .then(conf => { - this.config = conf + .then(() => statAsync( + path.join(this.config.get('prefix'), 'node_modules') + ).catch(err => { if (err.code !== 'ENOENT') { throw err } })) + .then(stat => { return BB.join( this.checkLock(), - rimraf(path.join(this.prefix, 'node_modules')) + stat && rimraf(path.join(this.config.get('prefix'), 'node_modules')) ) }).then(() => { // This needs to happen -after- we've done checkLock() @@ -83,9 +85,13 @@ class Installer { }) } + teardown () { + return extract.stopWorkers() + } + checkLock () { const pkg = this.pkg - const prefix = this.prefix + const prefix = this.config.get('prefix') if (!pkg._shrinkwrap || !pkg._shrinkwrap.lockfileVersion) { return BB.reject( new Error(`cipm can only install packages with an existing package-lock.json or npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or later to generate it, then try again.`) @@ -108,11 +114,12 @@ class Installer { extractTree (tree) { return tree.forEachAsync((dep, next) => { - if (dep.dev && this.config.config.production) { return } - const depPath = dep.path(this.prefix) + if (dep.dev && this.config.get('production')) { return } + const depPath = dep.path(this.config.get('prefix')) // Process children first, then extract this child return BB.join( - !dep.isRoot && extract.child(dep.name, dep, depPath, this.config), + !dep.isRoot && + extract.child(dep.name, dep, depPath, this.config, this.opts), next() ).then(() => { !dep.isRoot && this.pkgCount++ }) }, {concurrency: 50, Promise: BB}) @@ -120,28 +127,26 @@ class Installer { buildTree (tree) { return tree.forEachAsync((dep, next) => { - if (dep.dev && this.config.config.production) { return } - const depPath = dep.path(this.prefix) + if (dep.dev && this.config.get('production')) { return } + const depPath = dep.path(this.config.get('prefix')) return readPkgJson(path.join(depPath, 'package.json')) .then(pkg => { return this.runScript('preinstall', pkg, depPath) .then(next) // build children between preinstall and binLink // Don't link root bins .then(() => !dep.isRoot && binLink(pkg, depPath, false, { - force: this.config.config.force, - ignoreScripts: this.config.lifecycleOpts.ignoreScripts, + force: this.config.get('force'), + ignoreScripts: this.config.get('ignore-scripts'), log: this.log, name: pkg.name, pkgId: pkg.name + '@' + pkg.version, - prefix: this.prefix, - prefixes: [this.prefix], - umask: this.config.config.umask + prefix: this.config.get('prefix'), + prefixes: [this.config.get('prefix')], + umask: this.config.get('umask') }), e => {}) .then(() => this.runScript('install', pkg, depPath)) .then(() => this.runScript('postinstall', pkg, depPath)) - .then(() => { - return this - }) + .then(() => this) .catch(e => { if (dep.optional) { this.failedDeps.add(dep) @@ -158,7 +163,7 @@ class Installer { if (!this.failedDeps.size) { return } return sweep( tree, - this.prefix, + this.config.get('prefix'), mark(tree, this.failedDeps) ) .then(purged => { @@ -168,15 +173,19 @@ class Installer { } runScript (stage, pkg, pkgPath) { - if (!this.config.lifecycleOpts.ignoreScripts && pkg.scripts && pkg.scripts[stage]) { + if ( + !this.config.get('ignore-scripts') && pkg.scripts && pkg.scripts[stage] + ) { // TODO(mikesherov): remove pkg._id when npm-lifecycle no longer relies on it pkg._id = pkg.name + '@' + pkg.version - return lifecycle(pkg, stage, pkgPath, this.config.lifecycleOpts) + const opts = this.config.toLifecycle() + return lifecycle(pkg, stage, pkgPath, opts) } return BB.resolve() } } module.exports = Installer +module.exports.CipmConfig = require('./lib/config/npm-config.js').CipmConfig function mark (tree, failed) { const liveDeps = new Set() diff --git a/lib/config.js b/lib/config.js deleted file mode 100644 index 4d421e3..0000000 --- a/lib/config.js +++ /dev/null @@ -1,87 +0,0 @@ -'use strict' - -const BB = require('bluebird') -const log = require('npmlog') -const cipmPkg = require('../package.json') -const spawn = require('child_process').spawn - -module.exports = getConfig -module.exports._resetConfig = _resetConfig - -let _config - -// Right now, we're leaning on npm itself to give us a config and do all the -// usual npm config logic. In the future, we'll have a standalone package that -// does compatible config loading -- but this version just runs a child -// process. -function readConfig (argv) { - return new BB((resolve, reject) => { - const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm' - const child = spawn(npmBin, [ - 'config', 'ls', '--json', '-l' - // We add argv here to get npm to parse those options for us :D - ].concat(argv || []), { - env: process.env, - cwd: process.cwd(), - stdio: [0, 'pipe', 2] - }) - - let stdout = '' - if (child.stdout) { - child.stdout.on('data', (chunk) => { - stdout += chunk - }) - } - - child.on('error', reject) - child.on('close', (code) => { - if (code === 127) { - reject(new Error('`npm` command not found. Please ensure you have npm@5.4.0 or later installed.')) - } else { - try { - resolve(JSON.parse(stdout)) - } catch (e) { - reject(new Error('`npm config ls --json` failed to output json. Please ensure you have npm@5.4.0 or later installed.')) - } - } - }) - }) -} - -/** - * used solely for testing - */ -function _resetConfig () { - _config = undefined -} - -function getConfig (dir, argv, rootPkg) { - if (_config) return BB.resolve(_config) - return readConfig(argv).then(config => { - log.level = config['loglevel'] - config['user-agent'] = config['user-agent'] || `${cipmPkg.name}@${cipmPkg.version} ${process.release.name}@${process.version.replace(/^v/, '')} ${process.platform} ${process.arch}` - _config = { - prefix: dir, - log, - rootPkg, - config, - // These are opts for `npm-lifecycle` - lifecycleOpts: { - config, - scriptShell: config['script-shell'], - force: config.force, - user: config.user, - group: config.group, - ignoreScripts: config['ignore-scripts'], - ignorePrepublish: config['ignore-prepublish'], - scriptsPrependNodePath: config['scripts-prepend-node-path'], - unsafePerm: config['unsafe-perm'], - log, - dir, - failOk: false, - production: config.production - } - } - return _config - }) -} diff --git a/lib/config/lifecycle-opts.js b/lib/config/lifecycle-opts.js new file mode 100644 index 0000000..7d57459 --- /dev/null +++ b/lib/config/lifecycle-opts.js @@ -0,0 +1,29 @@ +'use strict' + +const log = require('npmlog') + +module.exports = lifecycleOpts +function lifecycleOpts (opts) { + const objConfig = {} + for (const key of opts.keys()) { + const val = opts.get(key) + if (val != null) { + objConfig[key] = val + } + } + return { + config: objConfig, + scriptShell: opts.get('script-shell'), + force: opts.get('force'), + user: opts.get('user'), + group: opts.get('group'), + ignoreScripts: opts.get('ignore-scripts'), + ignorePrepublish: opts.get('ignore-prepublish'), + scriptsPrependNodePath: opts.get('scripts-prepend-node-path'), + unsafePerm: opts.get('unsafe-perm'), + log, + dir: opts.get('prefix'), + failOk: false, + production: opts.get('production') + } +} diff --git a/lib/config/npm-config.js b/lib/config/npm-config.js new file mode 100644 index 0000000..76b4054 --- /dev/null +++ b/lib/config/npm-config.js @@ -0,0 +1,72 @@ +'use strict' + +const BB = require('bluebird') +const lifecycleOpts = require('./lifecycle-opts.js') +const pacoteOpts = require('./pacote-opts.js') +const protoduck = require('protoduck') +const spawn = require('child_process').spawn + +class NpmConfig extends Map {} + +const CipmConfig = protoduck.define({ + get: [], + set: [], + toPacote: [], + toLifecycle: [] +}, { + name: 'CipmConfig' +}) +module.exports.CipmConfig = CipmConfig + +CipmConfig.impl(NpmConfig, { + get: Map.prototype.get, + set: Map.prototype.set, + toPacote (opts) { + return pacoteOpts(this, opts) + }, + toLifecycle () { + return lifecycleOpts(this) + } +}) + +module.exports.fromObject = fromObj +function fromObj (obj) { + const map = new NpmConfig() + Object.keys(obj).forEach(k => map.set(k, obj[k])) + return map +} + +module.exports.fromNpm = getNpmConfig +function getNpmConfig (argv) { + return new BB((resolve, reject) => { + const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm' + const child = spawn(npmBin, [ + 'config', 'ls', '--json', '-l' + // We add argv here to get npm to parse those options for us :D + ].concat(argv || []), { + env: process.env, + cwd: process.cwd(), + stdio: [0, 'pipe', 2] + }) + + let stdout = '' + if (child.stdout) { + child.stdout.on('data', (chunk) => { + stdout += chunk + }) + } + + child.on('error', reject) + child.on('close', (code) => { + if (code === 127) { + reject(new Error('`npm` command not found. Please ensure you have npm@5.4.0 or later installed.')) + } else { + try { + resolve(fromObj(JSON.parse(stdout))) + } catch (e) { + reject(new Error('`npm config ls --json` failed to output json. Please ensure you have npm@5.4.0 or later installed.')) + } + } + }) + }) +} diff --git a/lib/pacote-opts.js b/lib/config/pacote-opts.js similarity index 66% rename from lib/pacote-opts.js rename to lib/config/pacote-opts.js index 0472bd9..234ed13 100644 --- a/lib/pacote-opts.js +++ b/lib/config/pacote-opts.js @@ -11,42 +11,41 @@ const npmSession = crypto.randomBytes(8).toString('hex') module.exports = pacoteOpts function pacoteOpts (npmOpts, moreOpts) { - const conf = npmOpts.config const ownerStats = calculateOwner() const opts = { - cache: path.join(conf['cache'], '_cacache'), - ca: conf['ca'], - cert: conf['cert'], - git: conf['git'], - key: conf['key'], - localAddress: conf['local-address'], - loglevel: conf['loglevel'], - maxSockets: +(conf['maxsockets'] || 15), + cache: path.join(npmOpts.get('cache'), '_cacache'), + ca: npmOpts.get('ca'), + cert: npmOpts.get('cert'), + git: npmOpts.get('git'), + key: npmOpts.get('key'), + localAddress: npmOpts.get('local-address'), + loglevel: npmOpts.get('loglevel'), + maxSockets: +(npmOpts.get('maxsockets') || 15), npmSession: npmSession, - offline: conf['offline'], - projectScope: getProjectScope((npmOpts.rootPkg || moreOpts.rootPkg).name), - proxy: conf['https-proxy'] || conf['proxy'], + offline: npmOpts.get('offline'), + projectScope: moreOpts.rootPkg && getProjectScope(moreOpts.rootPkg.name), + proxy: npmOpts.get('https-proxy') || npmOpts.get('proxy'), refer: 'cipm', - registry: conf['registry'], + registry: npmOpts.get('registry'), retry: { - retries: conf['fetch-retries'], - factor: conf['fetch-retry-factor'], - minTimeout: conf['fetch-retry-mintimeout'], - maxTimeout: conf['fetch-retry-maxtimeout'] + retries: npmOpts.get('fetch-retries'), + factor: npmOpts.get('fetch-retry-factor'), + minTimeout: npmOpts.get('fetch-retry-mintimeout'), + maxTimeout: npmOpts.get('fetch-retry-maxtimeout') }, - strictSSL: conf['strict-ssl'], - userAgent: conf['user-agent'], + strictSSL: npmOpts.get('strict-ssl'), + userAgent: npmOpts.get('user-agent'), - dmode: parseInt('0777', 8) & (~conf['umask']), - fmode: parseInt('0666', 8) & (~conf['umask']), - umask: conf['umask'] + dmode: parseInt('0777', 8) & (~npmOpts.get('umask')), + fmode: parseInt('0666', 8) & (~npmOpts.get('umask')), + umask: npmOpts.get('umask') } if (ownerStats.uid != null || ownerStats.gid != null) { Object.assign(opts, ownerStats) } - Object.keys(conf).forEach(k => { + (npmOpts.forEach ? Array.from(npmOpts.keys()) : npmOpts.keys).forEach(k => { const authMatchGlobal = k.match( /^(_authToken|username|_password|password|email|always-auth|_auth)$/ ) @@ -65,16 +64,16 @@ function pacoteOpts (npmOpts, moreOpts) { if (authMatchScoped) { nerfDart = authMatchScoped[1] key = authMatchScoped[2] - val = conf[k] + val = npmOpts.get(k) if (!opts.auth[nerfDart]) { opts.auth[nerfDart] = { - alwaysAuth: !!conf['always-auth'] + alwaysAuth: !!npmOpts.get('always-auth') } } } else { key = authMatchGlobal[1] - val = conf[k] - opts.auth.alwaysAuth = !!conf['always-auth'] + val = npmOpts.get(k) + opts.auth.alwaysAuth = !!npmOpts.get('always-auth') } const auth = authMatchScoped ? opts.auth[nerfDart] : opts.auth @@ -94,7 +93,7 @@ function pacoteOpts (npmOpts, moreOpts) { if (k[0] === '@') { if (!opts.scopeTargets) { opts.scopeTargets = {} } - opts.scopeTargets[k.replace(/:registry$/, '')] = conf[k] + opts.scopeTargets[k.replace(/:registry$/, '')] = npmOpts.get(k) } }) diff --git a/lib/extract.js b/lib/extract.js index 6115ffe..27a9588 100644 --- a/lib/extract.js +++ b/lib/extract.js @@ -3,7 +3,6 @@ const BB = require('bluebird') const npa = require('npm-package-arg') -const pacoteOpts = require('./pacote-opts.js') const workerFarm = require('worker-farm') const extractionWorker = require('./worker.js') @@ -21,19 +20,27 @@ module.exports = { workerFarm.end(this._workers) }, - child (name, child, childPath, opts) { + child (name, child, childPath, config, opts) { if (child.bundled) return BB.resolve() - const spec = npa.resolve(name, child.resolved || child.version) - const childOpts = pacoteOpts(opts, { - integrity: child.integrity - }) + const spec = npa.resolve(name, child.version) + const childOpts = config.toPacote(Object.assign({ + integrity: child.integrity, + resolved: child.resolved + }, { + dirPacker: opts.dirPacker + })) const args = [spec, childPath, childOpts] return BB.fromNode((cb) => { let launcher = extractionWorker let msg = args const spec = typeof args[0] === 'string' ? npa(args[0]) : args[0] + childOpts.loglevel = opts.log.level if (spec.registry || spec.type === 'remote') { + // We can't serialize these options + childOpts.config = null + childOpts.log = null + childOpts.dirPacker = null // workers will run things in parallel! launcher = this._workers try { diff --git a/test/specs/bin/cli.js b/test/specs/bin/cli.js deleted file mode 100644 index 1f9edde..0000000 --- a/test/specs/bin/cli.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict' - -const BB = require('bluebird') -const test = require('tap').test -const requireInject = require('require-inject') - -test('cli: invokes main with parsed options', t => { - t.plan(2) - class FakeInstaller { - constructor (opts) { - this.opts = opts - } - run () { - t.comment('opts:', this.opts) - t.is(this.opts.ignoreScripts, false, 'ignoreScripts defaults to false') - t.is(this.opts.offline, false, 'offline defaults to false') - return BB.resolve({count: 0, time: 0}) - } - } - let cli = requireInject('../../../bin/cli.js', { - '../../../index.js': FakeInstaller - }) - return cli() -}) diff --git a/test/specs/index.js b/test/specs/index.js index 11d8eb5..2bad9a4 100644 --- a/test/specs/index.js +++ b/test/specs/index.js @@ -2,6 +2,7 @@ const BB = require('bluebird') +const npmConfig = require('../../lib/config/npm-config.js') const fixtureHelper = require('../lib/fixtureHelper.js') const fs = BB.promisifyAll(require('fs')) const path = require('path') @@ -31,13 +32,24 @@ const Installer = requireInject('../../index.js', { } }) +function run (moreOpts) { + return new Installer({ + config: npmConfig.fromObject(Object.assign({}, { + global: true, + prefix, + 'unsafe-perm': true, // this is the default when running non-root + loglevel: 'error' + }, moreOpts || {})) + }).run() +} + test('throws error when no package.json is found', t => { const fixture = new Tacks(Dir({ 'index.js': File('var a = 1') })) fixture.create(prefix) - return new Installer({prefix}).run().catch(err => { + return run().catch(err => { t.equal(err.code, 'ENOENT') }) }) @@ -51,7 +63,7 @@ test('throws error when no package-lock nor shrinkwrap is found', t => { })) fixture.create(prefix) - return new Installer({prefix}).run().catch(err => { + return run().catch(err => { t.equal(err.message, 'cipm can only install packages with an existing package-lock.json or npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or later to generate it, then try again.') }) }) @@ -72,7 +84,7 @@ test('throws error when package.json and package-lock.json do not match', t => { })) fixture.create(prefix) - return new Installer({prefix}).run().catch(err => { + return run().catch(err => { t.match(err.message, 'cipm can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync') }) }) @@ -87,7 +99,7 @@ test('throws error when old shrinkwrap is found', t => { })) fixture.create(prefix) - return new Installer({prefix}).run().catch(err => { + return run().catch(err => { t.equal(err.message, 'cipm can only install packages with an existing package-lock.json or npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or later to generate it, then try again.') }) }) @@ -105,7 +117,7 @@ test('handles empty dependency list', t => { })) fixture.create(prefix) - return new Installer({prefix}).run().then(details => { + return run().then(details => { t.equal(details.pkgCount, 0) }) }) @@ -145,7 +157,7 @@ test('handles dependency list with only shallow subdeps', t => { files.create(childPath) } - return new Installer({prefix}).run().then(details => { + return run().then(details => { t.equal(details.pkgCount, 1) const modPath = path.join(prefix, 'node_modules', 'a') return fs.readFileAsync(path.join(modPath, 'index.js'), 'utf8') @@ -196,7 +208,7 @@ test('handles dependency list with only deep subdeps', t => { files.create(childPath) } - return new Installer({prefix}).run().then(details => { + return run().then(details => { t.equal(details.pkgCount, 2) return BB.join( fs.readFileAsync( @@ -253,7 +265,7 @@ test('prioritizes npm-shrinkwrap over package-lock if both present', t => { files.create(childPath) } - return new Installer({prefix}).run().then(details => { + return run().then(details => { t.equal(details.pkgCount, 1) return fs.readFileAsync(path.join(prefix, 'node_modules', 'a', 'package.json'), 'utf8') }).then(pkgJson => { @@ -318,7 +330,7 @@ test('links binaries for dependencies', t => { } const isWindows = process.platform === 'win32' - return new Installer({prefix}).run().then(details => { + return run().then(details => { const modP = path.join(prefix, 'node_modules') if (isWindows) { t.match( @@ -443,7 +455,7 @@ test('removes failed optional dependencies', t => { const originalConsoleLog = console.log console.log = () => {} - return new Installer({prefix}).run().then(details => { + return run().then(details => { console.log = originalConsoleLog t.ok(true, 'installer succeeded even with optDep failure') t.equal(details.pkgCount, 2, 'only successful deps counted') @@ -504,7 +516,7 @@ test('runs lifecycle hooks of packages with env variables', t => { files.create(childPath) } - return new Installer({prefix}).run().then(details => { + return run().then(details => { t.equal(details.pkgCount, 1) t.match(fixtureHelper.read(prefix, 'preinstall'), 'preinstall') t.match(fixtureHelper.read(prefix, 'install'), 'install') @@ -546,10 +558,6 @@ test('skips lifecycle scripts with ignoreScripts is set', t => { lockfileVersion: 1 } }) - const opts = { - ignoreScripts: true, - prefix: prefix - } extract = fixtureHelper.getWriter(pkgName, { '/node_modules/a': { @@ -567,7 +575,7 @@ test('skips lifecycle scripts with ignoreScripts is set', t => { } }) - return new Installer(opts).run().then(details => { + return run({prefix, 'ignore-scripts': true}).then(details => { t.equal(details.pkgCount, 1) t.ok(fixtureHelper.missing(prefix, 'preinstall')) t.ok(fixtureHelper.missing(prefix, 'install')) diff --git a/test/specs/lib/config.js b/test/specs/lib/config.js index aea79a6..bd1e144 100644 --- a/test/specs/lib/config.js +++ b/test/specs/lib/config.js @@ -2,27 +2,23 @@ const test = require('tap').test const requireInject = require('require-inject') -const npmlog = require('npmlog') const childProcessFactory = require('../../lib/childProcessFactory.js') -const dir = 'dir' - let child -const config = requireInject('../../../lib/config.js', { +const config = requireInject('../../../lib/config/npm-config.js', { child_process: { spawn: () => child } -}) +}).fromNpm function cleanup () { child = childProcessFactory() - config._resetConfig() } test('config: errors if npm is not found', t => { cleanup() - config(dir).catch(err => { + config().catch(err => { t.equal(err.message, '`npm` command not found. Please ensure you have npm@5.4.0 or later installed.') t.end() }) @@ -33,7 +29,7 @@ test('config: errors if npm is not found', t => { test('config: errors if npm config ls --json cant output json', t => { cleanup() - config(dir).catch(err => { + config().catch(err => { t.equal(err.message, '`npm config ls --json` failed to output json. Please ensure you have npm@5.4.0 or later installed.') t.end() }) @@ -49,7 +45,7 @@ test('config: errors if npm errors for any reason', t => { const errorMessage = 'failed to reticulate splines' - config(dir).catch(err => { + config().catch(err => { t.equal(err, errorMessage) t.end() }) @@ -62,11 +58,12 @@ test('config: parses configs from npm', t => { const expectedConfig = { a: 1, b: 2 } - config(dir).then(config => { - t.same(config.lifecycleOpts.config.a, expectedConfig.a) - t.same(config.lifecycleOpts.config.b, expectedConfig.b) - t.same(config.prefix, dir) - t.same(config.log, npmlog) + config().then(config => { + const objConf = {} + for (const k of config.keys()) { + objConf[k] = config.get(k) + } + t.deepEqual(objConf, expectedConfig, 'configs match') t.end() }) @@ -74,27 +71,6 @@ test('config: parses configs from npm', t => { child.emit('close', 0) }) -test('config: uses a cached config from npm on subsequent invocations', t => { - cleanup() - - const expectedConfig = { a: 1, b: 2 } - const unexpectedConfig = { a: 3, b: 4 } - - config().then(config1 => { - child = childProcessFactory() - config().then(config2 => { - t.equal(config1, config2) - t.end() - }) - - child.stdout.emit('data', JSON.stringify(unexpectedConfig)) - child.emit('close', 0) - }) - - child.stdout.emit('data', JSON.stringify(expectedConfig)) - child.emit('close', 0) -}) - test('config: cleanup', t => { cleanup() t.end() diff --git a/test/specs/lib/pacote-opts.js b/test/specs/lib/pacote-opts.js index 30d0e6d..7479c7c 100644 --- a/test/specs/lib/pacote-opts.js +++ b/test/specs/lib/pacote-opts.js @@ -2,20 +2,19 @@ const test = require('tap').test -const pacoteOpts = require('../../../lib/pacote-opts.js') +const npmConfig = require('../../../lib/config/npm-config.js') +const pacoteOpts = require('../../../lib/config/pacote-opts.js') test('returns a config object usable by pacote', t => { - const opts = pacoteOpts({ - config: { - ca: 'idk', - cache: '/foo', - 'maxsockets': '10', - 'fetch-retries': 23, - _authToken: 'deadbeef', - '//registry.npmjs.org:_authToken': 'c0ffee', - '@myscope:registry': 'https://my-other.registry.internet/' - } - }, { + const opts = pacoteOpts(npmConfig.fromObject({ + ca: 'idk', + cache: '/foo', + 'maxsockets': '10', + 'fetch-retries': 23, + _authToken: 'deadbeef', + '//registry.npmjs.org:_authToken': 'c0ffee', + '@myscope:registry': 'https://my-other.registry.internet/' + }), { rootPkg: require('../../../package.json') })