autod.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. #!/usr/bin/env node
  2. 'use strict';
  3. const resolve = require('path').resolve;
  4. const pkg = require('../package.json');
  5. const printable = require('printable');
  6. const minimatch = require('minimatch');
  7. const program = require('commander');
  8. const pjoin = require('path').join;
  9. const util = require('util');
  10. const path = require('path');
  11. const Autod = require('..');
  12. const fs = require('fs');
  13. require('colors');
  14. const argv = program
  15. .version(pkg.version)
  16. .option('-p, --path [root path]', 'the root path to be parse', '.')
  17. .option('-t, --test <test/benchmark/example directory paths>', 'modules in these paths will be tread as devDependencies')
  18. .option('-e, --exclude <exclude directory path>', 'exclude parse directory, split by `,`')
  19. .option('-r, --registry <remote registry>', 'get latest version from which registry')
  20. .option('-f, --prefix [version prefix]', 'version prefix, can be `~` or `^`')
  21. .option('-F, --devprefix [dev dependencies version prefix]', 'develop dependencies version prefix, can be `~` or `^`')
  22. .option('-w, --write', 'write dependencies into package.json')
  23. .option('-i, --ignore', 'ignore errors, display the dependencies or write the dependencies.')
  24. .option('-m, --map', 'display all the dependencies require by which file')
  25. .option('-d, --dep <dependently modules>', 'modules that not require in source file, but you need them as dependencies')
  26. .option('-D, --devdep <dev dependently modules>', 'modules that not require in source file, but you need them in as devDependencies')
  27. .option('-k, --keep <dependently modules>', 'modules that you want to keep version in package.json file')
  28. .option('-s, --semver <dependencies@version>', 'auto update these modules within the specified semver')
  29. .option('-n, --notransform', 'disable transfrom es next, don\'t support es6 modules')
  30. .option('-P, --plugin <name>', 'plugin module name')
  31. .option('--check', 'check missing dependencies and devDependencies')
  32. .parse(process.argv);
  33. let options = {};
  34. let confPath;
  35. try {
  36. confPath = require.resolve(path.resolve('.autod.conf'));
  37. options = require(confPath);
  38. } catch (err) {
  39. if (err.code !== 'MODULE_NOT_FOUND') {
  40. console.error('load config %s error:', confPath);
  41. console.error(err.stack);
  42. }
  43. // ignore
  44. }
  45. for (const key in argv) {
  46. if (argv[key] !== undefined) {
  47. options[key] = argv[key];
  48. }
  49. }
  50. [ 'exclude', 'dep', 'devdep', 'test', 'keep' ].forEach(function(key) {
  51. options[key] = split(options[key]);
  52. });
  53. options.semver = processSemver(options.semver);
  54. if (options.prefix && options.prefix !== '^') {
  55. options.prefix = '~';
  56. }
  57. if (options.devprefix && options.devprefix !== '^') {
  58. options.devprefix = '~';
  59. }
  60. options.root = options.path = options.path || '.';
  61. // don't write when check
  62. if (options.check) options.write = false;
  63. // get registry from pacakge.json
  64. // default to Chinese Mirror
  65. if (!options.registry) {
  66. let modulePackage;
  67. try {
  68. modulePackage = fs.readFileSync('package.json', 'utf8');
  69. modulePackage = JSON.parse(modulePackage);
  70. } catch (err) {
  71. modulePackage = {};
  72. }
  73. options.registry = 'https://registry.npmjs.org';
  74. // get from npm env
  75. if (process.env.npm_config_registry) options.registry = process.env.npm_config_registry;
  76. // get from package.json
  77. if (modulePackage.publishConfig && modulePackage.publishConfig.registry) {
  78. options.registry = modulePackage.publishConfig.registry;
  79. }
  80. }
  81. const autod = new Autod(options);
  82. if (options.check) {
  83. const allDeps = autod.findDependencies();
  84. const result = { dependencies: {}, devDependencies: {} };
  85. allDeps.dependencies.forEach(dep => {
  86. result.dependencies[dep] = true;
  87. });
  88. allDeps.devDependencies.forEach(dep => {
  89. result.devDependencies[dep] = true;
  90. });
  91. comparePackage(result);
  92. } else {
  93. autod.findVersions().then(result => {
  94. result.map = autod.dependencyMap;
  95. if (autod.errors.length) {
  96. autod.errors.forEach(err => {
  97. console.error('[ERROR]'.red, err.message);
  98. });
  99. if (!options.ignore) process.exit(1);
  100. }
  101. log('\n[DEPENDENCIES]\n'.green);
  102. comparePackage(result);
  103. if (options.map) {
  104. log('\n[DEPENDENCY MAP]'.green);
  105. printDependencyMap(result.map);
  106. }
  107. });
  108. }
  109. function comparePackage(result) {
  110. let pkgInfo;
  111. let pkgStr;
  112. let pkgExist = true;
  113. const pkgPath = pjoin(resolve(options.path), 'package.json');
  114. // add prefix
  115. if (options.prefix) {
  116. for (const key in result.dependencies) {
  117. result.dependencies[key] = options.prefix + result.dependencies[key];
  118. }
  119. }
  120. const devprefix = options.devprefix ? options.devprefix : options.prefix;
  121. if (devprefix) {
  122. for (const key in result.devDependencies) {
  123. result.devDependencies[key] = devprefix + result.devDependencies[key];
  124. }
  125. }
  126. try {
  127. pkgInfo = require(pkgPath);
  128. pkgStr = fs.readFileSync(pkgPath, 'utf-8');
  129. } catch (err) {
  130. if (err.code === 'MODULE_NOT_FOUND') {
  131. pkgInfo = {};
  132. pkgExist = false;
  133. } else {
  134. log(output(result));
  135. console.error('`package.json` parsed error: %s', err.message);
  136. process.exit(1);
  137. }
  138. }
  139. if (!pkgExist) {
  140. log(output(result));
  141. if (options.write) {
  142. console.log('[WARN]'.yellow + ' `package.json` not exist, auto generate and write dependencies.');
  143. fs.writeFileSync(pkgPath, '{\n' + output(result) + '\n}\n', 'utf-8');
  144. }
  145. process.exit(0);
  146. }
  147. if (options.keep) {
  148. // keep these modules version, won't change by autod
  149. options.keep.forEach(function(key) {
  150. for (const pkgKey in result.dependencies) {
  151. if (minimatch(pkgKey, key)) {
  152. delete result.dependencies[pkgKey];
  153. }
  154. }
  155. for (const pkgKey in result.devDependencies) {
  156. if (minimatch(pkgKey, key)) {
  157. delete result.devDependencies[pkgKey];
  158. }
  159. }
  160. const dependencies = pkgInfo.dependencies;
  161. const devDependencies = pkgInfo.devDependencies;
  162. for (const pkgKey in dependencies) {
  163. if (minimatch(pkgKey, key)) {
  164. result.dependencies[pkgKey] = dependencies[pkgKey];
  165. }
  166. }
  167. for (const pkgKey in devDependencies) {
  168. if (minimatch(pkgKey, key)) {
  169. result.devDependencies[key] = devDependencies[key];
  170. }
  171. }
  172. });
  173. }
  174. if (pkgInfo.dependencies) {
  175. pkgStr = pkgStr.replace(/( |\t)*"dependencies"\s*:\s*{(.|\n)*?}/,
  176. outputDep('dependencies', result.dependencies));
  177. } else {
  178. pkgStr = pkgStr.replace(/(\s*)(\}\n*\s*)$/, function(end, before, after) {
  179. return ',' + before + outputDep('dependencies', result.dependencies) + '\n' + after;
  180. });
  181. }
  182. if (pkgInfo.devDependencies) {
  183. // merge parsed into devDependencies
  184. for (const key in pkgInfo.devDependencies) {
  185. if (!result.devDependencies[key]) {
  186. result.devDependencies[key] = pkgInfo.devDependencies[key];
  187. }
  188. }
  189. pkgStr = pkgStr.replace(/( |\t)*"devDependencies"\s*:\s*{(.|\n)*?}/,
  190. outputDep('devDependencies', result.devDependencies));
  191. } else {
  192. pkgStr = pkgStr.replace(/(\s*)(\}\n*\s*)$/, function(end, before, after) {
  193. return ',' + before + outputDep('devDependencies', result.devDependencies) + '\n' + after;
  194. });
  195. }
  196. log(output(result));
  197. printUpdates('Dependencies', result.dependencies, pkgInfo.dependencies, true);
  198. printUpdates('DevDependencies', result.devDependencies, pkgInfo.devDependencies);
  199. if (options.write) {
  200. console.log('[INFO]'.green + ' Write dependencies into package.json.');
  201. fs.writeFileSync(pkgPath, pkgStr, 'utf-8');
  202. }
  203. if (options.check) {
  204. const missingDependencies = [];
  205. const missingDevDependencies = [];
  206. const deps = result.dependencies || {};
  207. const old = pkgInfo.dependencies || {};
  208. for (const key in deps) {
  209. if (!old[key]) {
  210. missingDependencies.push(key);
  211. }
  212. }
  213. const devDeps = result.devDependencies || {};
  214. const devOld = pkgInfo.devDependencies || {};
  215. for (const key in devDeps) {
  216. if (!devOld[key] && !old[key]) {
  217. missingDevDependencies.push(key);
  218. }
  219. }
  220. if (missingDependencies.length > 0) {
  221. console.error('[ERROR]'.red + ' Missing dependencies: ' +
  222. JSON.stringify(missingDependencies) + '\n');
  223. }
  224. if (missingDevDependencies.length > 0) {
  225. console.error('[ERROR]'.red + ' Missing devDependencies: ' +
  226. JSON.stringify(missingDevDependencies) + '\n');
  227. }
  228. if (missingDependencies.length > 0 || missingDevDependencies.length > 0) {
  229. process.exit(1);
  230. }
  231. }
  232. }
  233. function processSemver(semver) {
  234. semver = semver || [];
  235. if (typeof semver === 'string') {
  236. semver = semver.split(/\s*,\s*/);
  237. }
  238. const map = {};
  239. semver.forEach(function(m) {
  240. let prefix = '';
  241. if (m.indexOf('@') === 0) {
  242. prefix = '@';
  243. m = m.slice(1);
  244. }
  245. m = m.split('@');
  246. if (m.length !== 2) return;
  247. map[prefix + m[0]] = m[1];
  248. });
  249. return map;
  250. }
  251. function outputDep(name, values) {
  252. let str = util.format(' "%s": {\n', name);
  253. const deps = [];
  254. for (const key in values) {
  255. deps.push(util.format(' "%s": "%s"', key, values[key]));
  256. }
  257. str += deps.sort().join(',\n') + '\n }';
  258. return str;
  259. }
  260. function output(result) {
  261. let str = outputDep('dependencies', result.dependencies);
  262. if (!Object.keys(result.devDependencies).length) {
  263. return str;
  264. }
  265. str += ',\n' + outputDep('devDependencies', result.devDependencies);
  266. return str;
  267. }
  268. function printUpdates(title, latest, old, remove) {
  269. latest = latest || {};
  270. old = old || {};
  271. const arr = [[ 'Package Name', 'Old Version', 'Latest Version' ]];
  272. for (const key in latest) {
  273. if (!old[key]) {
  274. arr.push([ key, '-', latest[key] ]);
  275. } else if (old[key] !== latest[key]) {
  276. arr.push([ key, old[key], latest[key] ]);
  277. }
  278. }
  279. if (remove) {
  280. for (const key in old) {
  281. if (!latest[key]) {
  282. arr.push([ key, old[key], 'remove' ]);
  283. }
  284. }
  285. }
  286. if (arr.length > 1) {
  287. log((title + ' updates').yellow + '\n');
  288. log(printable.print(arr));
  289. log();
  290. } else {
  291. log(('nothing to update in ' + title).green + '\n');
  292. }
  293. }
  294. function printDependencyMap(map) {
  295. Object.keys(map).sort().forEach(function(name) {
  296. console.log((' ' + name).cyan);
  297. console.log((' ' + map[name].join('\n ')).grey);
  298. });
  299. }
  300. function split(str) {
  301. if (typeof str === 'string') {
  302. return str.split(/\s*,\s*/);
  303. }
  304. return str;
  305. }
  306. function log() {
  307. if (options.check) return;
  308. console.log.apply(console, arguments);
  309. }