Skip to content
This repository has been archived by the owner on Aug 17, 2019. It is now read-only.

Commit

Permalink
feat(opts): support full range of relevant CLI opts
Browse files Browse the repository at this point in the history
Fixes: #17
  • Loading branch information
zkat committed Sep 9, 2017
1 parent ff1d8cf commit d75ba0b
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 33 deletions.
2 changes: 1 addition & 1 deletion bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function cliMain () {
details.runTime / 1000
}s.`)
}, err => {
console.error(`Error!\n${err.message}`)
console.error(`Error!\n${err.message}\n${err.stack}`)
})
}

Expand Down
17 changes: 9 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const readFileAsync = BB.promisify(fs.readFile)

class Installer {
constructor (opts) {
// Config
this.opts = opts

// Stats
Expand Down Expand Up @@ -47,6 +46,9 @@ class Installer {
}
)
}).then(() => {
return config(this.prefix, process.argv, this.pkg)
}).then(conf => {
this.config = conf
return BB.join(
this.checkLock(),
rimraf(path.join(this.prefix, 'node_modules'))
Expand Down Expand Up @@ -96,13 +98,14 @@ class Installer {
return BB.map(Object.keys(deps || {}), name => {
const child = deps[name]
const childPath = path.join(modPath, name)
return extract.child(name, child, childPath).then(() => {
return extract.child(name, child, childPath, this.config).then(() => {
return readJson(childPath, 'package.json')
}).tap(pkg => {
return this.runScript('preinstall', pkg, childPath)
}).then(pkg => {
return this.extractDeps(path.join(childPath, 'node_modules'), child.dependencies)
.then(dependencies => {
return this.extractDeps(
path.join(childPath, 'node_modules'), child.dependencies
).then(dependencies => {
return {
name,
package: pkg,
Expand All @@ -124,12 +127,10 @@ class Installer {
}

runScript (stage, pkg, pkgPath) {
if (!this.opts.ignoreScripts && pkg.scripts && pkg.scripts[stage]) {
if (!this.config['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 config(this.prefix).then(config => {
return lifecycle(pkg, stage, pkgPath, config)
})
return lifecycle(pkg, stage, pkgPath, this.config)
}
return BB.resolve()
}
Expand Down
35 changes: 18 additions & 17 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@

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

function readConfig () {
// 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'], {
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]
Expand Down Expand Up @@ -47,25 +55,18 @@ function _resetConfig () {
_config = undefined
}

function getConfig (dir) {
function getConfig (dir, argv, rootPkg) {
if (_config) return BB.resolve(_config)
return readConfig().then(config => {
_config = {
config,
return readConfig(argv).then(config => {
log.level = config['loglevel']
_config = Object.assign(config, {
userAgent: config['user-agent'] || `${cipmPkg.name}@${cipmPkg.version} ${process.release.name}@${process.version.replace(/^v/, '')} ${process.platform} ${process.arch}`,
dir,
prefix: dir,
failOk: false,
force: config.force,
group: config.group,
ignorePrepublish: config['ignore-prepublish'],
ignoreScripts: config['ignore-scripts'],
log,
production: config.production,
scriptShell: config['script-shell'],
scriptsPrependNodePath: config['scripts-prepend-node-path'],
unsafePerm: config['unsafe-perm'],
user: config['user']
}

rootPkg
})
return _config
})
}
14 changes: 7 additions & 7 deletions lib/extract.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use strict'

const BB = require('bluebird')

const npa = require('npm-package-arg')
const path = require('path')
const pacoteOpts = require('./pacote-opts.js')
const workerFarm = require('worker-farm')

const extractionWorker = require('./worker.js')
Expand All @@ -11,7 +12,7 @@ const WORKER_PATH = require.resolve('./worker.js')
module.exports = {
startWorkers () {
this._workers = workerFarm({
maxConcurrentCallsPerWorker: 30,
maxConcurrentCallsPerWorker: 20,
maxRetries: 1
}, WORKER_PATH)
},
Expand All @@ -20,15 +21,14 @@ module.exports = {
workerFarm.end(this._workers)
},

child (name, child, childPath) {
child (name, child, childPath, opts) {
if (child.bundled) return BB.resolve()

const spec = npa.resolve(name, child.resolved || child.version)
const opts = {
cache: path.resolve(process.env.HOME, '.npm/_cacache'),
const childOpts = pacoteOpts(opts, {
integrity: child.integrity
}
const args = [spec, childPath, opts]
})
const args = [spec, childPath, childOpts]
return BB.fromNode((cb) => {
let launcher = extractionWorker
let msg = args
Expand Down
139 changes: 139 additions & 0 deletions lib/pacote-opts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
'use strict'

const Buffer = require('safe-buffer').Buffer

const crypto = require('crypto')
const path = require('path')

let effectiveOwner

const npmSession = crypto.randomBytes(8).toString('hex')

module.exports = pacoteOpts
function pacoteOpts (npmOpts, moreOpts) {
const ownerStats = calculateOwner()
const opts = {
cache: path.join(npmOpts['cache'], '_cacache'),
ca: npmOpts['ca'],
cert: npmOpts['cert'],
git: npmOpts['git'],
key: npmOpts['key'],
localAddress: npmOpts['local-address'],
loglevel: npmOpts['loglevel'],
maxSockets: +npmOpts['maxsockets'],
npmSession: npmSession,
offline: npmOpts['offline'],
projectScope: scopeifyScope(npmOpts['scope'] || getProjectScope((npmOpts.rootPkg || moreOpts.rootPkg).name)),
proxy: npmOpts['https-proxy'] || npmOpts['proxy'],
refer: 'cipm',
registry: npmOpts['registry'],
retry: {
retries: npmOpts['fetch-retries'],
factor: npmOpts['fetch-retry-factor'],
minTimeout: npmOpts['fetch-retry-mintimeout'],
maxTimeout: npmOpts['fetch-retry-maxtimeout']
},
strictSSL: npmOpts['strict-ssl'],
userAgent: npmOpts['user-agent'],

dmode: parseInt('0777', 8) & (~npmOpts['umask']),
fmode: parseInt('0666', 8) & (~npmOpts['umask']),
umask: npmOpts['umask']
}

if (ownerStats.uid != null || ownerStats.gid != null) {
Object.assign(opts, ownerStats)
}

Object.keys(npmOpts).forEach(k => {
const authMatchGlobal = k.match(
/^(_authToken|username|_password|password|email|always-auth|_auth)$/
)
const authMatchScoped = k[0] === '/' && k.match(
/(.*):(_authToken|username|_password|password|email|always-auth|_auth)$/
)

// if it matches scoped it will also match global
if (authMatchGlobal || authMatchScoped) {
let nerfDart = null
let key = null
let val = null

if (!opts.auth) { opts.auth = {} }

if (authMatchScoped) {
nerfDart = authMatchScoped[1]
key = authMatchScoped[2]
val = npmOpts[k]
if (!opts.auth[nerfDart]) {
opts.auth[nerfDart] = {
alwaysAuth: !!npmOpts['always-auth']
}
}
} else {
key = authMatchGlobal[1]
val = npmOpts[k]
opts.auth.alwaysAuth = !!npmOpts['always-auth']
}

const auth = authMatchScoped ? opts.auth[nerfDart] : opts.auth
if (key === '_authToken') {
auth.token = val
} else if (key.match(/password$/i)) {
auth.password =
// the config file stores password auth already-encoded. pacote expects
// the actual username/password pair.
Buffer.from(val, 'base64').toString('utf8')
} else if (key === 'always-auth') {
auth.alwaysAuth = val === 'false' ? false : !!val
} else {
auth[key] = val
}
}

if (k[0] === '@') {
if (!opts.scopeTargets) { opts.scopeTargets = {} }
opts.scopeTargets[k.replace(/:registry$/, '')] = npmOpts[k]
}
})

Object.keys(moreOpts || {}).forEach((k) => {
opts[k] = moreOpts[k]
})

return opts
}

function calculateOwner () {
if (!effectiveOwner) {
effectiveOwner = { uid: 0, gid: 0 }

// Pretty much only on windows
if (!process.getuid) {
return effectiveOwner
}

effectiveOwner.uid = +process.getuid()
effectiveOwner.gid = +process.getgid()

if (effectiveOwner.uid === 0) {
if (process.env.SUDO_UID) effectiveOwner.uid = +process.env.SUDO_UID
if (process.env.SUDO_GID) effectiveOwner.gid = +process.env.SUDO_GID
}
}

return effectiveOwner
}

function scopeifyScope (scope) {
return (!scope || scope[0] === '@') ? scope : ('@' + scope)
}

function getProjectScope (pkgName) {
const sep = pkgName.indexOf('/')
if (sep === -1) {
return ''
} else {
return pkgName.slice(0, sep)
}
}
1 change: 1 addition & 0 deletions lib/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = (args, cb) => {
const extractTo = parsed[1]
const opts = parsed[2]
opts.log = log
log.level = opts.loglevel
return rimraf(extractTo, {ignore: 'node_modules'}).then(() => {
return pacote.extract(spec, extractTo, opts)
}).nodeify(cb)
Expand Down

0 comments on commit d75ba0b

Please sign in to comment.