autod
Version:
auto generate dependencies
344 lines (309 loc) • 10.5 kB
JavaScript
const resolve = require('path').resolve;
const pkg = require('../package.json');
const printable = require('printable');
const minimatch = require('minimatch');
const program = require('commander');
const pjoin = require('path').join;
const util = require('util');
const path = require('path');
const Autod = require('..');
const fs = require('fs');
require('colors');
const argv = program
.version(pkg.version)
.option('-p, --path [root path]', 'the root path to be parse', '.')
.option('-t, --test <test/benchmark/example directory paths>', 'modules in these paths will be tread as devDependencies')
.option('-e, --exclude <exclude directory path>', 'exclude parse directory, split by `,`')
.option('-r, --registry <remote registry>', 'get latest version from which registry')
.option('-f, --prefix [version prefix]', 'version prefix, can be `~` or `^`')
.option('-F, --devprefix [dev dependencies version prefix]', 'develop dependencies version prefix, can be `~` or `^`')
.option('-w, --write', 'write dependencies into package.json')
.option('-i, --ignore', 'ignore errors, display the dependencies or write the dependencies.')
.option('-m, --map', 'display all the dependencies require by which file')
.option('-d, --dep <dependently modules>', 'modules that not require in source file, but you need them as dependencies')
.option('-D, --devdep <dev dependently modules>', 'modules that not require in source file, but you need them in as devDependencies')
.option('-k, --keep <dependently modules>', 'modules that you want to keep version in package.json file')
.option('-s, --semver <dependencies@version>', 'auto update these modules within the specified semver')
.option('-n, --notransform', 'disable transfrom es next, don\'t support es6 modules')
.option('-P, --plugin <name>', 'plugin module name')
.option('--check', 'check missing dependencies and devDependencies')
.parse(process.argv);
let options = {};
let confPath;
try {
confPath = require.resolve(path.resolve('.autod.conf'));
options = require(confPath);
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') {
console.error('load config %s error:', confPath);
console.error(err.stack);
}
// ignore
}
for (const key in argv) {
if (argv[key] !== undefined) {
options[key] = argv[key];
}
}
[ 'exclude', 'dep', 'devdep', 'test', 'keep' ].forEach(function(key) {
options[key] = split(options[key]);
});
options.semver = processSemver(options.semver);
if (options.prefix && options.prefix !== '^') {
options.prefix = '~';
}
if (options.devprefix && options.devprefix !== '^') {
options.devprefix = '~';
}
options.root = options.path = options.path || '.';
// don't write when check
if (options.check) options.write = false;
// get registry from pacakge.json
// default to Chinese Mirror
if (!options.registry) {
let modulePackage;
try {
modulePackage = fs.readFileSync('package.json', 'utf8');
modulePackage = JSON.parse(modulePackage);
} catch (err) {
modulePackage = {};
}
options.registry = 'https://registry.npmjs.org';
// get from npm env
if (process.env.npm_config_registry) options.registry = process.env.npm_config_registry;
// get from package.json
if (modulePackage.publishConfig && modulePackage.publishConfig.registry) {
options.registry = modulePackage.publishConfig.registry;
}
}
const autod = new Autod(options);
if (options.check) {
const allDeps = autod.findDependencies();
const result = { dependencies: {}, devDependencies: {} };
allDeps.dependencies.forEach(dep => {
result.dependencies[dep] = true;
});
allDeps.devDependencies.forEach(dep => {
result.devDependencies[dep] = true;
});
comparePackage(result);
} else {
autod.findVersions().then(result => {
result.map = autod.dependencyMap;
if (autod.errors.length) {
autod.errors.forEach(err => {
console.error('[ERROR]'.red, err.message);
});
if (!options.ignore) process.exit(1);
}
log('\n[DEPENDENCIES]\n'.green);
comparePackage(result);
if (options.map) {
log('\n[DEPENDENCY MAP]'.green);
printDependencyMap(result.map);
}
});
}
function comparePackage(result) {
let pkgInfo;
let pkgStr;
let pkgExist = true;
const pkgPath = pjoin(resolve(options.path), 'package.json');
// add prefix
if (options.prefix) {
for (const key in result.dependencies) {
result.dependencies[key] = options.prefix + result.dependencies[key];
}
}
const devprefix = options.devprefix ? options.devprefix : options.prefix;
if (devprefix) {
for (const key in result.devDependencies) {
result.devDependencies[key] = devprefix + result.devDependencies[key];
}
}
try {
pkgInfo = require(pkgPath);
pkgStr = fs.readFileSync(pkgPath, 'utf-8');
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
pkgInfo = {};
pkgExist = false;
} else {
log(output(result));
console.error('`package.json` parsed error: %s', err.message);
process.exit(1);
}
}
if (!pkgExist) {
log(output(result));
if (options.write) {
console.log('[WARN]'.yellow + ' `package.json` not exist, auto generate and write dependencies.');
fs.writeFileSync(pkgPath, '{\n' + output(result) + '\n}\n', 'utf-8');
}
process.exit(0);
}
if (options.keep) {
// keep these modules version, won't change by autod
options.keep.forEach(function(key) {
for (const pkgKey in result.dependencies) {
if (minimatch(pkgKey, key)) {
delete result.dependencies[pkgKey];
}
}
for (const pkgKey in result.devDependencies) {
if (minimatch(pkgKey, key)) {
delete result.devDependencies[pkgKey];
}
}
const dependencies = pkgInfo.dependencies;
const devDependencies = pkgInfo.devDependencies;
for (const pkgKey in dependencies) {
if (minimatch(pkgKey, key)) {
result.dependencies[pkgKey] = dependencies[pkgKey];
}
}
for (const pkgKey in devDependencies) {
if (minimatch(pkgKey, key)) {
result.devDependencies[key] = devDependencies[key];
}
}
});
}
if (pkgInfo.dependencies) {
pkgStr = pkgStr.replace(/( |\t)*"dependencies"\s*:\s*{(.|\n)*?}/,
outputDep('dependencies', result.dependencies));
} else {
pkgStr = pkgStr.replace(/(\s*)(\}\n*\s*)$/, function(end, before, after) {
return ',' + before + outputDep('dependencies', result.dependencies) + '\n' + after;
});
}
if (pkgInfo.devDependencies) {
// merge parsed into devDependencies
for (const key in pkgInfo.devDependencies) {
if (!result.devDependencies[key]) {
result.devDependencies[key] = pkgInfo.devDependencies[key];
}
}
pkgStr = pkgStr.replace(/( |\t)*"devDependencies"\s*:\s*{(.|\n)*?}/,
outputDep('devDependencies', result.devDependencies));
} else {
pkgStr = pkgStr.replace(/(\s*)(\}\n*\s*)$/, function(end, before, after) {
return ',' + before + outputDep('devDependencies', result.devDependencies) + '\n' + after;
});
}
log(output(result));
printUpdates('Dependencies', result.dependencies, pkgInfo.dependencies, true);
printUpdates('DevDependencies', result.devDependencies, pkgInfo.devDependencies);
if (options.write) {
console.log('[INFO]'.green + ' Write dependencies into package.json.');
fs.writeFileSync(pkgPath, pkgStr, 'utf-8');
}
if (options.check) {
const missingDependencies = [];
const missingDevDependencies = [];
const deps = result.dependencies || {};
const old = pkgInfo.dependencies || {};
for (const key in deps) {
if (!old[key]) {
missingDependencies.push(key);
}
}
const devDeps = result.devDependencies || {};
const devOld = pkgInfo.devDependencies || {};
for (const key in devDeps) {
if (!devOld[key] && !old[key]) {
missingDevDependencies.push(key);
}
}
if (missingDependencies.length > 0) {
console.error('[ERROR]'.red + ' Missing dependencies: ' +
JSON.stringify(missingDependencies) + '\n');
}
if (missingDevDependencies.length > 0) {
console.error('[ERROR]'.red + ' Missing devDependencies: ' +
JSON.stringify(missingDevDependencies) + '\n');
}
if (missingDependencies.length > 0 || missingDevDependencies.length > 0) {
process.exit(1);
}
}
}
function processSemver(semver) {
semver = semver || [];
if (typeof semver === 'string') {
semver = semver.split(/\s*,\s*/);
}
const map = {};
semver.forEach(function(m) {
let prefix = '';
if (m.indexOf('@') === 0) {
prefix = '@';
m = m.slice(1);
}
m = m.split('@');
if (m.length !== 2) return;
map[prefix + m[0]] = m[1];
});
return map;
}
function outputDep(name, values) {
let str = util.format(' "%s": {\n', name);
const deps = [];
for (const key in values) {
deps.push(util.format(' "%s": "%s"', key, values[key]));
}
str += deps.sort().join(',\n') + '\n }';
return str;
}
function output(result) {
let str = outputDep('dependencies', result.dependencies);
if (!Object.keys(result.devDependencies).length) {
return str;
}
str += ',\n' + outputDep('devDependencies', result.devDependencies);
return str;
}
function printUpdates(title, latest, old, remove) {
latest = latest || {};
old = old || {};
const arr = [[ 'Package Name', 'Old Version', 'Latest Version' ]];
for (const key in latest) {
if (!old[key]) {
arr.push([ key, '-', latest[key] ]);
} else if (old[key] !== latest[key]) {
arr.push([ key, old[key], latest[key] ]);
}
}
if (remove) {
for (const key in old) {
if (!latest[key]) {
arr.push([ key, old[key], 'remove' ]);
}
}
}
if (arr.length > 1) {
log((title + ' updates').yellow + '\n');
log(printable.print(arr));
log();
} else {
log(('nothing to update in ' + title).green + '\n');
}
}
function printDependencyMap(map) {
Object.keys(map).sort().forEach(function(name) {
console.log((' ' + name).cyan);
console.log((' ' + map[name].join('\n ')).grey);
});
}
function split(str) {
if (typeof str === 'string') {
return str.split(/\s*,\s*/);
}
return str;
}
function log() {
if (options.check) return;
console.log.apply(console, arguments);
}
;