UNPKG

npm-link-up

Version:

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

352 lines (351 loc) 14.1 kB
#!/usr/bin/env node 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const util = require("util"); const assert = require("assert"); const path = require("path"); const chalk_1 = require("chalk"); const dashdash = require('dashdash'); const async = require("async"); const residence = require("residence"); const cwd = process.cwd(); let root = residence.findProjectRoot(cwd); const treeify = require('treeify'); const mkdirp = require("mkdirp"); const find_projects_1 = require("../../find-projects"); const map_paths_1 = require("../../map-paths"); const cache_clean_1 = require("../../cache-clean"); const logging_1 = require("../../logging"); const handle_options_1 = require("../../handle-options"); const cmd_line_opts_1 = require("./cmd-line-opts"); const run_link_1 = require("../../run-link"); const create_visual_tree_1 = require("../../create-visual-tree"); const get_clean_final_map_1 = require("../../get-clean-final-map"); const search_queue_1 = require("../../search-queue"); const utils_1 = require("../../utils"); process.once('exit', function (code) { if (code !== 0) { logging_1.default.warn('NLU is exiting with code:', code, '\n'); } else { logging_1.default.info('NLU is exiting with code:', code, '\n'); } }); const allowUnknown = process.argv.indexOf('--allow-unknown') > 0; let opts, globalConf, parser = dashdash.createParser({ options: cmd_line_opts_1.default, allowUnknown }); try { opts = parser.parse(process.argv); } catch (e) { logging_1.default.error(chalk_1.default.magenta('CLI parsing error:'), chalk_1.default.magentaBright.bold(e.message)); process.exit(1); } if (opts.help) { let help = parser.help({ includeEnv: true }).trimRight(); console.log('usage: nlu run [OPTIONS]\n' + 'options:\n' + help); process.exit(0); } try { globalConf = require(utils_1.globalConfigFilePath); } catch (err) { logging_1.default.warn('Could not load global config'); globalConf = {}; } if (!(globalConf && typeof globalConf === 'object')) { globalConf = {}; } if (Array.isArray(globalConf)) { globalConf = {}; } const { nluFilePath, nluConfigRoot } = utils_1.handleConfigCLIOpt(cwd, opts); let pkg, conf, hasNLUJSONFile = false; try { conf = require(nluFilePath); opts.umbrella = opts.umbrella || Boolean(conf.umbrella); hasNLUJSONFile = true; } catch (e) { if (!opts.umbrella) { logging_1.default.error('Could not load your .nlu.json file at this path:', chalk_1.default.bold(nluFilePath)); logging_1.default.error('Your project root is supposedly here:', chalk_1.default.bold(root)); logging_1.default.error(chalk_1.default.magentaBright(e.message)); process.exit(1); } opts.all_packages = true; conf = { 'npm-link-up': true, linkable: false, searchRoots: ['.'], list: [] }; } if (!root) { if (!(opts.all_packages || opts.umbrella)) { logging_1.default.warn('You do not appear to be within an NPM project (no package.json could be found).'); logging_1.default.warn(' => Your present working directory is =>', chalk_1.default.magenta.bold(cwd)); logging_1.default.warn('Perhaps you meant to use the', chalk_1.default.bold('--umbrella'), 'CLI option?'); process.exit(1); } root = cwd; } try { pkg = require(path.resolve(root + '/package.json')); } catch (e) { if (!(opts.umbrella || opts.all_packages)) { logging_1.default.error('Bizarrely, you do not seem to have a "package.json" file in the root of your project.'); logging_1.default.error('Your project root is supposedly here:', chalk_1.default.magenta(root)); logging_1.default.error(e.message); process.exit(1); } pkg = { name: '(root)' }; } if (Array.isArray(conf.packages)) { throw chalk_1.default.magenta(`"packages" property should be an object but no an array => ${util.inspect(conf)}`); } if ('packages' in conf) { assert.strictEqual(typeof conf.packages, 'object', `packages" property should be an object => ${util.inspect(conf)}`); } conf.packages = conf.packages || {}; conf.localSettings = conf.localSettings || {}; if (!(conf.localSettings && typeof conf.localSettings === 'object')) { conf.localSettings = {}; } if (Array.isArray(conf.localSettings)) { conf.localSettings = {}; } opts = Object.assign({}, utils_1.mapConfigObject(globalConf), utils_1.mapConfigObject(conf.localSettings), opts); if (!utils_1.validateOptions(opts)) { logging_1.default.error(chalk_1.default.bold('Your command line arguments were invalid, try:', chalk_1.default.magentaBright('nlu run --help'))); process.exit(1); } if (!utils_1.validateConfigFile(conf)) { console.error(); if (!opts.override) { logging_1.default.error(chalk_1.default.redBright('Your .nlu.json config file appears to be invalid. To override this, use --override.')); process.exit(1); } } const mainProjectName = pkg.name; if (!mainProjectName) { logging_1.default.error('Ummmm, your package.json file does not have a name property. Fatal.'); process.exit(1); } if (opts.verbosity > 0) { logging_1.default.info(`We are running the "npm-link-up" tool for your project named "${chalk_1.default.magenta(mainProjectName)}".`); } const productionDepsKeys = utils_1.getProdKeys(pkg); const allDepsKeys = utils_1.getDevKeys(pkg); const list = utils_1.getDepsListFromNluJSON(conf); if (list.length < 1) { if (!opts.all_packages) { logging_1.default.error(chalk_1.default.magenta(' => You do not have any dependencies listed in your .nlu.json file.')); logging_1.default.error(chalk_1.default.cyan.bold(util.inspect(conf))); process.exit(1); } } const searchRoots = handle_options_1.getSearchRoots(opts, conf); if (searchRoots.length < 1) { logging_1.default.error(chalk_1.default.red('No search-roots provided.')); logging_1.default.error('You should either update your .nlu.json config to have a searchRoots array.'); logging_1.default.error('Or you can use the --search-root=X option at the command line.'); logging_1.default.error(chalk_1.default.bold('Conveniently, you may include environment variables in your search root strings.')); logging_1.default.error('For example, to search everything starting from $HOME, you can use --search-root=$HOME option at the command line.'); logging_1.default.error('However, it is highly recommended to choose a subdirectory from $HOME, since searching that many files can take some time.'); process.exit(1); } const inListButNotInDeps = list.filter(item => { return !allDepsKeys.includes(item); }); inListButNotInDeps.forEach(item => { if (opts.verbosity > 1) { logging_1.default.warning('warning, the following item was listed in your .nlu.json file, ' + 'but is not listed in your package.json dependencies => "' + item + '".'); } }); const originalList = list.slice(0); if (!list.includes(mainProjectName)) { if (!opts.umbrella) { list.push(mainProjectName); } } const totalList = new Map(); list.forEach(l => { totalList.set(l, true); }); const ignore = handle_options_1.getIgnore(conf, opts); originalList.forEach((item) => { if (opts.verbosity > 0) { logging_1.default.info(`The following dep will be linked to this project => "${chalk_1.default.gray.bold(item)}".`); } }); const map = {}; if (opts.dry_run) { logging_1.default.warning(chalk_1.default.bold.gray('Because --dry-run was used, we are not actually linking projects together.')); } const mainDep = map[root] = { name: mainProjectName, bin: null, hasNLUJSONFile, isMainProject: true, linkToItself: conf.linkToItself, runInstall: conf.alwaysReinstall, path: root, deps: list, package: pkg, searchRoots: null, installedSet: new Set(), linkedSet: {} }; async.autoInject({ readNodeModulesFolders(cb) { const nm = path.resolve(root + '/node_modules'); const keys = opts.production ? productionDepsKeys : allDepsKeys; utils_1.determineIfReinstallIsNeeded(nm, mainDep, keys, opts, (err, val) => { if (err) { return cb(err); } if (val === true) { mainDep.runInstall = true; opts.install_main = true; } cb(null); }); }, ensureNodeModules(readNodeModulesFolders, cb) { if (!readNodeModulesFolders) { return process.nextTick(cb); } opts.install_main = true; mkdirp(path.resolve(root + '/node_modules'), cb); }, npmCacheClean(cb) { if (opts.dry_run) { return process.nextTick(cb); } if (!opts.clear_all_caches) { return process.nextTick(cb); } logging_1.default.info(`Cleaning the NPM cache.`); cache_clean_1.cleanCache(cb); }, mapSearchRoots(npmCacheClean, cb) { opts.verbosity > 3 && logging_1.default.info(`Mapping original search roots from your root project's "searchRoots" property.`); map_paths_1.mapPaths(searchRoots, nluConfigRoot, (err, roots) => { if (err) { return cb(err); } mainDep.searchRoots = roots.slice(0); cb(err, roots); }); }, findItems(mapSearchRoots, cb) { let searchRoots = mapSearchRoots.slice(0); if (opts.verbosity > 1) { logging_1.default.info('Beginning to search for NPM projects on your filesystem.'); } if (opts.verbosity > 3) { logging_1.default.info('NPM-Link-Up will be searching these roots for relevant projects:'); logging_1.default.info(chalk_1.default.magenta(util.inspect(searchRoots))); } if (opts.verbosity > 2) { logging_1.default.warning('Note that NPM-Link-Up may come across a project of yours that needs to search in directories'); logging_1.default.warning('not covered by your original search roots, and these new directories will be searched as well.'); } const status = { searching: true }; const findProject = find_projects_1.makeFindProject(mainProjectName, totalList, map, ignore, opts, status, conf); searchRoots.forEach(sr => { search_queue_1.q.push(cb => findProject(sr, cb)); }); if (search_queue_1.q.idle()) { return process.nextTick(cb, new Error('For some reason, no paths/items went onto the search queue.')); } let first = true; search_queue_1.q.error = search_queue_1.q.drain = (err) => { if (err) { status.searching = false; logging_1.default.error(chalk_1.default.magenta('There was a search queue processing error.')); } if (first) { search_queue_1.q.kill(); cb(err, { actuallyRan: true }); } first = false; }; }, runUtility(findItems, cb) { const unfound = Array.from(totalList.keys()).filter(v => { return !map[v]; }); if (false && unfound.length > 0) { logging_1.default.warn(`The following packages could ${chalk_1.default.bold('not')} be located:`); for (let i of unfound.keys()) { logging_1.default.warn(chalk_1.default.bold(String(i + 1), chalk_1.default.bold.green(unfound[i]))); } if (!opts.allow_missing) { console.error(); logging_1.default.warn('The following paths (and their subdirectories) were searched:'); find_projects_1.rootPaths.forEach((v, i) => { console.info('\t\t', `${chalk_1.default.blueBright.bold(String(i + 1))}.`, chalk_1.default.blueBright(v)); }); console.error(); logging_1.default.error('because the --allow-missing flag was not use, we are exiting.'); process.exit(1); } } let cleanMap; try { if (opts.all_packages) { cleanMap = get_clean_final_map_1.getCleanMapOfOnlyPackagesWithNluJSONFiles(mainProjectName, map); } else { cleanMap = get_clean_final_map_1.getCleanMap(mainDep, map, opts); } } catch (err) { return process.nextTick(cb, err); } if (opts.dry_run) { return process.nextTick(() => { cb(null, cleanMap); }); } logging_1.default.info('Beginning to actually link projects together...'); run_link_1.runNPMLink(cleanMap, opts, err => { cb(err, cleanMap); }); } }, (err, results) => { if (err) { logging_1.default.error('There was an error while running nlu run/add:'); logging_1.default.error(chalk_1.default.magenta(util.inspect(err.message || err))); return process.exit(1); } if (results.runUtility) { logging_1.default.good(chalk_1.default.green.underline('NPM-Link-Up run was successful. All done.')); } const cleanMap = results.runUtility; if (cleanMap && typeof cleanMap === 'object') { const treeObj = create_visual_tree_1.createTree(cleanMap, mainDep.path, mainDep, opts); const treeString = treeify.asTree(treeObj, true); const formattedStr = [''].concat(String(treeString).split('\n')).map(function (line) { return ' look here: \t' + line; }).concat(''); if (opts.verbosity > 1) { console.log(); logging_1.default.info(chalk_1.default.cyan.bold('NPM-Link-Up results as a visual:'), '\n'); console.log(chalk_1.default.white(formattedStr.join('\n'))); } } else { logging_1.default.warn('Missing map object; could not create dependency tree visualization.'); } setTimeout(function () { process.exit(0); }, 100); });