UNPKG

npm-link-up

Version:

Use this package to link your projects together for local development.

298 lines (297 loc) 12.3 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.runNPMLink = void 0; 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"); const 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((0, 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'); (0, utils_1.determineIfReinstallIsNeeded)(nm, dep, (0, 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); }; exports.runNPMLink = runNPMLink;