nlu
Version:
Use this package to link your projects together for local development.
267 lines (206 loc) • 7.77 kB
text/typescript
;
//core
import * as path from 'path';
import * as fs from 'fs';
//npm
import chalk from 'chalk';
const dashdash = require('dashdash');
import async = require('async');
import residence = require('residence');
import mkdirp = require('mkdirp');
//project
import options from "./cmd-line-opts";
import {EVCb, NLUAddOpts, NluConf, NLUInitOpts} from "../../index";
import log from '../../logging';
const cwd = process.cwd();
const root = residence.findProjectRoot(cwd);
import {makeFindProjects} from "./find-matching-projects";
import alwaysIgnore from './add-ignore';
import alwaysIgnoreThese from "../../always-ignore";
import {mapPaths} from "../../map-paths-with-env-vars";
import {runNPMLink} from "../../run-link";
import {getCleanMap} from "../../get-clean-final-map";
import {validateConfigFile} from "../../utils";
process.once('exit', code => {
log.info('Exiting with code:', code,'\n');
});
if (!root) {
log.error('Cannot find a project root given your current working directory:', chalk.magenta(cwd));
log.error(' => NLU could not find a package.json file within your cwd.');
process.exit(1);
}
const allowUnknown = process.argv.indexOf('--allow-unknown') > 0;
let opts: NLUAddOpts, parser = dashdash.createParser({options, allowUnknown});
try {
opts = parser.parse(process.argv);
} catch (e) {
log.error(chalk.magenta(' => CLI parsing error:'), chalk.magentaBright.bold(e.message));
process.exit(1);
}
if (opts.help) {
let help = parser.help({includeEnv: true}).trimRight();
console.log('usage: nlu add [OPTIONS]\n' + 'options:\n' + help);
process.exit(0);
}
let pkgJSON: any;
try {
pkgJSON = require(path.resolve(root + '/package.json'));
}
catch (err) {
log.error(err);
log.error('Could not load your projects package.json file.');
log.error('No package.json file could be found in path:', root);
process.exit(1);
}
const flattenDeep = function (arr1: Array<any>): Array<any> {
return arr1.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []);
};
const projectsToAdd = flattenDeep([opts._args]).map(v => String(v || '').trim()).filter(Boolean);
const absolutePaths = projectsToAdd.filter(v => path.isAbsolute(v));
if (absolutePaths.length > 0) {
throw `NLU cannot currently handle file paths. Instead, use 'nlu add one two three', and nlu will find them on your fs.`;
}
if (projectsToAdd.length < 1) {
log.error('You did not pass any projects to add. Try: nlu add foo');
process.exit(1);
}
projectsToAdd.forEach(v => {
log.info('nlu will add a symlink to this project:', v);
});
const mainProjectName = pkgJSON.name;
if (!mainProjectName) {
log.error('Your current project does not have a name (no name property in package.json.');
log.error('That is weird.');
}
else {
log.info('Your project name is:', chalk.bold.blueBright(mainProjectName));
}
let nluJSON: NluConf, nluJSONPath = path.resolve(root + '/.nlu.json');
try {
nluJSON = require(nluJSONPath);
}
catch (err) {
log.error('Cannot find an .nlu.json config file in your project.');
log.error('Your project path is:', root);
throw err.message;
}
if(!validateConfigFile(nluJSON)){
log.error('Your .nlu.json config file appears to be invalid. To override this, use --override.');
if(!opts.override){
process.exit(1);
}
}
let searchRoots = nluJSON.searchRoots;
if (opts.search_from_home && opts.search_root) {
log.error('You passed the --search-from-home option along with --search/--search-root.');
process.exit(1);
}
if (opts.search_from_home) {
searchRoots = [process.env.HOME];
}
if (opts.search_root) {
searchRoots = flattenDeep([opts.search_root]).map(v => String(v || '').trim()).filter(Boolean);
}
if (!(searchRoots && searchRoots.length > 0)) {
log.warn(`Using $HOME to search for related projects. To limit your fs search, use the --search=<path> option.`);
searchRoots = [process.env.HOME];
if (!opts.search_from_home) {
log.warn('Please use --search=<path> to confine your project search to something less wide as user home.');
log.warn('For multiple search roots, you can use --search more than once.');
log.warn(`If you wish to use $HOME as the search root, use ${chalk.bold('--search-from-home')},`,
`but be aware that it can take a long time to search through user home.`);
process.exit(1);
}
}
else {
if (opts.search_from_home) {
log.error('You passed the --search-from-home option along with --search/--search-root.');
process.exit(1);
}
}
const ignore = alwaysIgnore.concat(alwaysIgnoreThese)
.filter((item, index, arr) => arr.indexOf(item) === index)
.map(item => new RegExp(item));
async.autoInject({
ensureNodeModules(cb: EVCb<any>){
mkdirp(path.resolve(root + '/node_modules'), cb);
},
mapSearchRoots(cb: EVCb<Array<string>>) {
opts.verbosity > 3 && log.info(`Mapping original search roots from your root project's "searchRoots" property.`);
mapPaths(searchRoots, cb);
},
getMatchingProjects(mapSearchRoots: Array<string>, cb: EVCb<any>) {
const map = {}, status = {searching: true};
const findProjects = makeFindProjects(mainProjectName, ignore, opts, map, projectsToAdd.slice(0), status);
const q = async.queue(function (task: any, cb) {
task(cb);
});
log.info('Search roots are:', mapSearchRoots);
mapSearchRoots.forEach((v: string) => {
q.push(function (cb: EVCb<any>) {
findProjects(v, cb);
});
});
if (q.idle()) {
return process.nextTick(cb, new Error('For some reason no items/paths ended up on the search queue.'));
}
let first = true;
q.drain = q.error = function (err?: any) {
if (err) {
status.searching = false;
log.error(chalk.magenta('There was a search queue processing error.'));
}
if (first) {
q.kill();
cb(err, map);
}
first = false;
};
},
runUtility(ensureNodeModules: any, getMatchingProjects: any, cb: EVCb<any>) {
try {
nluJSON.list = nluJSON.list.concat(projectsToAdd)
.map(v => String(v || '').trim()).filter((v,i,a) => a.indexOf(v) === i);
}
catch (e) {
return process.nextTick(cb, e);
}
getMatchingProjects[mainProjectName] = {
name: mainProjectName,
bin: pkgJSON.bin || null,
isMainProject: true,
linkToItself: Boolean(nluJSON.linkToItself),
runInstall: Boolean(nluJSON.alwaysReinstall),
path: root,
deps: nluJSON.list
};
let cleanMap;
try {
cleanMap = getCleanMap(mainProjectName, getMatchingProjects);
}
catch (err) {
return process.nextTick(cb, err);
}
opts.verbosity > 1 && log.good('Beginning to actually link projects together...');
runNPMLink(cleanMap, opts, cb);
},
addToNLUJSON(runUtility: any, cb: EVCb<any>) {
// nluJSON.list = Object.keys(projectsToAdd)
// .concat(nluJSON.list)
// .filter((v, i, a) => a.indexOf(v) === i);
const newNluJSONstr = JSON.stringify(nluJSON, null, 2);
fs.writeFile(nluJSONPath, newNluJSONstr, 'utf8', cb);
},
},
function (err: any, results: any) {
if (err) {
log.error('There was an error when running "nlu add".');
log.error('Here were your command line arguments used:');
process.argv.forEach((v, i) => log.info(chalk.gray.bold(String(i)), chalk.gray(v)));
log.error(err.message || err);
return process.exit(1);
}
log.veryGood(chalk.green('Looks like the nlu init routine succeeded. ') +
'Check your new .nlu.json file in the root of your project.');
process.exit(0);
});