├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── README.md ├── demo.gif ├── example ├── dummy.test.js ├── es5.js ├── es6.js ├── mixed.js └── package.json ├── lib ├── helpers.js ├── includes-polyfill.js └── index.js ├── license.md ├── package.json ├── src ├── helpers.js ├── includes-polyfill.js └── index.js └── test ├── lib ├── diff.test.js ├── filterRegistryModules.test.js ├── getInstalledModules.test.js ├── getUsedModules.test.js ├── packageJSONExists.test.js └── testData.js └── src ├── diff.test.js ├── filterRegistryModules.test.js ├── getInstalledModules.test.js ├── getUsedModules.test.js ├── packageJSONExists.test.js └── testData.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': 'airbnb', 3 | 'rules': { 4 | 'no-console': 'off', 5 | 'indent': ['error', 4], 6 | 'prefer-const': 'off', 7 | 'comma-dangle': ['error', 'never'], 8 | 'object-curly-spacing': ['error', 'never'], 9 | 'no-extend-native': 'off', 10 | 'no-self-compare': 'off', 11 | 'prefer-rest-params': 'off', 12 | 'arrow-parens': 'off', 13 | 'no-plusplus': 'off' 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "14" 4 | - "12" 5 | - "10" 6 | - "8" 7 | - "6" 8 | cache: 9 | directories: 10 | - node_modules 11 | notifications: 12 | email: false 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### auto-install 2 | 3 | [![Build 4 | Status](https://api.travis-ci.org/siddharthkp/auto-install.svg?branch=master)](https://travis-ci.org/siddharthkp/auto-install) 5 | [![npm](https://img.shields.io/npm/v/auto-install.svg?maxAge=3600)](https://www.npmjs.com/package/auto-install) 6 | [![npm](https://img.shields.io/npm/dt/auto-install.svg?maxAge=3600)](https://www.npmjs.com/package/auto-install) 7 | 8 | Auto installs dependencies as you code. Just hit save. 9 | 10 | ![Auto installs dependencies as you code](https://raw.githubusercontent.com/siddharthkp/auto-install/master/demo.gif) 11 | 12 | Featured in [npm weekly #56](https://medium.com/npm-inc/npm-weekly-56-npm-on-javascript-air-the-cli-team-changes-cadence-and-easier-dependency-f7e4cc24d053)! 13 | 14 | #### Install 15 | 16 | `npm install -g auto-install` 17 | 18 | #### Usage 19 | 20 | Run `auto-install` in the directory you are working in. 21 | 22 | Modules in `.spec.js` and `.test.js` are added to `devDependencies` 23 | 24 | #### Options 25 | 26 | `--secure` Install popular modules only (> 10k downloads in the last month) 27 | 28 | `--exact` Install exact version similar to `npm install express --save-exact` 29 | 30 | `--dont-uninstall` Do not uninstall unused modules 31 | 32 | `--yarn` Use [yarn](https://yarnpkg.com) instead of npm 33 | 34 | #### Show your support 35 | 36 | :star: this repo 37 | 38 | #### FAQ 39 | 40 | [Does it protect against typosquatting?](https://github.com/siddharthkp/auto-install/issues/6) 41 | 42 | [Hackernews post](https://news.ycombinator.com/item?id=12248997) 43 | 44 | #### License 45 | 46 | MIT © [siddharthkp](https://github.com/siddharthkp) 47 | 48 | #### Sponsor 49 | 50 | [![Sponsor](https://app.codesponsor.io/embed/LhLT2c31ydJzdLUuSR9f8mCA/siddharthkp/auto-install.svg)](https://app.codesponsor.io/link/LhLT2c31ydJzdLUuSR9f8mCA/siddharthkp/auto-install) 51 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siddharthkp/auto-install/05977cd6b4edefed33b7aa5c855f007bd74de1f6/demo.gif -------------------------------------------------------------------------------- /example/dummy.test.js: -------------------------------------------------------------------------------- 1 | /* Modules used in [*.test.js, *.spec.js] 2 | * are installed as dev dependencies 3 | */ 4 | 5 | const chai = require('chai'); 6 | -------------------------------------------------------------------------------- /example/es5.js: -------------------------------------------------------------------------------- 1 | // Good old require 2 | const chokidar = require('chokidar'); 3 | 4 | // Require with a attribute 5 | const argv = require('yargs').argv; 6 | 7 | // Require in-built module (these are ignored) 8 | const fs = require('fs'); 9 | 10 | // Require files 11 | const helpers = require('../src/helpers'); 12 | 13 | // Require scoped modules 14 | const autoInstall = require('@siddharthkp/auto-install'); 15 | 16 | -------------------------------------------------------------------------------- /example/es6.js: -------------------------------------------------------------------------------- 1 | // ES6 import 2 | import memoize from 'lodash/memoize'; 3 | 4 | import * as request from 'request'; 5 | -------------------------------------------------------------------------------- /example/mixed.js: -------------------------------------------------------------------------------- 1 | import async from 'async'; 2 | require('ava'); 3 | 4 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "main": "diff.test.js", 5 | "keywords": [], 6 | "author": "", 7 | "license": "MIT", 8 | "description": "This is just for test purposes", 9 | "dependencies": { 10 | "request": "2.75.0", 11 | "yargs": "4.8.1" 12 | }, 13 | "devDependencies": { 14 | "mocha": "3.0.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var glob = require('glob'); 5 | 6 | var _require = require('child_process'), 7 | execSync = _require.execSync; 8 | 9 | var isBuiltInModule = require('is-builtin-module'); 10 | var ora = require('ora'); 11 | var logSymbols = require('log-symbols'); 12 | var detective = require('detective'); 13 | var es6detective = require('detective-es6'); 14 | var colors = require('colors'); 15 | var argv = require('yargs').argv; 16 | var packageJson = require('package-json'); 17 | var https = require('https'); 18 | require('./includes-polyfill'); 19 | 20 | /* File reader 21 | * Return contents of given file 22 | */ 23 | var readFile = function readFile(path) { 24 | var content = fs.readFileSync(path, 'utf8'); 25 | return content; 26 | }; 27 | 28 | /* Get installed modules 29 | * Read dependencies array from package.json 30 | */ 31 | 32 | var getInstalledModules = function getInstalledModules() { 33 | var content = JSON.parse(readFile('package.json')); 34 | var installedModules = []; 35 | 36 | var dependencies = content.dependencies || {}; 37 | var devDependencies = content.devDependencies || {}; 38 | 39 | var _iteratorNormalCompletion = true; 40 | var _didIteratorError = false; 41 | var _iteratorError = undefined; 42 | 43 | try { 44 | for (var _iterator = Object.keys(dependencies)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 45 | var key = _step.value; 46 | 47 | installedModules.push({ 48 | name: key, 49 | dev: false 50 | }); 51 | } 52 | } catch (err) { 53 | _didIteratorError = true; 54 | _iteratorError = err; 55 | } finally { 56 | try { 57 | if (!_iteratorNormalCompletion && _iterator.return) { 58 | _iterator.return(); 59 | } 60 | } finally { 61 | if (_didIteratorError) { 62 | throw _iteratorError; 63 | } 64 | } 65 | } 66 | 67 | var _iteratorNormalCompletion2 = true; 68 | var _didIteratorError2 = false; 69 | var _iteratorError2 = undefined; 70 | 71 | try { 72 | for (var _iterator2 = Object.keys(devDependencies)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 73 | var _key = _step2.value; 74 | 75 | installedModules.push({ 76 | name: _key, 77 | dev: true 78 | }); 79 | } 80 | } catch (err) { 81 | _didIteratorError2 = true; 82 | _iteratorError2 = err; 83 | } finally { 84 | try { 85 | if (!_iteratorNormalCompletion2 && _iterator2.return) { 86 | _iterator2.return(); 87 | } 88 | } finally { 89 | if (_didIteratorError2) { 90 | throw _iteratorError2; 91 | } 92 | } 93 | } 94 | 95 | return installedModules; 96 | }; 97 | 98 | /* Get all js files 99 | * Return path of all js files 100 | */ 101 | var getFiles = function getFiles() { 102 | return glob.sync('**/*.js', { ignore: ['node_modules/**/*'] }); 103 | }; 104 | 105 | /* Check for valid string - to stop malicious intentions */ 106 | 107 | var isValidModule = function isValidModule(_ref) { 108 | var name = _ref.name; 109 | 110 | var regex = new RegExp('^([a-z0-9-_]{1,})$'); 111 | return regex.test(name); 112 | }; 113 | 114 | /* Find modules from file 115 | * Returns array of modules from a file 116 | */ 117 | 118 | var getModulesFromFile = function getModulesFromFile(path) { 119 | var content = fs.readFileSync(path, 'utf8'); 120 | var modules = []; 121 | var detectiveOptions = { parse: { sourceType: 'module' } }; 122 | try { 123 | modules = detective(content, detectiveOptions); 124 | 125 | var es6modules = es6detective(content, detectiveOptions); 126 | modules = modules.concat(es6modules); 127 | 128 | modules = modules.filter(function (module) { 129 | return isValidModule(module); 130 | }); 131 | } catch (err) { 132 | var line = content.split('\n')[err.loc.line - 1]; 133 | console.log(colors.red('Could not parse ' + path + '. There is a syntax error in file at line ' + err.loc.line + ' column: ' + err.loc.column + '.\n' + line.slice(0, err.loc.column - 1) + '^' + line.slice(err.loc.column - 1))); 134 | } 135 | return modules; 136 | }; 137 | 138 | /* Is test file? 139 | * [.spec.js, .test.js] are supported test file formats 140 | */ 141 | 142 | var isTestFile = function isTestFile(name) { 143 | return name.endsWith('.spec.js') || name.endsWith('.test.js'); 144 | }; 145 | 146 | /* Dedup similar modules 147 | * Deduplicates list 148 | * Ignores/assumes type of the modules in list 149 | */ 150 | 151 | var deduplicateSimilarModules = function deduplicateSimilarModules(modules) { 152 | var dedupedModules = []; 153 | var dedupedModuleNames = []; 154 | 155 | var _iteratorNormalCompletion3 = true; 156 | var _didIteratorError3 = false; 157 | var _iteratorError3 = undefined; 158 | 159 | try { 160 | for (var _iterator3 = modules[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { 161 | var _module = _step3.value; 162 | 163 | if (!dedupedModuleNames.includes(_module.name)) { 164 | dedupedModules.push(_module); 165 | dedupedModuleNames.push(_module.name); 166 | } 167 | } 168 | } catch (err) { 169 | _didIteratorError3 = true; 170 | _iteratorError3 = err; 171 | } finally { 172 | try { 173 | if (!_iteratorNormalCompletion3 && _iterator3.return) { 174 | _iterator3.return(); 175 | } 176 | } finally { 177 | if (_didIteratorError3) { 178 | throw _iteratorError3; 179 | } 180 | } 181 | } 182 | 183 | return dedupedModules; 184 | }; 185 | 186 | /* Dedup modules 187 | * Divide modules into prod and dev 188 | * Deduplicates each list 189 | */ 190 | 191 | var deduplicate = function deduplicate(modules) { 192 | var dedupedModules = []; 193 | 194 | var testModules = modules.filter(function (module) { 195 | return module.dev; 196 | }); 197 | dedupedModules = dedupedModules.concat(deduplicateSimilarModules(testModules)); 198 | 199 | var prodModules = modules.filter(function (module) { 200 | return !module.dev; 201 | }); 202 | dedupedModules = dedupedModules.concat(deduplicateSimilarModules(prodModules)); 203 | 204 | return dedupedModules; 205 | }; 206 | 207 | /* Get used modules 208 | * Read all .js files and grep for modules 209 | */ 210 | 211 | var getUsedModules = function getUsedModules() { 212 | var files = getFiles(); 213 | var usedModules = []; 214 | var _iteratorNormalCompletion4 = true; 215 | var _didIteratorError4 = false; 216 | var _iteratorError4 = undefined; 217 | 218 | try { 219 | for (var _iterator4 = files[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { 220 | var fileName = _step4.value; 221 | 222 | var modulesFromFile = getModulesFromFile(fileName); 223 | var dev = isTestFile(fileName); 224 | var _iteratorNormalCompletion5 = true; 225 | var _didIteratorError5 = false; 226 | var _iteratorError5 = undefined; 227 | 228 | try { 229 | for (var _iterator5 = modulesFromFile[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { 230 | var name = _step5.value; 231 | usedModules.push({ name: name, dev: dev }); 232 | } 233 | } catch (err) { 234 | _didIteratorError5 = true; 235 | _iteratorError5 = err; 236 | } finally { 237 | try { 238 | if (!_iteratorNormalCompletion5 && _iterator5.return) { 239 | _iterator5.return(); 240 | } 241 | } finally { 242 | if (_didIteratorError5) { 243 | throw _iteratorError5; 244 | } 245 | } 246 | } 247 | } 248 | } catch (err) { 249 | _didIteratorError4 = true; 250 | _iteratorError4 = err; 251 | } finally { 252 | try { 253 | if (!_iteratorNormalCompletion4 && _iterator4.return) { 254 | _iterator4.return(); 255 | } 256 | } finally { 257 | if (_didIteratorError4) { 258 | throw _iteratorError4; 259 | } 260 | } 261 | } 262 | 263 | usedModules = deduplicate(usedModules); 264 | return usedModules; 265 | }; 266 | 267 | /* Handle error 268 | * Pretty error message for common errors 269 | */ 270 | 271 | var handleError = function handleError(err) { 272 | if (err.includes('E404')) { 273 | console.log(colors.red('Module is not in the npm registry.')); 274 | } else if (err.includes('ENOTFOUND')) { 275 | console.log(colors.red('Could not connect to npm, check your internet connection!')); 276 | } else console.log(colors.red(err)); 277 | }; 278 | 279 | /* Command runner 280 | * Run a given command 281 | */ 282 | 283 | var runCommand = function runCommand(command) { 284 | var succeeded = true; 285 | try { 286 | execSync(command, { encoding: 'utf8' }); 287 | } catch (error) { 288 | succeeded = false; 289 | handleError(error.stderr); 290 | } 291 | return succeeded; 292 | }; 293 | 294 | /* Show pretty outputs 295 | * Use ora spinners to show what's going on 296 | */ 297 | 298 | var startSpinner = function startSpinner(message, type) { 299 | var spinner = ora(); 300 | spinner.text = message; 301 | spinner.color = type; 302 | spinner.start(); 303 | return spinner; 304 | }; 305 | 306 | var stopSpinner = function stopSpinner(spinner, message, type) { 307 | spinner.stop(); 308 | if (!message) return; 309 | var symbol = void 0; 310 | if (type === 'red') symbol = logSymbols.error;else if (type === 'yellow') symbol = logSymbols.warning;else symbol = logSymbols.success; 311 | console.log(symbol, message); 312 | }; 313 | 314 | /* Is module popular? - for secure mode */ 315 | 316 | var POPULARITY_THRESHOLD = 10000; 317 | var isModulePopular = function isModulePopular(name, callback) { 318 | var spinner = startSpinner('Checking ' + name, 'yellow'); 319 | var url = 'https://api.npmjs.org/downloads/point/last-month/' + name; 320 | https.get(url).then(function (response) { 321 | var body = ''; 322 | response.on('data', function (data) { 323 | body += data; 324 | }); 325 | 326 | response.on('end', function () { 327 | stopSpinner(spinner); 328 | var downloads = JSON.parse(body).downloads; 329 | callback(downloads > POPULARITY_THRESHOLD); 330 | }); 331 | }).catch(function (error) { 332 | console.log(colors.red('Could not connect to npm, check your internet connection!'), error); 333 | }); 334 | }; 335 | 336 | /* Get install command 337 | * 338 | * Depends on package manager, dev and exact 339 | */ 340 | 341 | var getInstallCommand = function getInstallCommand(name, dev) { 342 | var packageManager = 'npm'; 343 | if (argv.yarn) packageManager = 'yarn'; 344 | 345 | var command = void 0; 346 | 347 | if (packageManager === 'npm') { 348 | command = 'npm install ' + name + ' --save'; 349 | if (dev) command += '-dev'; 350 | if (argv.exact) command += ' --save-exact'; 351 | } else if (packageManager === 'yarn') { 352 | command = 'yarn add ' + name; 353 | if (dev) command += ' --dev'; 354 | // yarn always adds exact 355 | } 356 | return command; 357 | }; 358 | 359 | /* Install module 360 | * Install given module 361 | */ 362 | 363 | var installModule = function installModule(_ref2) { 364 | var name = _ref2.name, 365 | dev = _ref2.dev; 366 | 367 | var spinner = startSpinner('Installing ' + name, 'green'); 368 | 369 | var command = getInstallCommand(name, dev); 370 | 371 | var message = name + ' installed'; 372 | if (dev) message += ' in devDependencies'; 373 | 374 | var success = runCommand(command); 375 | if (success) stopSpinner(spinner, message, 'green');else stopSpinner(spinner, name + ' installation failed', 'yellow'); 376 | }; 377 | 378 | /* is scoped module? */ 379 | 380 | var isScopedModule = function isScopedModule(name) { 381 | return name[0] === '@'; 382 | }; 383 | 384 | /* Install module if author is trusted */ 385 | 386 | var installModuleIfTrustedAuthor = function installModuleIfTrustedAuthor(_ref3) { 387 | var name = _ref3.name, 388 | dev = _ref3.dev; 389 | 390 | var trustedAuthor = argv['trust-author']; 391 | packageJson(name).then(function (json) { 392 | if (json.author && json.author.name === trustedAuthor) installModule({ name: name, dev: dev });else console.log(colors.red(name + ' not trusted')); 393 | }); 394 | }; 395 | 396 | /* Install module if trusted 397 | * Call isModulePopular before installing 398 | */ 399 | 400 | var installModuleIfTrusted = function installModuleIfTrusted(_ref4) { 401 | var name = _ref4.name, 402 | dev = _ref4.dev; 403 | 404 | // Trust scoped modules 405 | if (isScopedModule(name)) installModule({ name: name, dev: dev });else { 406 | isModulePopular(name, function (popular) { 407 | // Popular as proxy for trusted 408 | if (popular) installModule({ name: name, dev: dev }); 409 | // Trusted Author 410 | else if (argv['trust-author']) installModuleIfTrustedAuthor({ name: name, dev: dev }); 411 | // Not trusted 412 | else console.log(colors.red(name + ' not trusted')); 413 | }); 414 | } 415 | }; 416 | 417 | /* Get uninstall command 418 | * 419 | * Depends on package manager 420 | */ 421 | 422 | var getUninstallCommand = function getUninstallCommand(name) { 423 | var packageManager = 'npm'; 424 | if (argv.yarn) packageManager = 'yarn'; 425 | 426 | var command = void 0; 427 | 428 | if (packageManager === 'npm') command = 'npm uninstall ' + name + ' --save';else if (packageManager === 'yarn') command = 'yarn remove ' + name; 429 | 430 | return command; 431 | }; 432 | 433 | /* Uninstall module */ 434 | 435 | var uninstallModule = function uninstallModule(_ref5) { 436 | var name = _ref5.name, 437 | dev = _ref5.dev; 438 | 439 | if (dev) return; 440 | 441 | var command = getUninstallCommand(name); 442 | var message = name + ' removed'; 443 | 444 | var spinner = startSpinner('Uninstalling ' + name, 'red'); 445 | runCommand(command); 446 | stopSpinner(spinner, message, 'red'); 447 | }; 448 | 449 | /* Remove built in/native modules */ 450 | 451 | var removeBuiltInModules = function removeBuiltInModules(modules) { 452 | return modules.filter(function (module) { 453 | return !isBuiltInModule(module.name); 454 | }); 455 | }; 456 | 457 | /* Remove local files that are required */ 458 | 459 | var removeLocalFiles = function removeLocalFiles(modules) { 460 | return modules.filter(function (module) { 461 | return !module.name.includes('./'); 462 | }); 463 | }; 464 | 465 | /* Remove file paths from module names 466 | * Example: convert `colors/safe` to `colors` 467 | */ 468 | 469 | var removeFilePaths = function removeFilePaths(modules) { 470 | var _iteratorNormalCompletion6 = true; 471 | var _didIteratorError6 = false; 472 | var _iteratorError6 = undefined; 473 | 474 | try { 475 | for (var _iterator6 = modules[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { 476 | var _module2 = _step6.value; 477 | 478 | var slicedName = _module2.name.split('/')[0]; 479 | if (slicedName.substr(0, 1) !== '@') _module2.name = slicedName; 480 | } 481 | } catch (err) { 482 | _didIteratorError6 = true; 483 | _iteratorError6 = err; 484 | } finally { 485 | try { 486 | if (!_iteratorNormalCompletion6 && _iterator6.return) { 487 | _iterator6.return(); 488 | } 489 | } finally { 490 | if (_didIteratorError6) { 491 | throw _iteratorError6; 492 | } 493 | } 494 | } 495 | 496 | return modules; 497 | }; 498 | 499 | /* Filter registry modules */ 500 | 501 | var filterRegistryModules = function filterRegistryModules(modules) { 502 | return removeBuiltInModules(removeFilePaths(removeLocalFiles(modules))); 503 | }; 504 | 505 | /* Get module names from array of module objects */ 506 | 507 | var getNamesFromModules = function getNamesFromModules(modules) { 508 | return modules.map(function (module) { 509 | return module.name; 510 | }); 511 | }; 512 | 513 | /* Modules diff */ 514 | 515 | var diff = function diff(first, second) { 516 | var namesFromSecond = getNamesFromModules(second); 517 | return first.filter(function (module) { 518 | return !namesFromSecond.includes(module.name); 519 | }); 520 | }; 521 | 522 | /* Reinstall modules */ 523 | 524 | var cleanup = function cleanup() { 525 | var spinner = startSpinner('Cleaning up', 'green'); 526 | if (argv.yarn) runCommand('yarn');else runCommand('npm install'); 527 | stopSpinner(spinner); 528 | }; 529 | 530 | /* Does package.json exist? 531 | * Without package.json, most of the functionality fails 532 | * installing + adding to package.json 533 | * removing unused modules 534 | */ 535 | 536 | var packageJSONExists = function packageJSONExists() { 537 | return fs.existsSync('package.json'); 538 | }; 539 | 540 | /* Public helper functions */ 541 | 542 | module.exports = { 543 | getInstalledModules: getInstalledModules, 544 | getUsedModules: getUsedModules, 545 | filterRegistryModules: filterRegistryModules, 546 | installModule: installModule, 547 | installModuleIfTrusted: installModuleIfTrusted, 548 | uninstallModule: uninstallModule, 549 | diff: diff, 550 | cleanup: cleanup, 551 | packageJSONExists: packageJSONExists 552 | }; -------------------------------------------------------------------------------- /lib/includes-polyfill.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Array.prototype.includes = function includes(searchElement) { 4 | if (this == null) { 5 | throw new TypeError('Array.prototype.includes called on null or undefined'); 6 | } 7 | 8 | var O = Object(this); 9 | var len = parseInt(O.length, 10) || 0; 10 | if (len === 0) { 11 | return false; 12 | } 13 | var n = parseInt(arguments[1], 10) || 0; 14 | var k = void 0; 15 | if (n >= 0) { 16 | k = n; 17 | } else { 18 | k = len + n; 19 | if (k < 0) { 20 | k = 0; 21 | } 22 | } 23 | var currentElement = void 0; 24 | while (k < len) { 25 | currentElement = O[k]; 26 | if (searchElement === currentElement || searchElement !== searchElement && currentElement !== currentElement) { 27 | // NaN !== NaN 28 | return true; 29 | } 30 | k++; 31 | } 32 | return false; 33 | }; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var helpers = require('./helpers'); 5 | var chokidar = require('chokidar'); 6 | var colors = require('colors'); 7 | var argv = require('yargs').argv; 8 | 9 | var watchersInitialized = false; 10 | var main = void 0; 11 | 12 | /* Secure mode */ 13 | 14 | var secureMode = false; 15 | if (argv.secure) secureMode = true; 16 | 17 | var uninstallMode = true; 18 | if (argv['dont-uninstall']) uninstallMode = false; 19 | 20 | /* Watch files and repeat drill 21 | * Add a watcher, call main wrapper to repeat cycle 22 | */ 23 | 24 | var initializeWatchers = function initializeWatchers() { 25 | var watcher = chokidar.watch('**/*.js', { 26 | ignored: 'node_modules' 27 | }); 28 | watcher.on('change', main).on('unlink', main); 29 | 30 | watchersInitialized = true; 31 | console.log('Watchers initialized'); 32 | }; 33 | 34 | /* Main wrapper 35 | * Get installed modules from package.json 36 | * Get used modules from all files 37 | * Install used modules that are not installed 38 | * Remove installed modules that are not used 39 | * After setup, initialize watchers 40 | */ 41 | 42 | main = function main() { 43 | if (!helpers.packageJSONExists()) { 44 | console.log(colors.red('package.json does not exist')); 45 | console.log(colors.red('You can create one by using `npm init`')); 46 | return; 47 | } 48 | 49 | var installedModules = []; 50 | installedModules = helpers.getInstalledModules(); 51 | 52 | var usedModules = helpers.getUsedModules(); 53 | usedModules = helpers.filterRegistryModules(usedModules); 54 | 55 | // removeUnusedModules 56 | 57 | if (uninstallMode) { 58 | var unusedModules = helpers.diff(installedModules, usedModules); 59 | var _iteratorNormalCompletion = true; 60 | var _didIteratorError = false; 61 | var _iteratorError = undefined; 62 | 63 | try { 64 | for (var _iterator = unusedModules[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 65 | var module = _step.value; 66 | helpers.uninstallModule(module); 67 | } 68 | } catch (err) { 69 | _didIteratorError = true; 70 | _iteratorError = err; 71 | } finally { 72 | try { 73 | if (!_iteratorNormalCompletion && _iterator.return) { 74 | _iterator.return(); 75 | } 76 | } finally { 77 | if (_didIteratorError) { 78 | throw _iteratorError; 79 | } 80 | } 81 | } 82 | } 83 | 84 | // installModules 85 | 86 | var modulesNotInstalled = helpers.diff(usedModules, installedModules); 87 | var _iteratorNormalCompletion2 = true; 88 | var _didIteratorError2 = false; 89 | var _iteratorError2 = undefined; 90 | 91 | try { 92 | for (var _iterator2 = modulesNotInstalled[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 93 | var _module = _step2.value; 94 | 95 | if (secureMode) helpers.installModuleIfTrusted(_module);else helpers.installModule(_module); 96 | } 97 | } catch (err) { 98 | _didIteratorError2 = true; 99 | _iteratorError2 = err; 100 | } finally { 101 | try { 102 | if (!_iteratorNormalCompletion2 && _iterator2.return) { 103 | _iterator2.return(); 104 | } 105 | } finally { 106 | if (_didIteratorError2) { 107 | throw _iteratorError2; 108 | } 109 | } 110 | } 111 | 112 | helpers.cleanup(); 113 | if (!watchersInitialized) initializeWatchers(); 114 | }; 115 | 116 | /* Turn the key */ 117 | main(); -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Siddharth Kshetrapal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auto-install", 3 | "version": "1.7.5", 4 | "description": "Auto installs dependencies as you code", 5 | "keywords": "auto, dependencies, install, package, watch", 6 | "homepage": "https://github.com/siddharthkp/auto-install#readme", 7 | "repository": "siddharthkp/auto-install", 8 | "license": "MIT", 9 | "main": "src/index.js", 10 | "bin": "lib/index.js", 11 | "author": "siddharthkp", 12 | "scripts": { 13 | "lint": "eslint src", 14 | "build": "babel src -d lib && babel test/src -d test/lib", 15 | "run-tests": "cd example && mocha ../test/lib", 16 | "test": "npm run build && npm run run-tests" 17 | }, 18 | "engines": { 19 | "node": ">= 4.4.5" 20 | }, 21 | "dependencies": { 22 | "chokidar": "2.0.0", 23 | "colors": "1.1.2", 24 | "detective": "5.0.2", 25 | "detective-es6": "1.2.0", 26 | "glob": "7.1.0", 27 | "is-builtin-module": "1.0.0", 28 | "log-symbols": "1.0.2", 29 | "ora": "0.3.0", 30 | "package-json": "2.4.0", 31 | "yargs": "6.3.0" 32 | }, 33 | "devDependencies": { 34 | "babel-cli": "6.14.0", 35 | "babel-preset-es2015": "6.14.0", 36 | "chai": "3.5.0", 37 | "eslint": "4.18.2", 38 | "eslint-config-airbnb": "12.0.0", 39 | "eslint-plugin-import": "1.16.0", 40 | "eslint-plugin-jsx-a11y": "2.2.3", 41 | "eslint-plugin-react": "6.4.1", 42 | "mocha": "3.1.2" 43 | }, 44 | "babel": { 45 | "presets": [ 46 | "es2015" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const glob = require('glob'); 3 | const {execSync} = require('child_process'); 4 | const isBuiltInModule = require('is-builtin-module'); 5 | const ora = require('ora'); 6 | const logSymbols = require('log-symbols'); 7 | const detective = require('detective'); 8 | const es6detective = require('detective-es6'); 9 | const colors = require('colors'); 10 | const argv = require('yargs').argv; 11 | const packageJson = require('package-json'); 12 | const https = require('https'); 13 | require('./includes-polyfill'); 14 | 15 | /* File reader 16 | * Return contents of given file 17 | */ 18 | let readFile = (path) => { 19 | let content = fs.readFileSync(path, 'utf8'); 20 | return content; 21 | }; 22 | 23 | /* Get installed modules 24 | * Read dependencies array from package.json 25 | */ 26 | 27 | let getInstalledModules = () => { 28 | let content = JSON.parse(readFile('package.json')); 29 | let installedModules = []; 30 | 31 | let dependencies = content.dependencies || {}; 32 | let devDependencies = content.devDependencies || {}; 33 | 34 | for (let key of Object.keys(dependencies)) { 35 | installedModules.push({ 36 | name: key, 37 | dev: false 38 | }); 39 | } 40 | for (let key of Object.keys(devDependencies)) { 41 | installedModules.push({ 42 | name: key, 43 | dev: true 44 | }); 45 | } 46 | 47 | return installedModules; 48 | }; 49 | 50 | /* Get all js files 51 | * Return path of all js files 52 | */ 53 | let getFiles = () => glob.sync('**/*.js', {ignore: ['node_modules/**/*']}); 54 | 55 | /* Check for valid string - to stop malicious intentions */ 56 | 57 | let isValidModule = ({name}) => { 58 | let regex = new RegExp('^([a-z0-9-_]{1,})$'); 59 | return regex.test(name); 60 | }; 61 | 62 | /* Find modules from file 63 | * Returns array of modules from a file 64 | */ 65 | 66 | let getModulesFromFile = (path) => { 67 | let content = fs.readFileSync(path, 'utf8'); 68 | let modules = []; 69 | const detectiveOptions = {parse: {sourceType: 'module'}}; 70 | try { 71 | modules = detective(content, detectiveOptions); 72 | 73 | let es6modules = es6detective(content, detectiveOptions); 74 | modules = modules.concat(es6modules); 75 | 76 | modules = modules.filter((module) => isValidModule(module)); 77 | } catch (err) { 78 | const line = content.split('\n')[err.loc.line - 1]; 79 | console.log(colors.red(`Could not parse ${path}. There is a syntax error in file at line ${err.loc.line} column: ${err.loc.column}.\n${line.slice(0, err.loc.column - 1)}^${line.slice(err.loc.column - 1)}`)); 80 | } 81 | return modules; 82 | }; 83 | 84 | /* Is test file? 85 | * [.spec.js, .test.js] are supported test file formats 86 | */ 87 | 88 | let isTestFile = (name) => (name.endsWith('.spec.js') || name.endsWith('.test.js')); 89 | 90 | /* Dedup similar modules 91 | * Deduplicates list 92 | * Ignores/assumes type of the modules in list 93 | */ 94 | 95 | let deduplicateSimilarModules = (modules) => { 96 | let dedupedModules = []; 97 | let dedupedModuleNames = []; 98 | 99 | for (let module of modules) { 100 | if (!dedupedModuleNames.includes(module.name)) { 101 | dedupedModules.push(module); 102 | dedupedModuleNames.push(module.name); 103 | } 104 | } 105 | 106 | return dedupedModules; 107 | }; 108 | 109 | /* Dedup modules 110 | * Divide modules into prod and dev 111 | * Deduplicates each list 112 | */ 113 | 114 | let deduplicate = (modules) => { 115 | let dedupedModules = []; 116 | 117 | let testModules = modules.filter(module => module.dev); 118 | dedupedModules = dedupedModules.concat(deduplicateSimilarModules(testModules)); 119 | 120 | let prodModules = modules.filter(module => !module.dev); 121 | dedupedModules = dedupedModules.concat(deduplicateSimilarModules(prodModules)); 122 | 123 | return dedupedModules; 124 | }; 125 | 126 | /* Get used modules 127 | * Read all .js files and grep for modules 128 | */ 129 | 130 | let getUsedModules = () => { 131 | let files = getFiles(); 132 | let usedModules = []; 133 | for (let fileName of files) { 134 | let modulesFromFile = getModulesFromFile(fileName); 135 | let dev = isTestFile(fileName); 136 | for (let name of modulesFromFile) usedModules.push({name, dev}); 137 | } 138 | usedModules = deduplicate(usedModules); 139 | return usedModules; 140 | }; 141 | 142 | /* Handle error 143 | * Pretty error message for common errors 144 | */ 145 | 146 | let handleError = (err) => { 147 | if (err.includes('E404')) { 148 | console.log(colors.red('Module is not in the npm registry.')); 149 | } else if (err.includes('ENOTFOUND')) { 150 | console.log(colors.red('Could not connect to npm, check your internet connection!')); 151 | } else console.log(colors.red(err)); 152 | }; 153 | 154 | /* Command runner 155 | * Run a given command 156 | */ 157 | 158 | let runCommand = (command) => { 159 | let succeeded = true; 160 | try { 161 | execSync(command, {encoding: 'utf8'}); 162 | } catch (error) { 163 | succeeded = false; 164 | handleError(error.stderr); 165 | } 166 | return succeeded; 167 | }; 168 | 169 | /* Show pretty outputs 170 | * Use ora spinners to show what's going on 171 | */ 172 | 173 | let startSpinner = (message, type) => { 174 | let spinner = ora(); 175 | spinner.text = message; 176 | spinner.color = type; 177 | spinner.start(); 178 | return spinner; 179 | }; 180 | 181 | let stopSpinner = (spinner, message, type) => { 182 | spinner.stop(); 183 | if (!message) return; 184 | let symbol; 185 | if (type === 'red') symbol = logSymbols.error; 186 | else if (type === 'yellow') symbol = logSymbols.warning; 187 | else symbol = logSymbols.success; 188 | console.log(symbol, message); 189 | }; 190 | 191 | /* Is module popular? - for secure mode */ 192 | 193 | const POPULARITY_THRESHOLD = 10000; 194 | let isModulePopular = (name, callback) => { 195 | let spinner = startSpinner(`Checking ${name}`, 'yellow'); 196 | let url = `https://api.npmjs.org/downloads/point/last-month/${name}`; 197 | https.get(url) 198 | .then(response => { 199 | let body = ''; 200 | response.on('data', data => { 201 | body += data; 202 | }); 203 | 204 | response.on('end', () => { 205 | stopSpinner(spinner); 206 | let downloads = JSON.parse(body).downloads; 207 | callback(downloads > POPULARITY_THRESHOLD); 208 | }); 209 | }) 210 | .catch(error => { 211 | console.log(colors.red('Could not connect to npm, check your internet connection!'), error); 212 | }); 213 | }; 214 | 215 | /* Get install command 216 | * 217 | * Depends on package manager, dev and exact 218 | */ 219 | 220 | let getInstallCommand = (name, dev) => { 221 | let packageManager = 'npm'; 222 | if (argv.yarn) packageManager = 'yarn'; 223 | 224 | let command; 225 | 226 | if (packageManager === 'npm') { 227 | command = `npm install ${name} --save`; 228 | if (dev) command += '-dev'; 229 | if (argv.exact) command += ' --save-exact'; 230 | } else if (packageManager === 'yarn') { 231 | command = `yarn add ${name}`; 232 | if (dev) command += ' --dev'; 233 | // yarn always adds exact 234 | } 235 | return command; 236 | }; 237 | 238 | /* Install module 239 | * Install given module 240 | */ 241 | 242 | let installModule = ({name, dev}) => { 243 | let spinner = startSpinner(`Installing ${name}`, 'green'); 244 | 245 | let command = getInstallCommand(name, dev); 246 | 247 | let message = `${name} installed`; 248 | if (dev) message += ' in devDependencies'; 249 | 250 | let success = runCommand(command); 251 | if (success) stopSpinner(spinner, message, 'green'); 252 | else stopSpinner(spinner, `${name} installation failed`, 'yellow'); 253 | }; 254 | 255 | /* is scoped module? */ 256 | 257 | let isScopedModule = (name) => name[0] === '@'; 258 | 259 | /* Install module if author is trusted */ 260 | 261 | let installModuleIfTrustedAuthor = ({name, dev}) => { 262 | let trustedAuthor = argv['trust-author']; 263 | packageJson(name).then(json => { 264 | if (json.author && json.author.name === trustedAuthor) installModule({name, dev}); 265 | else console.log(colors.red(`${name} not trusted`)); 266 | }); 267 | }; 268 | 269 | /* Install module if trusted 270 | * Call isModulePopular before installing 271 | */ 272 | 273 | let installModuleIfTrusted = ({name, dev}) => { 274 | // Trust scoped modules 275 | if (isScopedModule(name)) installModule({name, dev}); 276 | else { 277 | isModulePopular(name, (popular) => { 278 | // Popular as proxy for trusted 279 | if (popular) installModule({name, dev}); 280 | // Trusted Author 281 | else if (argv['trust-author']) installModuleIfTrustedAuthor({name, dev}); 282 | // Not trusted 283 | else console.log(colors.red(`${name} not trusted`)); 284 | }); 285 | } 286 | }; 287 | 288 | /* Get uninstall command 289 | * 290 | * Depends on package manager 291 | */ 292 | 293 | let getUninstallCommand = (name) => { 294 | let packageManager = 'npm'; 295 | if (argv.yarn) packageManager = 'yarn'; 296 | 297 | let command; 298 | 299 | if (packageManager === 'npm') command = `npm uninstall ${name} --save`; 300 | else if (packageManager === 'yarn') command = `yarn remove ${name}`; 301 | 302 | return command; 303 | }; 304 | 305 | /* Uninstall module */ 306 | 307 | let uninstallModule = ({name, dev}) => { 308 | if (dev) return; 309 | 310 | let command = getUninstallCommand(name); 311 | let message = `${name} removed`; 312 | 313 | let spinner = startSpinner(`Uninstalling ${name}`, 'red'); 314 | runCommand(command); 315 | stopSpinner(spinner, message, 'red'); 316 | }; 317 | 318 | /* Remove built in/native modules */ 319 | 320 | let removeBuiltInModules = (modules) => modules.filter((module) => !isBuiltInModule(module.name)); 321 | 322 | /* Remove local files that are required */ 323 | 324 | let removeLocalFiles = (modules) => modules.filter((module) => !module.name.includes('./')); 325 | 326 | /* Remove file paths from module names 327 | * Example: convert `colors/safe` to `colors` 328 | */ 329 | 330 | let removeFilePaths = (modules) => { 331 | for (let module of modules) { 332 | let slicedName = module.name.split('/')[0]; 333 | if (slicedName.substr(0, 1) !== '@') module.name = slicedName; 334 | } 335 | return modules; 336 | }; 337 | 338 | /* Filter registry modules */ 339 | 340 | let filterRegistryModules = (modules) => removeBuiltInModules( 341 | removeFilePaths( 342 | removeLocalFiles( 343 | modules 344 | ))); 345 | 346 | /* Get module names from array of module objects */ 347 | 348 | let getNamesFromModules = (modules) => modules.map(module => module.name); 349 | 350 | /* Modules diff */ 351 | 352 | let diff = (first, second) => { 353 | let namesFromSecond = getNamesFromModules(second); 354 | return first.filter(module => !namesFromSecond.includes(module.name)); 355 | }; 356 | 357 | /* Reinstall modules */ 358 | 359 | let cleanup = () => { 360 | let spinner = startSpinner('Cleaning up', 'green'); 361 | if (argv.yarn) runCommand('yarn'); 362 | else runCommand('npm install'); 363 | stopSpinner(spinner); 364 | }; 365 | 366 | /* Does package.json exist? 367 | * Without package.json, most of the functionality fails 368 | * installing + adding to package.json 369 | * removing unused modules 370 | */ 371 | 372 | let packageJSONExists = () => fs.existsSync('package.json'); 373 | 374 | /* Public helper functions */ 375 | 376 | module.exports = { 377 | getInstalledModules, 378 | getUsedModules, 379 | filterRegistryModules, 380 | installModule, 381 | installModuleIfTrusted, 382 | uninstallModule, 383 | diff, 384 | cleanup, 385 | packageJSONExists 386 | }; 387 | 388 | -------------------------------------------------------------------------------- /src/includes-polyfill.js: -------------------------------------------------------------------------------- 1 | Array.prototype.includes = function includes(searchElement) { 2 | if (this == null) { 3 | throw new TypeError('Array.prototype.includes called on null or undefined'); 4 | } 5 | 6 | let O = Object(this); 7 | let len = parseInt(O.length, 10) || 0; 8 | if (len === 0) { 9 | return false; 10 | } 11 | let n = parseInt(arguments[1], 10) || 0; 12 | let k; 13 | if (n >= 0) { 14 | k = n; 15 | } else { 16 | k = len + n; 17 | if (k < 0) { k = 0; } 18 | } 19 | let currentElement; 20 | while (k < len) { 21 | currentElement = O[k]; 22 | if (searchElement === currentElement || 23 | (searchElement !== searchElement && currentElement !== currentElement)) { // NaN !== NaN 24 | return true; 25 | } 26 | k++; 27 | } 28 | return false; 29 | }; 30 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const helpers = require('./helpers'); 4 | const chokidar = require('chokidar'); 5 | const colors = require('colors'); 6 | const argv = require('yargs').argv; 7 | 8 | let watchersInitialized = false; 9 | let main; 10 | 11 | /* Secure mode */ 12 | 13 | let secureMode = false; 14 | if (argv.secure) secureMode = true; 15 | 16 | let uninstallMode = true; 17 | if (argv['dont-uninstall']) uninstallMode = false; 18 | 19 | /* Watch files and repeat drill 20 | * Add a watcher, call main wrapper to repeat cycle 21 | */ 22 | 23 | let initializeWatchers = () => { 24 | let watcher = chokidar.watch('**/*.js', { 25 | ignored: 'node_modules' 26 | }); 27 | watcher.on('change', main) 28 | .on('unlink', main); 29 | 30 | watchersInitialized = true; 31 | console.log('Watchers initialized'); 32 | }; 33 | 34 | /* Main wrapper 35 | * Get installed modules from package.json 36 | * Get used modules from all files 37 | * Install used modules that are not installed 38 | * Remove installed modules that are not used 39 | * After setup, initialize watchers 40 | */ 41 | 42 | main = () => { 43 | if (!helpers.packageJSONExists()) { 44 | console.log(colors.red('package.json does not exist')); 45 | console.log(colors.red('You can create one by using `npm init`')); 46 | return; 47 | } 48 | 49 | let installedModules = []; 50 | installedModules = helpers.getInstalledModules(); 51 | 52 | let usedModules = helpers.getUsedModules(); 53 | usedModules = helpers.filterRegistryModules(usedModules); 54 | 55 | // removeUnusedModules 56 | 57 | if (uninstallMode) { 58 | let unusedModules = helpers.diff(installedModules, usedModules); 59 | for (let module of unusedModules) helpers.uninstallModule(module); 60 | } 61 | 62 | // installModules 63 | 64 | let modulesNotInstalled = helpers.diff(usedModules, installedModules); 65 | for (let module of modulesNotInstalled) { 66 | if (secureMode) helpers.installModuleIfTrusted(module); 67 | else helpers.installModule(module); 68 | } 69 | 70 | helpers.cleanup(); 71 | if (!watchersInitialized) initializeWatchers(); 72 | }; 73 | 74 | /* Turn the key */ 75 | main(); 76 | 77 | -------------------------------------------------------------------------------- /test/lib/diff.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('chai').should(); 4 | var helpers = require('../../lib/helpers'); 5 | var testData = require('./testData.js'); 6 | 7 | describe('diff', function () { 8 | var installedModules = helpers.getInstalledModules(); 9 | var usedModules = helpers.getUsedModules(); 10 | usedModules = helpers.filterRegistryModules(usedModules); 11 | var unusedModules = helpers.diff(installedModules, usedModules); 12 | var modulesNotInstalled = helpers.diff(usedModules, installedModules); 13 | it('should return unusedModules', function () { 14 | unusedModules.should.deep.equal(testData.unusedModules); 15 | }); 16 | it('should return modulesNotInstalled', function () { 17 | modulesNotInstalled.should.deep.equal(testData.modulesNotInstalled); 18 | }); 19 | }); -------------------------------------------------------------------------------- /test/lib/filterRegistryModules.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('chai').should(); 4 | var helpers = require('../../lib/helpers'); 5 | var testData = require('./testData.js'); 6 | 7 | describe('filterRegistryModules', function () { 8 | var usedModules = helpers.getUsedModules(); 9 | it('should filter registry modules', function () { 10 | helpers.filterRegistryModules(usedModules).should.deep.equal(testData.filteredUsedModules); 11 | }); 12 | }); -------------------------------------------------------------------------------- /test/lib/getInstalledModules.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('chai').should(); 4 | var helpers = require('../../lib/helpers'); 5 | var testData = require('./testData.js'); 6 | 7 | describe('getInstalledModules', function () { 8 | it('should return installed modules', function () { 9 | helpers.getInstalledModules().should.deep.equal(testData.installedModules); 10 | }); 11 | }); -------------------------------------------------------------------------------- /test/lib/getUsedModules.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('chai').should(); 4 | var helpers = require('../../lib/helpers'); 5 | var testData = require('./testData.js'); 6 | 7 | describe('getUsedModules', function () { 8 | it('should return used modules', function () { 9 | helpers.getUsedModules().should.deep.equal(testData.usedModules); 10 | }); 11 | }); -------------------------------------------------------------------------------- /test/lib/packageJSONExists.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('chai').should(); 4 | var helpers = require('../../lib/helpers'); 5 | 6 | var _require = require('child_process'), 7 | execSync = _require.execSync; 8 | 9 | describe('packageJSONExists', function () { 10 | it('should return true', function () { 11 | helpers.packageJSONExists().should.equal(true); 12 | }); 13 | 14 | it('should return false', function () { 15 | execSync('mv package.json package.json.disappeared'); 16 | helpers.packageJSONExists().should.equal(false); 17 | execSync('mv package.json.disappeared package.json'); 18 | }); 19 | }); -------------------------------------------------------------------------------- /test/lib/testData.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var installedModules = [{ name: 'request', dev: false }, { name: 'yargs', dev: false }, { name: 'mocha', dev: true }]; 4 | 5 | var usedModules = [{ name: 'chai', dev: true }, { name: 'chokidar', dev: false }, { name: 'yargs', dev: false }, { name: 'fs', dev: false }, { name: '../src/helpers', dev: false }, { name: '@siddharthkp/auto-install', dev: false }, { name: 'lodash/memoize', dev: false }, { name: 'request', dev: false }, { name: 'ava', dev: false }, { name: 'async', dev: false }]; 6 | 7 | var unusedModules = [{ name: 'mocha', dev: true }]; 8 | 9 | var modulesNotInstalled = [{ name: 'chai', dev: true }, { name: 'chokidar', dev: false }, { name: '@siddharthkp/auto-install', dev: false }, { name: 'lodash', dev: false }, { name: 'ava', dev: false }, { name: 'async', dev: false }]; 10 | 11 | var filteredUsedModules = [{ name: 'chai', dev: true }, { name: 'chokidar', dev: false }, { name: 'yargs', dev: false }, { name: '@siddharthkp/auto-install', dev: false }, { name: 'lodash', dev: false }, { name: 'request', dev: false }, { name: 'ava', dev: false }, { name: 'async', dev: false }]; 12 | 13 | module.exports = { 14 | installedModules: installedModules, 15 | usedModules: usedModules, 16 | unusedModules: unusedModules, 17 | modulesNotInstalled: modulesNotInstalled, 18 | filteredUsedModules: filteredUsedModules 19 | }; -------------------------------------------------------------------------------- /test/src/diff.test.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const helpers = require('../../lib/helpers'); 3 | const testData = require('./testData.js'); 4 | 5 | describe('diff', () => { 6 | let installedModules = helpers.getInstalledModules(); 7 | let usedModules = helpers.getUsedModules(); 8 | usedModules = helpers.filterRegistryModules(usedModules); 9 | let unusedModules = helpers.diff(installedModules, usedModules); 10 | let modulesNotInstalled = helpers.diff(usedModules, installedModules); 11 | it('should return unusedModules', () => { 12 | unusedModules.should.deep.equal(testData.unusedModules); 13 | }); 14 | it('should return modulesNotInstalled', () => { 15 | modulesNotInstalled.should.deep.equal(testData.modulesNotInstalled); 16 | }); 17 | }); 18 | 19 | -------------------------------------------------------------------------------- /test/src/filterRegistryModules.test.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const helpers = require('../../lib/helpers'); 3 | const testData = require('./testData.js'); 4 | 5 | describe('filterRegistryModules', () => { 6 | let usedModules = helpers.getUsedModules(); 7 | it('should filter registry modules', () => { 8 | helpers.filterRegistryModules(usedModules).should.deep.equal(testData.filteredUsedModules); 9 | }); 10 | }); 11 | 12 | -------------------------------------------------------------------------------- /test/src/getInstalledModules.test.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const helpers = require('../../lib/helpers'); 3 | const testData = require('./testData.js'); 4 | 5 | describe('getInstalledModules', () => { 6 | it('should return installed modules', () => { 7 | helpers.getInstalledModules().should.deep.equal(testData.installedModules); 8 | }); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /test/src/getUsedModules.test.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const helpers = require('../../lib/helpers'); 3 | const testData = require('./testData.js'); 4 | 5 | describe('getUsedModules', () => { 6 | it('should return used modules', () => { 7 | helpers.getUsedModules().should.deep.equal(testData.usedModules); 8 | }); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /test/src/packageJSONExists.test.js: -------------------------------------------------------------------------------- 1 | require('chai').should(); 2 | const helpers = require('../../lib/helpers'); 3 | const {execSync} = require('child_process'); 4 | 5 | describe('packageJSONExists', () => { 6 | it('should return true', () => { 7 | helpers.packageJSONExists().should.equal(true); 8 | }); 9 | 10 | it('should return false', () => { 11 | execSync('mv package.json package.json.disappeared'); 12 | helpers.packageJSONExists().should.equal(false); 13 | execSync('mv package.json.disappeared package.json'); 14 | }); 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /test/src/testData.js: -------------------------------------------------------------------------------- 1 | let installedModules = [ 2 | {name: 'request', dev: false}, 3 | {name: 'yargs', dev: false}, 4 | {name: 'mocha', dev: true} 5 | ]; 6 | 7 | let usedModules = [ 8 | {name: 'chai', dev: true}, 9 | {name: 'chokidar', dev: false}, 10 | {name: 'yargs', dev: false}, 11 | {name: 'fs', dev: false}, 12 | {name: '../src/helpers', dev: false}, 13 | {name: '@siddharthkp/auto-install', dev: false}, 14 | {name: 'lodash/memoize', dev: false}, 15 | {name: 'request', dev: false}, 16 | {name: 'ava', dev: false}, 17 | {name: 'async', dev: false} 18 | ]; 19 | 20 | let unusedModules = [ 21 | {name: 'mocha', dev: true} 22 | ]; 23 | 24 | let modulesNotInstalled = [ 25 | {name: 'chai', dev: true}, 26 | {name: 'chokidar', dev: false}, 27 | {name: '@siddharthkp/auto-install', dev: false}, 28 | {name: 'lodash', dev: false}, 29 | {name: 'ava', dev: false}, 30 | {name: 'async', dev: false} 31 | ]; 32 | 33 | let filteredUsedModules = [ 34 | {name: 'chai', dev: true}, 35 | {name: 'chokidar', dev: false}, 36 | {name: 'yargs', dev: false}, 37 | {name: '@siddharthkp/auto-install', dev: false}, 38 | {name: 'lodash', dev: false}, 39 | {name: 'request', dev: false}, 40 | {name: 'ava', dev: false}, 41 | {name: 'async', dev: false} 42 | ]; 43 | 44 | module.exports = { 45 | installedModules, 46 | usedModules, 47 | unusedModules, 48 | modulesNotInstalled, 49 | filteredUsedModules 50 | }; 51 | --------------------------------------------------------------------------------