#!/usr/bin/env node 'use strict'; 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 ', 'modules in these paths will be tread as devDependencies') .option('-e, --exclude ', 'exclude parse directory, split by `,`') .option('-r, --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 ', 'modules that not require in source file, but you need them as dependencies') .option('-D, --devdep ', 'modules that not require in source file, but you need them in as devDependencies') .option('-k, --keep ', 'modules that you want to keep version in package.json file') .option('-s, --semver ', 'auto update these modules within the specified semver') .option('-n, --notransform', 'disable transfrom es next, don\'t support es6 modules') .option('-P, --plugin ', '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); }