pnpm
Version:
A fast implementation of npm install
259 lines • 10.7 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments)).next());
});
};
const debug_1 = require('../debug');
const debug = debug_1.default('pnpm:install');
const npa = require('npm-package-arg');
const fs = require('mz/fs');
const logger = require('@zkochan/logger');
const path = require('path');
const resolve_1 = require('../resolve');
const mkdirp_1 = require('../fs/mkdirp');
const requireJson_1 = require('../fs/requireJson');
const relSymlink_1 = require('../fs/relSymlink');
const exists = require('exists-file');
const linkBundledDeps_1 = require('./linkBundledDeps');
const isAvailable_1 = require('./isAvailable');
const installMultiple_1 = require('../installMultiple');
const symlinkToModules_1 = require('./symlinkToModules');
/**
* Installs a package.
*
* What it does:
*
* - resolve() - resolve from registry.npmjs.org
* - fetch() - download tarball into node_modules/.store/{name}@{version}
* - recurse into its dependencies
* - symlink node_modules/{name}
*
* @param {Object} ctx - the context.
* @param {Object} pkgMeta - meta info about the package to install.
*
* @example
* install(ctx, 'rimraf@2', './node_modules')
*/
function install(ctx, pkgMeta, modules, options) {
return __awaiter(this, void 0, void 0, function* () {
debug('installing ' + pkgMeta.rawSpec);
if (!ctx.builds)
ctx.builds = {};
if (!ctx.fetches)
ctx.fetches = {};
// Preliminary spec data
// => { raw, name, scope, type, spec, rawSpec }
const spec = npa(pkgMeta.rawSpec);
const optional = pkgMeta.optional || options.optional === true;
// Dependency path to the current package. Not actually needed anmyore
// outside getting its length
// => ['babel-core@6.4.5', 'babylon@6.4.5', 'babel-runtime@5.8.35']
const keypath = (options && options.keypath || []);
const log = logger.fork(spec).log.bind(null, 'progress', pkgMeta.rawSpec);
let installedPkg;
try {
// it might be a bundleDependency, in which case, don't bother
const available = !options.force && (yield isAvailable_1.default(spec, modules));
if (available) {
installedPkg = yield saveCachedResolution();
log('package.json', installedPkg.pkg);
}
else {
const res = yield resolve_1.default(spec, {
log,
got: ctx.got,
root: options.parentRoot || options.root,
linkLocal: options.linkLocal,
tag: options.tag
});
const freshPkg = saveResolution(res);
log('resolved', freshPkg);
yield mkdirp_1.default(modules);
const target = path.join(options.store, res.id);
let installedPkgs = yield buildToStoreCached(ctx, target, freshPkg, log);
const pkg = requireJson_1.default(path.join(target, '_', 'package.json'));
yield symlinkToModules_1.default(path.join(target, '_'), modules);
installedPkg = {
pkg,
optional,
keypath,
id: freshPkg.id,
name: spec.name,
fromCache: false,
dependencies: installedPkgs,
};
log('package.json', pkg);
}
if (!ctx.installs[installedPkg.id]) {
ctx.installs[installedPkg.id] = installedPkg;
}
else {
ctx.installs[installedPkg.id].optional = ctx.installs[installedPkg.id].optional && installedPkg.optional;
}
log('done');
return installedPkg;
}
catch (err) {
log('error', err);
throw err;
}
// set metadata as fetched from resolve()
function saveResolution(res) {
return {
optional,
keypath,
linkLocal: options.linkLocal,
// Full name of package as it should be put in the store. Aim to make
// this as friendly as possible as this will appear in stack traces.
// => 'lodash@4.0.0'
// => '@rstacruz!tap-spec@4.1.1'
// => 'rstacruz!pnpm@0a1b382da'
// => 'foobar@9a3b283ac'
id: res.id,
root: res.root,
fetch: res.fetch,
installationRoot: options.root,
store: options.store,
force: options.force,
depth: options.depth,
tag: options.tag
};
}
function saveCachedResolution() {
return __awaiter(this, void 0, void 0, function* () {
const target = path.join(modules, spec.name);
const stat = yield fs.lstat(target);
if (stat.isSymbolicLink()) {
const linkPath = yield fs.readlink(target);
return save(path.resolve(linkPath, target));
}
return save(target);
function save(fullpath) {
const data = requireJson_1.default(path.join(fullpath, 'package.json'));
return {
pkg: data,
id: path.basename(fullpath),
optional,
keypath,
name: spec.name,
fromCache: true,
dependencies: [],
};
}
});
}
});
}
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = install;
/**
* Builds to `.store/lodash@4.0.0` (paths.target)
* If an ongoing build is already working, use it. Also, if that ongoing build
* is part of the dependency chain (ie, it's a circular dependency), use its stub
*/
function buildToStoreCached(ctx, target, buildInfo, log) {
return __awaiter(this, void 0, void 0, function* () {
// If a package is requested for a second time (usually when many packages depend
// on the same thing), only resolve until it's fetched (not built).
if (ctx.fetches[buildInfo.id]) {
yield ctx.fetches[buildInfo.id];
return [];
}
if (!(yield exists(target)) || buildInfo.force) {
return memoize(ctx.builds, buildInfo.id, function () {
return __awaiter(this, void 0, void 0, function* () {
yield memoize(ctx.fetches, buildInfo.id, () => fetchToStore(ctx, target, buildInfo, log));
return buildInStore(ctx, target, buildInfo, log);
});
});
}
if (buildInfo.keypath.length <= buildInfo.depth) {
const pkg = requireJson_1.default(path.resolve(path.join(target, '_', 'package.json')));
return installSubDeps(ctx, target, buildInfo, pkg, log);
}
return [];
});
}
/**
* Builds to `.store/lodash@4.0.0` (paths.target)
* Fetches from npm, recurses to dependencies, runs lifecycle scripts, etc
*/
function fetchToStore(ctx, target, buildInfo, log) {
return __awaiter(this, void 0, void 0, function* () {
// download and untar
log('download-queued');
return buildInfo.fetch(path.join(target, '_'), { log, got: ctx.got });
// TODO: this is the point it becomes partially useable.
// ie, it can now be symlinked into .store/foo@1.0.0.
// it is only here that it should be available for ciruclar dependencies.
});
}
function buildInStore(ctx, target, buildInfo, log) {
return __awaiter(this, void 0, void 0, function* () {
const pkg = requireJson_1.default(path.resolve(path.join(target, '_', 'package.json')));
log('package.json', pkg);
yield linkBundledDeps_1.default(path.join(target, '_'));
const installedPkgs = yield installSubDeps(ctx, target, buildInfo, pkg, log);
// symlink itself; . -> node_modules/lodash@4.0.0
// this way it can require itself
yield symlinkSelf(target, pkg, buildInfo.keypath.length);
ctx.piq = ctx.piq || [];
ctx.piq.push({
path: target,
pkgId: buildInfo.id
});
return installedPkgs;
});
}
function installSubDeps(ctx, target, buildInfo, pkg, log) {
return __awaiter(this, void 0, void 0, function* () {
// recurse down to dependencies
log('dependencies');
return installMultiple_1.default(ctx, pkg.dependencies || {}, pkg.optionalDependencies || {}, path.join(target, '_', 'node_modules'), {
keypath: buildInfo.keypath.concat([buildInfo.id]),
dependent: buildInfo.id,
parentRoot: buildInfo.root,
optional: buildInfo.optional,
linkLocal: buildInfo.linkLocal,
root: buildInfo.installationRoot,
store: buildInfo.store,
force: buildInfo.force,
depth: buildInfo.depth,
tag: buildInfo.tag
});
});
}
/**
* Symlink a package into its own node_modules. this way, babel-runtime@5 can
* require('babel-runtime') within itself.
*/
function symlinkSelf(target, pkg, depth) {
return __awaiter(this, void 0, void 0, function* () {
debug(`symlinkSelf ${pkg.name}`);
if (depth === 0) {
return;
}
yield mkdirp_1.default(path.join(target, 'node_modules'));
const src = isScoped(pkg.name)
? path.join('..', '..', '_')
: path.join('..', '_');
yield relSymlink_1.default(src, path.join(target, 'node_modules', pkg.name));
});
}
function isScoped(pkgName) {
return pkgName.indexOf('/') !== -1;
}
/**
* Save promises for later
*/
function memoize(locks, key, fn) {
if (locks && locks[key])
return locks[key];
locks[key] = fn();
return locks[key];
}
//# sourceMappingURL=index.js.map