diff options
author | Kat Marchán <kzm@sykosomatic.org> | 2018-02-08 20:12:54 +0300 |
---|---|---|
committer | Rebecca Turner <me@re-becca.org> | 2018-02-20 07:19:16 +0300 |
commit | 26cd648697c1324979289e381fe837f9837f3874 (patch) | |
tree | 578c640fed8ccd36e660a78c786b6d559a4c567a /node_modules/libcipm/index.js | |
parent | 11a0b00bd3c18625075dcdf4a5cb6500b33c6265 (diff) |
libcipm@1.3.2
Diffstat (limited to 'node_modules/libcipm/index.js')
-rw-r--r-- | node_modules/libcipm/index.js | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/node_modules/libcipm/index.js b/node_modules/libcipm/index.js new file mode 100644 index 000000000..458b8508b --- /dev/null +++ b/node_modules/libcipm/index.js @@ -0,0 +1,313 @@ +'use strict' + +const BB = require('bluebird') + +const binLink = require('bin-links') +const buildLogicalTree = require('npm-logical-tree') +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 mkdirp = BB.promisify(require('mkdirp')) +const npa = require('npm-package-arg') +const path = require('path') +const readPkgJson = BB.promisify(require('read-package-json')) +const rimraf = BB.promisify(require('rimraf')) + +const appendFileAsync = BB.promisify(fs.appendFile) +const readFileAsync = BB.promisify(fs.readFile) +const statAsync = BB.promisify(fs.stat) +const symlinkAsync = BB.promisify(fs.symlink) +const truncateAsync = BB.promisify(fs.truncate) + +class Installer { + constructor (opts) { + this.opts = opts + this.config = opts.config + + // Stats + this.startTime = Date.now() + this.runTime = 0 + this.pkgCount = 0 + + // Misc + this.log = this.opts.log || require('./lib/silentlog.js') + this.pkg = null + this.tree = null + this.failedDeps = new Set() + } + + run () { + const prefix = this.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, 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 () { + this.log.level = this.config.get('loglevel') + this.log.silly('init', 'starting workers') + extract.startWorkers() + + return ( + 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(/^\s*--prefix\s*/i)) + ? this.config.get('prefix') + : getPrefix(process.cwd()) + ) + .then(prefix => { + this.prefix = prefix + this.log.silly('init', 'prefix: ' + prefix) + return BB.join( + readJson(prefix, 'package.json'), + readJson(prefix, 'package-lock.json', true), + readJson(prefix, 'npm-shrinkwrap.json', true), + (pkg, lock, shrink) => { + pkg._shrinkwrap = shrink || lock + this.pkg = pkg + } + ) + }) + .then(() => statAsync( + path.join(this.prefix, 'node_modules') + ).catch(err => { if (err.code !== 'ENOENT') { throw err } })) + .then(stat => { + stat && this.log.warn( + 'init', 'removing existing node_modules/ before installation' + ) + this.log.silly('init', 'removing node_modules/ before install') + return BB.join( + this.checkLock(), + stat && rimraf(path.join(this.prefix, 'node_modules')) + ) + }).then(() => { + // This needs to happen -after- we've done checkLock() + this.tree = buildLogicalTree(this.pkg, this.pkg._shrinkwrap) + this.log.silly('tree', this.tree) + }) + } + + teardown () { + return extract.stopWorkers() + } + + checkLock () { + this.log.silly('checkLock', 'verifying package-lock data') + const pkg = this.pkg + const prefix = this.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.`) + ) + } + return lockVerify(prefix).then(result => { + if (result.status) { + result.warnings.forEach(w => this.log.warn('lockfile', w)) + } else { + throw new Error( + 'cipm can only install packages when your package.json and package-lock.json or ' + + 'npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` ' + + 'before continuing.\n\n' + + result.warnings.map(w => 'Warning: ' + w).join('\n') + '\n' + + result.errors.join('\n') + '\n' + ) + } + }).catch(err => { + throw err + }) + } + + extractTree (tree) { + this.log.silly('extractTree', 'extracting dependencies to node_modules/') + return tree.forEachAsync((dep, next) => { + if (dep.dev && this.config.get('production')) { return } + const depPath = dep.path(this.prefix) + // Process children first, then extract this child + const spec = npa.resolve(dep.name, dep.version, this.prefix) + if (dep.isRoot) { + return next() + } else if (spec.type === 'directory') { + const relative = path.relative(path.dirname(depPath), spec.fetchSpec) + this.log.silly('extractTree', `${dep.name}@${spec.fetchSpec} -> ${depPath} (symlink)`) + return mkdirp(path.dirname(depPath)) + .then(() => symlinkAsync(relative, depPath, 'junction')) + .catch( + () => rimraf(depPath) + .then(() => symlinkAsync(relative, depPath, 'junction')) + ).then(() => next()) + .then(() => { this.pkgCount++ }) + } else { + this.log.silly('extractTree', `${dep.name}@${dep.version} -> ${depPath}`) + return ( + dep.bundled + ? statAsync(depPath).catch(err => { + if (err.code !== 'ENOENT') { throw err } + }) + : BB.resolve(false) + ) + .then(wasBundled => { + const hasBundled = Array.from(dep.dependencies.values()) + .some(d => d.bundled) + + if (hasBundled) { + return BB.resolve( + wasBundled || + extract.child(dep.name, dep, depPath, this.config, this.opts) + ) + .then(next) + } else { + // If it has no bundled children, we can extract children + // concurrently + return BB.join( + wasBundled || + extract.child(dep.name, dep, depPath, this.config, this.opts), + next() + ) + } + }) + .then(() => { this.pkgCount++ }) + } + }, {concurrency: 50, Promise: BB}) + } + + buildTree (tree) { + this.log.silly('buildTree', 'finalizing tree and running scripts') + return tree.forEachAsync((dep, next) => { + if (dep.dev && this.config.get('production')) { return } + const spec = npa.resolve(dep.name, dep.version) + const depPath = dep.path(this.prefix) + return readPkgJson(path.join(depPath, 'package.json')) + .then(pkg => { + if (!spec.registry) { + return this.updateFromField(dep, pkg) + .then(() => pkg) + } else { + return pkg + } + }) + .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.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.get('umask') + }), e => {}) + .then(() => this.runScript('install', pkg, depPath)) + .then(() => this.runScript('postinstall', pkg, depPath)) + .then(() => this) + .catch(e => { + if (dep.optional) { + this.failedDeps.add(dep) + } else { + throw e + } + }) + }) + }, {concurrency: 50, Promise: BB}) + } + + updateFromField (dep, pkg) { + const depPath = dep.path(this.prefix) + const depPkgPath = path.join(depPath, 'package.json') + const parent = dep.requiredBy.values().next().value + const pkgPath = path.join(parent.path(this.prefix), 'package.json') + return readPkgJson(pkgPath) + .then(parentPkg => + parentPkg.dependencies[dep.name] || + parentPkg.devDependencies[dep.name] || + parentPkg.optionalDependencies[dep.name] + ) + .then(from => npa.resolve(dep.name, from)) + .then(from => { pkg._from = from.toString() }) + .then(() => truncateAsync(depPkgPath)) + .then(() => appendFileAsync(depPkgPath, JSON.stringify(pkg, null, 2))) + } + + // A cute little mark-and-sweep collector! + garbageCollect (tree) { + if (!this.failedDeps.size) { return } + return sweep( + tree, + this.prefix, + mark(tree, this.failedDeps) + ) + .then(purged => { + this.purgedDeps = purged + this.pkgCount -= purged.size + }) + } + + runScript (stage, pkg, pkgPath) { + 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 + 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() + tree.forEach((dep, next) => { + if (!failed.has(dep)) { + liveDeps.add(dep) + next() + } + }) + return liveDeps +} + +function sweep (tree, prefix, liveDeps) { + const purged = new Set() + return tree.forEachAsync((dep, next) => { + return next().then(() => { + if ( + !dep.isRoot && // never purge root! 🙈 + !liveDeps.has(dep) && + !purged.has(dep) + ) { + purged.add(dep) + return rimraf(dep.path(prefix)) + } + }) + }, {concurrency: 50, Promise: BB}).then(() => purged) +} + +function stripBOM (str) { + return str.replace(/^\uFEFF/, '') +} + +module.exports._readJson = readJson +function readJson (jsonPath, name, ignoreMissing) { + return readFileAsync(path.join(jsonPath, name), 'utf8') + .then(str => JSON.parse(stripBOM(str))) + .catch({code: 'ENOENT'}, err => { + if (!ignoreMissing) { + throw err + } + }) +} |