Skip to content
This repository has been archived by the owner on May 10, 2021. It is now read-only.

Commit

Permalink
feat(lifecycle): Run lifecycle events, implement prefix option, add u…
Browse files Browse the repository at this point in the history
…nit tests (#1)

* feat(lifecycle): run lifecycle events

* tmp: move things around
  • Loading branch information
mikesherov authored and zkat committed Aug 30, 2017
1 parent 0d10d0d commit d6629be
Show file tree
Hide file tree
Showing 11 changed files with 542 additions and 84 deletions.
38 changes: 38 additions & 0 deletions bin/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env node

'use strict'

const yargs = require('yargs')
const main = require('../index.js')

main(parseArgs()).then((details) => {
console.log(`added ${details.count} packages in ${
details.time / 1000
}s.`)
})

function parseArgs () {
return yargs
.option('loglevel', {
type: 'string',
describe: 'log level for npmlog',
default: 'notice'
})
.option('offline', {
type: 'boolean',
describe: 'force cipm to run offline, or error'
})
.option('ignore-scripts', {
type: 'boolean',
describe: 'skip running lifecycle scripts'
})
.option('prefix', {
type: 'string',
describe: 'path to package.json'
})
.option('userconfig', {
type: 'string',
describe: 'path to npmrc'
})
.argv
}
109 changes: 28 additions & 81 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,29 @@
#!/usr/bin/env node

'use strict'

const BB = require('bluebird')

const extractionWorker = require('./worker.js')
const config = require('./lib/config.js')
const extract = require('./lib/extract.js')
const fs = BB.promisifyAll(require('graceful-fs'))
const npa = require('npm-package-arg')
const path = require('path')
const rimraf = BB.promisify(require('rimraf'))
const workerFarm = require('worker-farm')
const yargs = require('yargs')
const lifecycle = require('npm-lifecycle')

const WORKER_PATH = require.resolve('./worker.js')
let workers
module.exports = main

main(parseArgs())
let pkgCount

let pkgCount = 0
function main ({ prefix = '.' }) {
const startTime = Date.now()
const nodeModulesPath = path.join(prefix, 'node_modules')
pkgCount = 0

function parseArgs () {
return yargs
.option('loglevel', {
type: 'string',
describe: 'log level for npmlog',
default: 'notice'
})
.option('offline', {
type: 'boolean',
describe: 'force cipm to run offline, or error'
})
.option('ignore-scripts', {
type: 'boolean',
describe: 'skip running lifecycle scripts'
})
.option('userconfig', {
type: 'string',
describe: 'path to npmrc'
})
.argv
}
extract.startWorkers()

function main () {
const startTime = Date.now()
workers = workerFarm({
maxConcurrentCallsPerWorker: 30,
maxRetries: 1
}, WORKER_PATH)
return BB.join(
readJson('.', 'package.json'),
readJson('.', 'package-lock.json', true),
readJson('.', 'npm-shrinkwrap.json', true),
readJson(prefix, 'package.json'),
readJson(prefix, 'package-lock.json', true),
readJson(prefix, 'npm-shrinkwrap.json', true),
(pkg, lock, shrink) => {
pkg._shrinkwrap = lock || shrink
return pkg
Expand All @@ -60,29 +33,30 @@ function main () {
throw 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.`)
}

return rimraf('./node_modules')
return rimraf(nodeModulesPath)
}).tap(pkg => {
return runScript('preinstall', pkg, '.')
return runScript('preinstall', pkg, prefix)
}).tap(pkg => {
return extractDeps(
`./node_modules`,
nodeModulesPath,
pkg._shrinkwrap.dependencies
)
}).tap(pkg => {
return runScript('install', pkg, '.')
return runScript('install', pkg, prefix)
}).tap(pkg => {
return runScript('postinstall', pkg, '.')
return runScript('postinstall', pkg, prefix)
}).then(pkg => {
workerFarm.end(workers)
console.log(`added ${pkgCount} packages in ${
(Date.now() - startTime) / 1000
}s.`)
extract.stopWorkers()
return {
count: pkgCount,
time: Date.now() - startTime
}
})
}

function runScript (script, pkg, pkgPath) {
if (pkg.scripts && pkg.scripts[script]) {
console.log('executing', script, 'on', pkgPath)
function runScript (stage, pkg, pkgPath) {
if (pkg.scripts && pkg.scripts[stage]) {
return config().then(config => lifecycle(pkg, stage, pkgPath, config))
}
return BB.resolve()
}
Expand All @@ -91,11 +65,8 @@ function extractDeps (modPath, deps) {
return BB.map(Object.keys(deps || {}), name => {
const child = deps[name]
const childPath = path.join(modPath, name)
return (
child.bundled
? BB.resolve()
: extractChild(name, child, childPath)
).then(() => {
return extract.child(name, child, childPath)
.then(() => {
return readJson(childPath, 'package.json')
}).tap(pkg => {
return runScript('preinstall', pkg, childPath)
Expand All @@ -122,30 +93,6 @@ function extractDeps (modPath, deps) {
}, {concurrency: 50})
}

function extractChild (name, child, childPath) {
const spec = npa.resolve(name, child.resolved || child.version)
const opts = {
cache: path.resolve(process.env.HOME, '.npm/_cacache'),
integrity: child.integrity
}
const args = [spec, childPath, opts]
return BB.fromNode((cb) => {
let launcher = extractionWorker
let msg = args
const spec = typeof args[0] === 'string' ? npa(args[0]) : args[0]
if (spec.registry || spec.type === 'remote') {
// workers will run things in parallel!
launcher = workers
try {
msg = JSON.stringify(msg)
} catch (e) {
return cb(e)
}
}
launcher(msg, cb)
})
}

function readJson (jsonPath, name, ignoreMissing) {
return fs.readFileAsync(path.join(jsonPath, name), 'utf8')
.then(str => JSON.parse(str))
Expand Down
57 changes: 57 additions & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict'

const BB = require('bluebird')
const log = require('npmlog')
const spawn = require('child_process').spawn

module.exports = getConfig
module.exports._resetConfig = _resetConfig

let _config

function readConfig () {
return new BB((resolve, reject) => {
const child = spawn('npm', ['config', 'ls', '--json'], {
env: process.env,
cwd: process.cwd(),
stdio: 'inherit'
})

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 [email protected] 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 [email protected] or later installed.'))
}
}
})
})
}

/**
* used solely for testing
*/
function _resetConfig () {
_config = undefined
}

function getConfig () {
if (_config) return BB.resolve(_config)
return readConfig().then(config => {
_config = config
_config.log = log

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

const BB = require('bluebird')
const npa = require('npm-package-arg')
const path = require('path')
const workerFarm = require('worker-farm')

const extractionWorker = require('./worker.js')
const WORKER_PATH = require.resolve('./worker.js')

module.exports = {
startWorkers () {
this._workers = workerFarm({
maxConcurrentCallsPerWorker: 30,
maxRetries: 1
}, WORKER_PATH)
},

stopWorkers () {
workerFarm.end(this._workers)
},

child (name, child, childPath) {
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'),
integrity: child.integrity
}
const args = [spec, childPath, opts]
return BB.fromNode((cb) => {
let launcher = extractionWorker
let msg = args
const spec = typeof args[0] === 'string' ? npa(args[0]) : args[0]
if (spec.registry || spec.type === 'remote') {
// workers will run things in parallel!
launcher = this._workers
try {
msg = JSON.stringify(msg)
} catch (e) {
return cb(e)
}
}
launcher(msg, cb)
})
}
}
File renamed without changes.
54 changes: 53 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d6629be

Please sign in to comment.