UNPKG

nlu

Version:

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

307 lines (233 loc) 8.38 kB
'use strict'; import {EVCb, NluMap, NluMapItem, NLURunOpts, NLUAddOpts} from "./index"; //core import * as util from 'util'; import * as cp from 'child_process'; //npm import chalk from 'chalk'; import async = require('async'); //project import log from './logging'; interface BinFieldObject { [key: string]: string } /////////////////////////////////////////////////////////////////////////////////////////// export const runNPMLink = (map: NluMap, opts: any, cb: EVCb<any>) => { const keys = Object.keys(map); if (keys.length < 1) { return process.nextTick(cb, new Error('NLU could not find any dependencies on the filesystem;' + ' perhaps broaden your search using searchRoots.')); } if (opts.dry_run) { log.warning('given the --treeify option passed at the command line, npm-link-up will only print out the dependency tree and exit.'); log.veryGood('the following is a complete list of recursively related dependencies:\n'); log.veryGood(util.inspect(Object.keys(map))); return process.nextTick(cb); } if (opts.verbosity > 2) { log.good('Dependency map:'); } Object.keys(map).forEach(function (k) { if (opts.verbosity > 2) { log.info('Info for project:', chalk.bold(k)); console.log(chalk.green.bold(util.inspect(map[k]))); console.log(); } }); const isAllLinked = function () { //Object.values might not be available on all Node.js versions. return Object.keys(map).every(function (k) { return map[k].isLinked; }); }; const getCountOfUnlinkedDeps = function (dep: NluMapItem) { return dep.deps.filter(function (d) { if (!map[d]) { log.warning(`there is no dependency named ${d} in the map.`); return false; } return !map[d].isLinked; }) .length; }; const findNextDep = function () { // not anymore: this routine finds the next dep, that has no deps or no unlinked dependencies // this routine finds the next dep with the fewest number of unlinked dependencies let dep; let count = null; for (let name in map) { if (map.hasOwnProperty(name)) { let d = map[name]; if (!d.isLinked) { if (!count) { dep = d; count = dep.deps.length; } else if (getCountOfUnlinkedDeps(d) < count) { dep = d; count = dep.deps.length; } } } } if (!dep) { log.error('Internal implementation error => no dep found,\nbut there should be at least one yet-to-be-linked dep.'); return process.exit(1); } return dep; }; const getNPMLinkList = (deps: Array<string>) => { return deps.filter(function (d) { if (!map[d]) { log.warning('Map for key => "' + d + '" is not defined.'); return false; } return map[d] && map[d].isLinked; }) .map(function (d: string) { const path = map[d].path; const bin = map[d].bin; // return ` npm link ${d} -f `; return ` mkdir -p node_modules && rm -rf "node_modules/${d}" && mkdir -p "node_modules/${d}" && rm -rf "node_modules/${d}" ` + ` && ln -s "${path}" "node_modules/${d}" ` + ` ${getBinMap(bin, path, d)}`; }); }; const getBinMap = function (bin: string | BinFieldObject, path: string, name: string) { if (!bin) { return ''; } if (typeof bin === 'string') { return ` && mkdir -p "node_modules/.bin" && ln -s "${path}/${bin}" "node_modules/.bin/${name}" ` } const keys = Object.keys(bin); if (keys.length < 1) { return ''; } return ` && ` + keys.map(function (k) { return ` mkdir -p node_modules/.bin && ln -sf "${path}/${bin[k]}" "node_modules/.bin/${k}" ` }) .join(' && '); }; const getCommandListOfLinked = function (name: string) { const path = map[name] && map[name].path; const bin = map[name] && map[name].bin; if (!path) { log.error(`missing path for dependency with name "${name}"`); return process.exit(1); } return Object.keys(map).filter(function (k) { return map[k].isLinked && map[k].deps.includes(name); }) .map(function (k) { const p = `${map[k].path}`; return ` cd ${p} && mkdir -p node_modules && rm -rf "node_modules/${name}" && mkdir -p "node_modules/${name}" ` + ` && rm -rf "node_modules/${name}" && ln -s "${path}" "node_modules/${name}" ` + ` ${getBinMap(bin, path, name)} `; }); }; const getInstallCommand = function (dep: NluMapItem) { if (!opts.no_install && (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 = function (dep: NluMapItem) { if (!opts.no_link && (opts.self_link_all || dep.linkToItself === true)) { // return `&& npm link ${String(dep.name).trim()} -f` return ` && mkdir -p node_modules && rm -rf "node_modules/${dep.name}" && mkdir -p "node_modules/${dep.name}" ` + ` && rm -rf "node_modules/${dep.name}" ` + ` && ln -s "${dep.path}" "node_modules/${dep.name}" `; } }; const getGlobalLinkCommand = function (dep: NluMapItem) { if (!opts.no_link && (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, function (cb: EVCb<any>) { if (opts.verbosity > 2) { log.info(`Searching for next dep to run.`); } const dep = findNextDep(); if (opts.verbosity > 1) { log.good(`Processing dep with name => '${chalk.bold(dep.name)}'.`); } const deps = getNPMLinkList(dep.deps); 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 > 1) { log.info(`First-pass script is => "${chalk.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', function (code) { if (code > 0 && /ERR/i.test(stderr)) { log.error(`Dep with name "${dep.name}" is done, but with an error.`); return cb({code, dep, error: stderr}); } dep.isLinked = map[dep.name].isLinked = true; const linkPreviouslyUnlinked = function (cb: EVCb<any>) { const cmds = getCommandListOfLinked(dep.name); if (!cmds.length) { return process.nextTick(cb); } const cmd = cmds.join(' && '); if (opts.verbosity > 1) { log.info(`Running this command for "${chalk.bold(dep.name)}" => '${chalk.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(function (err: any) { if (err) { log.error(`Dep with name "${dep.name}" is done, but with an error => `, err.message || err); } else { if (opts.verbosity > 1) { log.veryGood(`Dep with name '${chalk.bold(dep.name)}' is done.`); } } cb(err, { code: code, dep: dep, error: stderr }); }); }); }, cb); };