npm-link-up
Version:
Use this package to link your projects together for local development.
296 lines (295 loc) • 12.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const util = require("util");
const cp = require("child_process");
const chalk_1 = require("chalk");
const async = require("async");
const logging_1 = require("./logging");
const utils_1 = require("./utils");
const path = require("path");
exports.runNPMLink = (map, opts, cb) => {
const keys = Object.keys(map);
if (keys.length < 1) {
return process.nextTick(cb, 'NLU could not find any dependencies on the filesystem;' +
' perhaps broaden your search using searchRoots.');
}
if (opts.dry_run) {
logging_1.default.warning('given the --treeify option passed at the command line, npm-link-up will only print out the dependency tree and exit.');
logging_1.default.veryGood('the following is a complete list of recursively related dependencies:\n');
logging_1.default.veryGood(util.inspect(Object.keys(map)));
return process.nextTick(cb);
}
if (opts.verbosity > 2) {
logging_1.default.info('Dependency map:');
}
Object.keys(map).forEach(function (k) {
if (opts.verbosity > 2) {
logging_1.default.info('Info for project:', chalk_1.default.bold(k));
console.log(chalk_1.default.green.bold(util.inspect(map[k])));
console.log();
}
});
const isAllLinked = function () {
return Object.keys(map).every(k => map[k].isLinked);
};
const getCountOfUnlinkedDeps = (dep) => {
return dep.deps.filter(d => {
if (!map[d]) {
logging_1.default.warning(`there is no dependency named '${d}' in the map.`);
return false;
}
return !map[d].isLinked;
}).length;
};
const findNextDep = function () {
let dep;
let count = null;
for (let dir of Object.keys(map)) {
let d = map[dir];
if (!d.isLinked) {
if (!count) {
dep = d;
count = dep.deps.length;
}
else if (getCountOfUnlinkedDeps(d) < count) {
dep = d;
count = dep.deps.length;
}
}
}
if (!dep) {
logging_1.default.error('Internal implementation error => no dep found, but there should be at least one yet-to-be-linked dep.');
return process.exit(1);
}
return dep;
};
const getNPMLinkList = (dep) => {
if (opts.umbrella && dep.isMainProject) {
return [];
}
const deps = dep.deps.map(utils_1.getPath(map, dep, opts));
const isAccessible = (path) => {
const searchRoots = dep.searchRoots;
const matched = searchRoots.some(r => path.startsWith(r));
if (!matched) {
logging_1.default.error('The following dep', path, 'is not accessible for project at path:', dep.path);
}
return matched;
};
return deps.filter(Boolean).filter(d => {
if (!map[d]) {
logging_1.default.warning('Map for key => "' + d + '" is not defined.');
return false;
}
return map[d] && map[d].isLinked && isAccessible(map[d].path);
})
.map((d) => {
const { path, name, bin } = map[d];
if (dep.installedSet.has(name)) {
throw new Error('Already installed a package with name: ' + name + ', into dep: ' + util.inspect(dep));
}
dep.installedSet.add(name);
if (dep.linkedSet[path]) {
throw new Error(`Already linked ${path} to dep: ` + util.inspect(dep));
}
dep.linkedSet[path] = map[d];
if (path !== d) {
throw new Error('The "path" field and the map entry should be the same.');
}
return ` mkdir -p "node_modules/${name}" &&
rm -rf "node_modules/${name}" &&
mkdir -p "node_modules/${name}" &&
rm -rf "node_modules/${name}" &&
ln -sf "${path}" "node_modules/${name}" ${getBinMap(bin, path, name)} `;
});
};
const getBinMap = (bin, path, name) => {
if (!bin) {
return '';
}
if (typeof bin === 'string') {
return ` && mkdir -p "node_modules/.bin" && ln -sf "${path}/${bin}" "node_modules/.bin/${name}" `;
}
const keys = Object.keys(bin);
if (keys.length < 1) {
return '';
}
return ` && ` + keys.map(k => {
return ` mkdir -p "node_modules/.bin" && ln -sf "${path}/${bin[k]}" "node_modules/.bin/${k}" `;
})
.join(' && ');
};
const getCommandListOfLinked = (dep) => {
const name = dep.name;
const path = dep.path;
const bin = map[path] && map[path].bin;
if (!path) {
logging_1.default.error(`missing "path" field for dependency with name "${name}"`);
return process.exit(1);
}
if (!bin) {
logging_1.default.warn(`missing "bin" field for dependency with name "${name}"`);
}
if (dep.bin !== bin) {
throw new Error('"bin" fields do not match => ' + util.inspect(dep));
}
const isAccessible = (dep) => {
const searchRoots = dep.searchRoots;
return searchRoots.some(r => path.startsWith(r));
};
return Object.keys(map).filter(k => {
return map[k].isLinked && map[k].deps.includes(name) && isAccessible(map[k]);
})
.map(k => {
const p = `${map[k].path}`;
if (map[k].installedSet.has(name)) {
throw new Error('Already installed a package with name: ' + name + ', into dep: ' + util.inspect(map[k]));
}
map[k].installedSet.add(name);
if (map[k].linkedSet[path]) {
throw new Error(`Already linked ${path} to dep: ` + util.inspect(map[k]));
}
map[k].linkedSet[path] = dep;
if (p !== k) {
throw new Error(`Paths should be the same: [1] '${p}', [2] '${k}'.`);
}
return ` cd "${p}" &&
mkdir -p "node_modules/${name}" &&
rm -rf "node_modules/${name}" &&
mkdir -p "node_modules/${name}" &&
rm -rf "node_modules/${name}" &&
ln -sf "${path}" "node_modules/${name}" ${getBinMap(bin, path, name)} `;
});
};
const getInstallCommand = (dep) => {
if (opts.umbrella && dep.isMainProject === true) {
return;
}
if (opts.no_install) {
return;
}
if (dep.runInstall || opts.install_all || (dep.isMainProject && opts.install_main)) {
const installProd = opts.production ? ' --production ' : '';
return ` && rm -rf node_modules && npm install --cache-min 999999 --loglevel=warn ${installProd}`;
}
};
const getLinkToItselfCommand = (dep) => {
if (opts.umbrella && dep.isMainProject) {
return;
}
if (opts.no_link) {
return;
}
if (opts.self_link_all || dep.linkToItself === true) {
return ` && mkdir -p "node_modules/${dep.name}" ` +
` && rm -rf "node_modules/${dep.name}" && mkdir -p "node_modules/${dep.name}" ` +
` && rm -rf "node_modules/${dep.name}" ` +
` && ln -sf "${dep.path}" "node_modules/${dep.name}" `;
}
};
const getGlobalLinkCommand = (dep) => {
if (opts.umbrella && dep.isMainProject) {
return;
}
if (opts.no_link) {
return;
}
if (opts.link_all || (dep.isMainProject && opts.link_main)) {
const installProd = opts.production ? ' --production ' : '';
return ` && mkdir -p "node_modules/.bin" && npm link --cache-min 999999 -f ${installProd} `;
}
};
async.until(isAllLinked, (cb) => {
if (opts.verbosity > 2) {
logging_1.default.info(`Searching for next dep to run.`);
}
const dep = findNextDep();
if (opts.verbosity > 1) {
logging_1.default.info(`Processing dep with name => '${chalk_1.default.bold(dep.name)}'.`);
}
const nm = path.resolve(dep.path + '/node_modules');
utils_1.determineIfReinstallIsNeeded(nm, dep, utils_1.getDevKeys(dep.package), opts, (err, val) => {
if (err) {
logging_1.default.warn(`Error generated from determining if npm (re)install was is necessary for package: ${dep.name} =>`, err);
}
if (val === true) {
dep.runInstall = true;
}
const deps = getNPMLinkList(dep);
const links = deps.length > 0 ? ' && ' + deps.join(' && ') : '';
const script = [
`cd "${dep.path}"`,
getInstallCommand(dep),
getGlobalLinkCommand(dep),
links,
getLinkToItselfCommand(dep)
]
.filter(Boolean)
.join(' ');
if (opts.verbosity > 2) {
logging_1.default.info(`First-pass script is => "${chalk_1.default.blueBright.bold(script)}"`);
}
const k = cp.spawn('bash', [], {
env: Object.assign({}, process.env, {
NPM_LINK_UP: 'yes'
})
});
k.stdin.end(script);
k.stdout.setEncoding('utf8');
k.stderr.setEncoding('utf8');
k.stderr.pipe(process.stderr);
if (opts.verbosity > 2) {
k.stdout.pipe(process.stdout);
}
let stderr = '';
k.stderr.on('data', function (d) {
stderr += d;
});
k.once('error', cb);
k.once('exit', code => {
if (code > 0 && /ERR/i.test(stderr)) {
logging_1.default.error(`Dep with name "${dep.name}" is done, but with an error.`);
return cb({ code, dep, error: stderr });
}
dep.isLinked = map[dep.path].isLinked = true;
const linkPreviouslyUnlinked = function (cb) {
const cmds = getCommandListOfLinked(dep);
if (!cmds.length) {
return process.nextTick(cb);
}
const cmd = cmds.join(' && ');
if (opts.verbosity > 2) {
logging_1.default.info(`Running this command for "${chalk_1.default.bold(dep.name)}" => '${chalk_1.default.blueBright(cmd)}'.`);
}
const k = cp.spawn('bash', [], {
env: Object.assign({}, process.env, {
NPM_LINK_UP: 'yes'
})
});
k.stdin.write(cmd);
k.stdout.setEncoding('utf8');
k.stderr.setEncoding('utf8');
k.stderr.pipe(process.stderr, { end: false });
if (opts.verbosity > 2) {
k.stdout.pipe(process.stdout, { end: false });
}
k.stdin.end();
k.once('exit', cb);
};
linkPreviouslyUnlinked((err) => {
if (err) {
logging_1.default.error(`Dep with name "${dep.name}" is done, but with an error => `, err.message || err);
}
else if (opts.verbosity > 1) {
logging_1.default.veryGood(`Dep with name '${chalk_1.default.bold(dep.name)}' is done.`);
}
cb(err, {
code: code,
dep: dep,
error: stderr
});
});
});
});
}, cb);
};