├── .babelrc ├── .editorconfig ├── .gitignore ├── .travis.yml ├── .vscode └── launch.json ├── README.md ├── appveyor.yml ├── bin └── cli.js ├── index.d.ts ├── lib ├── cli.js ├── in │ ├── best-guess-homepage.js │ ├── create-package-summary.js │ ├── find-module-path.js │ ├── get-installed-packages.js │ ├── get-latest-from-registry.js │ ├── get-unused-packages.js │ ├── index.js │ └── read-package-json.js ├── index.js ├── out │ ├── emoji.js │ ├── install-packages.js │ ├── interactive-update.js │ ├── static-output.js │ └── update-all.js └── state │ ├── debug.js │ ├── init.js │ └── state.js ├── license ├── package-lock.json ├── package.json └── renovate.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ], 5 | "plugins": [ 6 | "transform-runtime", 7 | "transform-object-rest-spread" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Get the plugin for your editor and your 2 | # tab settings will be set automatically. 3 | # http://EditorConfig.org 4 | 5 | # top-most EditorConfig file 6 | root = true 7 | 8 | # Unix-style newlines with newline ending every file 9 | [*] 10 | end_of_line = lf 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | charset = utf-8 15 | trim_trailing_whitespace = true 16 | insert_final_newline = true 17 | 18 | # Indentation override for all JS under lib directory 19 | [*.js] 20 | indent_size = 4 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /node_modules/ 3 | /*.log 4 | /lib-es5/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | os: 6 | - linux 7 | - osx 8 | 9 | node_js: 10 | - 12.22.12 11 | - 10.24.1 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "cli", 11 | "program": "${workspaceFolder}/lib/cli.js", 12 | "args": ["--debug"] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | npm-check 2 | ========= 3 | [![Build Status](https://travis-ci.org/dylang/npm-check.svg?branch=master)](https://travis-ci.org/dylang/npm-check) 4 | [![NPM version](https://badge.fury.io/js/npm-check.svg)](http://badge.fury.io/js/npm-check) 5 | [![npm](https://img.shields.io/npm/dm/npm-check.svg?maxAge=2592000)]() 6 | 7 | > Check for outdated, incorrect, and unused dependencies. 8 | 9 | npm-check -u 10 | 11 | ### Features 12 | 13 | * Tells you what's out of date. 14 | * Provides a link to the package's documentation so you can decide if you want the update. 15 | * Kindly informs you if a dependency is not being used in your code. 16 | * Works on your globally installed packages too, via `-g`. 17 | * **Interactive Update** for less typing and fewer typos, via `-u`. 18 | * Supports public and private [@scoped/packages](https://docs.npmjs.com/getting-started/scoped-packages). 19 | * Supports ES6-style [`import from`](http://exploringjs.com/es6/ch_modules.html) syntax. 20 | * Upgrades your modules using your installed version of npm, including the new `npm@3`, so dependencies go where you expect them. 21 | * Works with any public npm registry, [private registries](https://www.npmjs.com/enterprise), and alternate registries like [Sinopia](https://github.com/rlidwka/sinopia). 22 | * Does not query registries for packages with `private: true` in their package.json. 23 | * Emoji in a command-line app, because command-line apps can be fun too. 24 | * Works with `npm@2` and `npm@3`, as well as newer alternative installers like `ied` and `pnpm`. 25 | 26 | ### Requirements 27 | * Node >= 10.9.0 28 | 29 | ### On the command line 30 | 31 | This is the easiest way to use `npm-check`. 32 | 33 | ### Install 34 | ```bash 35 | npm install -g npm-check 36 | ``` 37 | 38 | ### Use 39 | ```bash 40 | npm-check 41 | ``` 42 | 43 | npm-check 44 | 45 | The result should look like the screenshot, or something nice when your packages are all up-to-date and in use. 46 | 47 | When updates are required it will return a non-zero response code that you can use in your CI tools. 48 | 49 | ### Options 50 | 51 | ``` 52 | Usage 53 | $ npm-check 54 | 55 | Path 56 | Where to check. Defaults to current directory. Use -g for checking global modules. 57 | 58 | Options 59 | -u, --update Interactive update. 60 | -y, --update-all Uninteractive update. Apply all updates without prompting. 61 | -g, --global Look at global modules. 62 | -s, --skip-unused Skip check for unused packages. 63 | -p, --production Skip devDependencies. 64 | -d, --dev-only Look at devDependencies only (skip dependencies). 65 | -i, --ignore Ignore dependencies based on succeeding glob. 66 | -E, --save-exact Save exact version (x.y.z) instead of caret (^x.y.z) in package.json. 67 | --specials List of depcheck specials to include in check for unused dependencies. 68 | --no-color Force or disable color output. 69 | --no-emoji Remove emoji support. No emoji in default in CI environments. 70 | --debug Show debug output. Throw in a gist when creating issues on github. 71 | 72 | Examples 73 | $ npm-check # See what can be updated, what isn't being used. 74 | $ npm-check ../foo # Check another path. 75 | $ npm-check -gu # Update globally installed modules by picking which ones to upgrade. 76 | ``` 77 | 78 | ![npm-check-u](https://cloud.githubusercontent.com/assets/51505/9569912/8c600cd8-4f48-11e5-8757-9387a7a21316.gif) 79 | 80 | #### `-u, --update` 81 | 82 | Show an interactive UI for choosing which modules to update. 83 | 84 | Automatically updates versions referenced in the `package.json`. 85 | 86 | _Based on recommendations from the `npm` team, `npm-check` only updates using `npm install`, not `npm update`. 87 | To avoid using more than one version of `npm` in one directory, `npm-check` will automatically install updated modules 88 | using the version of `npm` installed globally._ 89 | 90 | npm-check -g -u 91 | 92 | ##### Update using [ied](https://github.com/alexanderGugel/ied) or [pnpm](https://github.com/rstacruz/pnpm) 93 | 94 | Set environment variable `NPM_CHECK_INSTALLER` to the name of the installer you wish to use. 95 | 96 | ```bash 97 | NPM_CHECK_INSTALLER=pnpm npm-check -u 98 | ## pnpm install --save-dev foo@version --color=always 99 | ``` 100 | 101 | You can also use this for dry-run testing: 102 | 103 | ```bash 104 | NPM_CHECK_INSTALLER=echo npm-check -u 105 | ``` 106 | 107 | #### `-y, --update-all` 108 | 109 | Updates your dependencies like `--update`, just without any prompt. This is especially useful if you want to automate your dependency updates with `npm-check`. 110 | 111 | #### `-g, --global` 112 | 113 | Check the versions of your globally installed packages. 114 | 115 | If the value of `process.env.NODE_PATH` is set, it will override the default path of global node_modules returned by package [`global-modules`](https://www.npmjs.com/package/global-modules). 116 | 117 | _Tip: Use `npm-check -u -g` to do a safe interactive update of global modules, including npm itself._ 118 | 119 | #### `-s, --skip-unused` 120 | 121 | By default `npm-check` will let you know if any of your modules are not being used by looking at `require` statements 122 | in your code. 123 | 124 | This option will skip that check. 125 | 126 | This is enabled by default when using `global` or `update`. 127 | 128 | #### `-p, --production` 129 | 130 | By default `npm-check` will look at packages listed as `dependencies` and `devDependencies`. 131 | 132 | This option will let it ignore outdated and unused checks for packages listed as `devDependencies`. 133 | 134 | #### `-d, --dev-only` 135 | 136 | Ignore `dependencies` and only check `devDependencies`. 137 | 138 | This option will let it ignore outdated and unused checks for packages listed as `dependencies`. 139 | 140 | #### `-i, --ignore` 141 | 142 | Ignore dependencies that match specified glob. 143 | 144 | `$ npm-check -i babel-*` will ignore all dependencies starting with 'babel-'. 145 | 146 | #### `-E, --save-exact` 147 | 148 | Install packages using `--save-exact`, meaning exact versions will be saved in package.json. 149 | 150 | Applies to both `dependencies` and `devDependencies`. 151 | 152 | #### `--specials` 153 | 154 | Check special (e.g. config) files when looking for unused dependencies. 155 | 156 | `$ npm-check --specials=bin,webpack` will look in the `scripts` section of package.json and in webpack config. 157 | 158 | See [https://github.com/depcheck/depcheck#special](https://github.com/depcheck/depcheck#special) for more information. 159 | 160 | #### `--color, --no-color` 161 | 162 | Enable or disable color support. 163 | 164 | By default `npm-check` uses colors if they are available. 165 | 166 | #### `--emoji, --no-emoji` 167 | 168 | Enable or disable emoji support. Useful for terminals that don't support them. Automatically disabled in CI servers. 169 | 170 | #### `--spinner, --no-spinner` 171 | 172 | Enable or disable the spinner. Useful for terminals that don't support them. Automatically disabled in CI servers. 173 | 174 | ### API 175 | 176 | The API is here in case you want to wrap this with your CI toolset. 177 | 178 | ```js 179 | const npmCheck = require('npm-check'); 180 | 181 | npmCheck(options) 182 | .then(currentState => console.log(currentState.get('packages'))); 183 | ``` 184 | 185 | #### `update` 186 | 187 | * Interactive update. 188 | * default is `false` 189 | 190 | #### `global` 191 | 192 | * Check global modules. 193 | * default is `false` 194 | * `cwd` is automatically set with this option. 195 | 196 | #### `skipUnused` 197 | 198 | * Skip checking for unused packages. 199 | * default is `false` 200 | 201 | #### `ignoreDev` 202 | 203 | * Ignore `devDependencies`. 204 | * This is called `--production` on the command line to match `npm`. 205 | * default is `false` 206 | 207 | #### `devOnly` 208 | 209 | * Ignore `dependencies` and only check `devDependencies`. 210 | * default is `false` 211 | 212 | #### `ignore` 213 | 214 | * Ignore dependencies that match specified glob. 215 | * default is `[]` 216 | 217 | #### `saveExact` 218 | 219 | * Update package.json with exact version `x.y.z` instead of semver range `^x.y.z`. 220 | * default is `false` 221 | 222 | #### `debug` 223 | 224 | * Show debug output. Throw in a gist when creating issues on github. 225 | * default is `false` 226 | 227 | #### `cwd` 228 | 229 | * Override where `npm-check` checks. 230 | * default is `process.cwd()` 231 | 232 | #### `specials` 233 | 234 | * List of [`depcheck`](https://github.com/depcheck/depcheck) special parsers to include. 235 | * default is `''` 236 | 237 | #### `currentState` 238 | 239 | The result of the promise is a `currentState` object, look in [state.js](lib/state/state.js) to see how it works. 240 | 241 | You will probably want `currentState.get('packages')` to get an array of packages and the state of each of them. 242 | 243 | Each item in the array will look like the following: 244 | 245 | ```js 246 | { 247 | moduleName: 'lodash', // name of the module. 248 | homepage: 'https://lodash.com/', // url to the home page. 249 | regError: undefined, // error communicating with the registry 250 | pkgError: undefined, // error reading the package.json 251 | latest: '4.7.0', // latest according to the registry. 252 | installed: '4.6.1', // version installed 253 | isInstalled: true, // Is it installed? 254 | notInstalled: false, // Is it installed? 255 | packageWanted: '4.7.0', // Requested version from the package.json. 256 | packageJson: '^4.6.1', // Version or range requested in the parent package.json. 257 | devDependency: false, // Is this a devDependency? 258 | usedInScripts: undefined, // Array of `scripts` in package.json that use this module. 259 | mismatch: false, // Does the version installed not match the range in package.json? 260 | semverValid: '4.6.1', // Is the installed version valid semver? 261 | easyUpgrade: true, // Will running just `npm install` upgrade the module? 262 | bump: 'minor', // What kind of bump is required to get the latest, such as patch, minor, major. 263 | unused: false // Is this module used in the code? 264 | }, 265 | ``` 266 | 267 | You will also see this if you use `--debug` on the command line. 268 | 269 | ### RC File Support 270 | Additional options can be sent to the depcheck process. See [depcheck API](https://github.com/depcheck/depcheck#api). Create a .npmcheckrc{.json,.yml,.js} file and set the depcheck options under depcheck property. 271 | 272 | For example, to skip packages for unused check, but still want them in the outdated check (so can't use the --ignore option): 273 | ``` 274 | # .npmcheckrc 275 | 276 | depcheck: 277 | ignoreMatches: ["replace-in-file","snyk","sonarqube-scanner"] 278 | 279 | ``` 280 | 281 | ### Inspiration 282 | 283 | * [npm outdated](https://docs.npmjs.com/cli/v7/commands/npm-outdated) - awkward output, requires --depth=0 to be grokable. 284 | * [david](https://github.com/alanshaw/david) - does not work with private registries. 285 | * [update-notifier](https://github.com/yeoman/update-notifier) - for single modules, not everything in package.json. 286 | * [depcheck](https://github.com/depcheck/depcheck) - only part of the puzzle. npm-check uses depcheck. 287 | 288 | ### About the Author 289 | 290 | Hi! Thanks for checking out this project! My name is **Dylan Greene**. When not overwhelmed with my two young kids I enjoy contributing 291 | to the open source community. I'm also a tech lead at [Opower](https://opower.com/). [![@dylang](https://img.shields.io/badge/github-dylang-green.svg)](https://github.com/dylang) [![@dylang](https://img.shields.io/badge/twitter-dylang-blue.svg)](https://twitter.com/dylang) 292 | 293 | Here's some of my other Node projects: 294 | 295 | | Name | Description | npm Downloads | 296 | |---|---|---| 297 | | [`grunt‑notify`](https://github.com/dylang/grunt-notify) | Automatic desktop notifications for Grunt errors and warnings. Supports OS X, Windows, Linux. | [![grunt-notify](https://img.shields.io/npm/dm/grunt-notify.svg?style=flat-square)](https://www.npmjs.org/package/grunt-notify) | 298 | | [`shortid`](https://github.com/dylang/shortid) | Amazingly short non-sequential url-friendly unique id generator. | [![shortid](https://img.shields.io/npm/dm/shortid.svg?style=flat-square)](https://www.npmjs.org/package/shortid) | 299 | | [`space‑hogs`](https://github.com/dylang/space-hogs) | Discover surprisingly large directories from the command line. | [![space-hogs](https://img.shields.io/npm/dm/space-hogs.svg?style=flat-square)](https://www.npmjs.org/package/space-hogs) | 300 | | [`rss`](https://github.com/dylang/node-rss) | RSS feed generator. Add RSS feeds to any project. Supports enclosures and GeoRSS. | [![rss](https://img.shields.io/npm/dm/rss.svg?style=flat-square)](https://www.npmjs.org/package/rss) | 301 | | [`grunt‑prompt`](https://github.com/dylang/grunt-prompt) | Interactive prompt for your Grunt config using console checkboxes, text input with filtering, password fields. | [![grunt-prompt](https://img.shields.io/npm/dm/grunt-prompt.svg?style=flat-square)](https://www.npmjs.org/package/grunt-prompt) | 302 | | [`xml`](https://github.com/dylang/node-xml) | Fast and simple xml generator. Supports attributes, CDATA, etc. Includes tests and examples. | [![xml](https://img.shields.io/npm/dm/xml.svg?style=flat-square)](https://www.npmjs.org/package/xml) | 303 | | [`changelog`](https://github.com/dylang/changelog) | Command line tool (and Node module) that generates a changelog in color output, markdown, or json for modules in npmjs.org's registry as well as any public github.com repo. | [![changelog](https://img.shields.io/npm/dm/changelog.svg?style=flat-square)](https://www.npmjs.org/package/changelog) | 304 | | [`grunt‑attention`](https://github.com/dylang/grunt-attention) | Display attention-grabbing messages in the terminal | [![grunt-attention](https://img.shields.io/npm/dm/grunt-attention.svg?style=flat-square)](https://www.npmjs.org/package/grunt-attention) | 305 | | [`observatory`](https://github.com/dylang/observatory) | Beautiful UI for showing tasks running on the command line. | [![observatory](https://img.shields.io/npm/dm/observatory.svg?style=flat-square)](https://www.npmjs.org/package/observatory) | 306 | | [`anthology`](https://github.com/dylang/anthology) | Module information and stats for any @npmjs user | [![anthology](https://img.shields.io/npm/dm/anthology.svg?style=flat-square)](https://www.npmjs.org/package/anthology) | 307 | | [`grunt‑cat`](https://github.com/dylang/grunt-cat) | Echo a file to the terminal. Works with text, figlets, ascii art, and full-color ansi. | [![grunt-cat](https://img.shields.io/npm/dm/grunt-cat.svg?style=flat-square)](https://www.npmjs.org/package/grunt-cat) | 308 | 309 | _This list was generated using [anthology](https://github.com/dylang/anthology)._ 310 | 311 | ### License 312 | Copyright (c) 2016 Dylan Greene, contributors. 313 | 314 | Released under the [MIT license](https://tldrlegal.com/license/mit-license). 315 | 316 | Screenshots are [CC BY-SA](https://creativecommons.org/licenses/by-sa/4.0/) (Attribution-ShareAlike). 317 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: 12 4 | - nodejs_version: 10 5 | 6 | install: 7 | - ps: Install-Product node $env:nodejs_version 8 | - npm install 9 | - set CI=true 10 | 11 | test_script: 12 | - node --version 13 | - npm --version 14 | - npm run lint 15 | - "node lib\\cli.js || ver > null" 16 | - "echo Exit code: %errorlevel%" 17 | 18 | build: off 19 | shallow_clone: true 20 | clone_depth: 1 21 | version: '{build}' 22 | matrix: 23 | fast_finish: true 24 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var isEs2015; 4 | try { 5 | isEs2015 = new Function('() => {}'); 6 | } catch (e) { 7 | isEs2015 = false; 8 | } 9 | isEs2015 ? require('../lib/cli') : require('../lib-es5/cli'); 10 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module NpmCheck { 2 | interface INpmCheckOptions { 3 | global?: boolean; 4 | update?: boolean; 5 | skipUnused?: boolean; 6 | devOnly?: boolean; 7 | ignoreDev?: boolean; 8 | cwd?: string; 9 | saveExact?: boolean; 10 | currentState?: Object; 11 | } 12 | 13 | type INpmCheckGetSetValues = "packages" | "debug" | "global" | "cwd" | "cwdPackageJson" | "emoji"; 14 | 15 | type INpmVersionBumpType = "patch" | "minor" | "major" | "prerelease" | "build" | "nonSemver" | null; 16 | 17 | interface INpmCheckCurrentState { 18 | get: (key: INpmCheckGetSetValues) => INpmCheckPackage[]; 19 | set: (key: INpmCheckGetSetValues, val: any) => void; 20 | } 21 | 22 | interface INpmCheckPackage { 23 | moduleName: string; // name of the module. 24 | homepage: string; // url to the home page. 25 | regError: any; // error communicating with the registry 26 | pkgError: any; // error reading the package.json 27 | latest: string; // latest according to the registry. 28 | installed: string; // version installed 29 | isInstalled: boolean; // Is it installed? 30 | notInstalled: boolean; // Is it installed? 31 | packageWanted: string; // Requested version from the package.json. 32 | packageJson: string; // Version or range requested in the parent package.json. 33 | devDependency: boolean; // Is this a devDependency? 34 | usedInScripts: undefined | string[], // Array of `scripts` in package.json that use this module. 35 | mismatch: boolean; // Does the version installed not match the range in package.json? 36 | semverValid: string; // Is the installed version valid semver? 37 | easyUpgrade: boolean; // Will running just `npm install` upgrade the module? 38 | bump: INpmVersionBumpType; // What kind of bump is required to get the latest 39 | unused: boolean; // Is this module used in the code? 40 | } 41 | 42 | //The default function returns a promise 43 | export default function(options: INpmCheckOptions): { 44 | then(stateFn: (state: INpmCheckCurrentState) => void): void; 45 | }; 46 | 47 | } 48 | 49 | declare module "npm-check" { 50 | export = NpmCheck.default; 51 | } -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const meow = require('meow'); 5 | const updateNotifier = require('update-notifier'); 6 | const isCI = require('is-ci'); 7 | const createCallsiteRecord = require('callsite-record'); 8 | const pkg = require('../package.json'); 9 | const npmCheck = require('./index'); 10 | const staticOutput = require('./out/static-output'); 11 | const interactiveUpdate = require('./out/interactive-update'); 12 | const updateAll = require('./out/update-all'); 13 | const debug = require('./state/debug'); 14 | const pkgDir = require('pkg-dir'); 15 | const detectPreferredPM = require('preferred-pm'); 16 | 17 | updateNotifier({pkg}).notify(); 18 | 19 | /* eslint-disable indent */ 20 | const cli = meow(` 21 | Usage 22 | $ npm-check 23 | 24 | Path 25 | Where to check. Defaults to current directory. Use -g for checking global modules. 26 | 27 | Options 28 | -u, --update Interactive update. 29 | -y, --update-all Uninteractive update. Apply all updates without prompting. 30 | -g, --global Look at global modules. 31 | -s, --skip-unused Skip check for unused packages. 32 | -p, --production Skip devDependencies. 33 | -d, --dev-only Look at devDependencies only (skip dependencies). 34 | -i, --ignore Ignore dependencies based on succeeding glob. 35 | -E, --save-exact Save exact version (x.y.z) instead of caret (^x.y.z) in package.json. 36 | --specials List of depcheck specials to include in check for unused dependencies. 37 | --no-color Force or disable color output. 38 | --no-emoji Remove emoji support. No emoji in default in CI environments. 39 | --debug Debug output. Throw in a gist when creating issues on github. 40 | 41 | Examples 42 | $ npm-check # See what can be updated, what isn't being used. 43 | $ npm-check ../foo # Check another path. 44 | $ npm-check -gu # Update globally installed modules by picking which ones to upgrade. 45 | `, 46 | { 47 | flags: { 48 | update: { 49 | type: 'boolean', 50 | alias: 'u' 51 | }, 52 | updateAll: { 53 | type: 'boolean', 54 | alias: 'y' 55 | }, 56 | global: { 57 | type: 'boolean', 58 | alias: 'g' 59 | }, 60 | skipUnused: { 61 | type: 'boolean', 62 | alias: 's' 63 | }, 64 | production: { 65 | type: 'boolean', 66 | alias: 'p' 67 | }, 68 | devOnly: { 69 | type: 'boolean', 70 | alias: 'd' 71 | }, 72 | saveExact: { 73 | type: 'boolean', 74 | alias: 'E' 75 | }, 76 | ignore: { 77 | type: 'string', 78 | alias: 'i' 79 | }, 80 | specials: { 81 | type: 'string' 82 | }, 83 | color: { 84 | type: 'boolean' 85 | }, 86 | emoji: { 87 | type: 'boolean', 88 | default: !isCI 89 | }, 90 | debug: { 91 | type: 'boolean' 92 | }, 93 | spinner: { 94 | type: 'boolean', 95 | default: !isCI 96 | } 97 | } 98 | }); 99 | 100 | /* eslint-enable indent */ 101 | 102 | const options = { 103 | cwd: cli.input[0] || pkgDir.sync() || process.cwd(), 104 | update: cli.flags.update, 105 | updateAll: cli.flags.updateAll, 106 | global: cli.flags.global, 107 | skipUnused: cli.flags.skipUnused, 108 | ignoreDev: cli.flags.production, 109 | devOnly: cli.flags.devOnly, 110 | saveExact: cli.flags.saveExact, 111 | specials: cli.flags.specials, 112 | emoji: cli.flags.emoji, 113 | installer: process.env.NPM_CHECK_INSTALLER || 'auto', 114 | debug: cli.flags.debug, 115 | spinner: cli.flags.spinner, 116 | ignore: cli.flags.ignore 117 | }; 118 | 119 | if (options.debug) { 120 | debug('cli.flags', cli.flags); 121 | debug('cli.input', cli.input); 122 | } 123 | 124 | Promise.resolve() 125 | .then(() => { 126 | return options.installer === 'auto' ? 127 | detectPreferredInstaller(options.cwd) : 128 | options.installer; 129 | }) 130 | .then(installer => { 131 | options.installer = installer; 132 | return npmCheck(options); 133 | }) 134 | .then(currentState => { 135 | currentState.inspectIfDebugMode(); 136 | 137 | if (options.updateAll) { 138 | return updateAll(currentState); 139 | } 140 | 141 | if (options.update) { 142 | return interactiveUpdate(currentState); 143 | } 144 | 145 | return staticOutput(currentState); 146 | }) 147 | .catch(error => { 148 | console.log(error.message); 149 | 150 | if (options.debug) { 151 | console.log(createCallsiteRecord(error).renderSync()); 152 | } else { 153 | console.log('For more detail, add `--debug` to the command'); 154 | } 155 | 156 | process.exit(1); 157 | }); 158 | 159 | const SUPPORTED_INSTALLERS = new Set(['npm', 'pnpm', 'ied', 'yarn']); 160 | 161 | async function detectPreferredInstaller(cwd) { 162 | const preferredPM = await detectPreferredPM(cwd); 163 | return preferredPM && SUPPORTED_INSTALLERS.has(preferredPM.name) ? preferredPM.name : 'npm'; 164 | } 165 | -------------------------------------------------------------------------------- /lib/in/best-guess-homepage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const gitUrl = require('giturl'); 4 | 5 | function bestGuessHomepage(data) { 6 | if (!data) { 7 | return false; 8 | } 9 | 10 | const packageDataForLatest = data.versions[data['dist-tags'].latest]; 11 | 12 | return packageDataForLatest.homepage || 13 | packageDataForLatest.bugs && packageDataForLatest.bugs.url && gitUrl.parse(packageDataForLatest.bugs.url.trim()) || 14 | packageDataForLatest.repository && packageDataForLatest.repository.url && gitUrl.parse(packageDataForLatest.repository.url.trim()); 15 | } 16 | 17 | module.exports = bestGuessHomepage; 18 | -------------------------------------------------------------------------------- /lib/in/create-package-summary.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const readPackageJson = require('./read-package-json'); 4 | const getLatestFromRegistry = require('./get-latest-from-registry'); 5 | const findModulePath = require('./find-module-path'); 6 | const _ = require('lodash'); 7 | const semverDiff = require('semver-diff'); 8 | const pathExists = require('path-exists'); 9 | const path = require('path'); 10 | const semver = require('semver'); 11 | const minimatch = require('minimatch'); 12 | 13 | function createPackageSummary(moduleName, currentState) { 14 | const cwdPackageJson = currentState.get('cwdPackageJson'); 15 | 16 | const modulePath = findModulePath(moduleName, currentState); 17 | const packageIsInstalled = pathExists.sync(modulePath); 18 | const modulePackageJson = readPackageJson(path.join(modulePath, 'package.json')); 19 | 20 | // Ignore private packages 21 | const isPrivate = Boolean(modulePackageJson.private); 22 | if (isPrivate) { 23 | return false; 24 | } 25 | 26 | // Ignore packages that are using github or file urls 27 | const packageJsonVersion = cwdPackageJson.dependencies[moduleName] || 28 | cwdPackageJson.devDependencies[moduleName] || 29 | currentState.get('globalPackages')[moduleName]; 30 | 31 | if (packageJsonVersion && !semver.validRange(packageJsonVersion)) { 32 | return false; 33 | } 34 | 35 | // Ignore specified '--ignore' package globs 36 | const ignore = currentState.get('ignore'); 37 | if (ignore) { 38 | const ignoreMatch = Array.isArray(ignore) ? ignore.some(ignoredModule => minimatch(moduleName, ignoredModule)) : minimatch(moduleName, ignore); 39 | if (ignoreMatch) { 40 | return false; 41 | } 42 | } 43 | 44 | const unusedDependencies = currentState.get('unusedDependencies'); 45 | const missingFromPackageJson = currentState.get('missingFromPackageJson'); 46 | 47 | function foundIn(files) { 48 | if (!files) { 49 | return; 50 | } 51 | 52 | return 'Found in: ' + files.map(filepath => filepath.replace(currentState.get('cwd'), '')) 53 | .join(', '); 54 | } 55 | 56 | return getLatestFromRegistry(moduleName) 57 | .then(fromRegistry => { 58 | const installedVersion = modulePackageJson.version; 59 | 60 | const latest = installedVersion && fromRegistry.latest && fromRegistry.next && semver.gt(installedVersion, fromRegistry.latest) ? fromRegistry.next : fromRegistry.latest; 61 | const versions = fromRegistry.versions || []; 62 | 63 | const versionWanted = semver.maxSatisfying(versions, packageJsonVersion); 64 | 65 | const versionToUse = installedVersion || versionWanted; 66 | const usingNonSemver = semver.valid(latest) && semver.lt(latest, '1.0.0-pre'); 67 | 68 | const bump = semver.valid(latest) && 69 | semver.valid(versionToUse) && 70 | (usingNonSemver && semverDiff(versionToUse, latest) ? 'nonSemver' : semverDiff(versionToUse, latest)); 71 | 72 | const unused = _.includes(unusedDependencies, moduleName); 73 | 74 | return { 75 | // info 76 | moduleName: moduleName, 77 | homepage: fromRegistry.homepage, 78 | regError: fromRegistry.error, 79 | pkgError: modulePackageJson.error, 80 | 81 | // versions 82 | latest: latest, 83 | installed: versionToUse, 84 | isInstalled: packageIsInstalled, 85 | notInstalled: !packageIsInstalled, 86 | packageWanted: versionWanted, 87 | packageJson: packageJsonVersion, 88 | 89 | // Missing from package json 90 | notInPackageJson: foundIn(missingFromPackageJson[moduleName]), 91 | 92 | // meta 93 | devDependency: _.has(cwdPackageJson.devDependencies, moduleName), 94 | usedInScripts: _.findKey(cwdPackageJson.scripts, script => { 95 | return script.indexOf(moduleName) !== -1; 96 | }), 97 | mismatch: semver.validRange(packageJsonVersion) && 98 | semver.valid(versionToUse) && 99 | !semver.satisfies(versionToUse, packageJsonVersion), 100 | semverValid: 101 | semver.valid(versionToUse), 102 | easyUpgrade: semver.validRange(packageJsonVersion) && 103 | semver.valid(versionToUse) && 104 | semver.satisfies(latest, packageJsonVersion) && 105 | bump !== 'major', 106 | bump: bump, 107 | 108 | unused: unused 109 | }; 110 | }); 111 | } 112 | 113 | module.exports = createPackageSummary; 114 | -------------------------------------------------------------------------------- /lib/in/find-module-path.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Module = require('module'); 4 | const path = require('path'); 5 | const pathExists = require('path-exists'); 6 | 7 | /** 8 | * Searches the directory hierarchy to return the path to the requested node module. 9 | * If the module can't be found, returns the initial (deepest) tried path. 10 | */ 11 | function findModulePath(moduleName, currentState) { 12 | const cwd = currentState.get('cwd'); 13 | 14 | if (currentState.get('global')) { 15 | return path.join(cwd, moduleName); 16 | } 17 | 18 | // Module._nodeModulePaths does not include some places the node module resolver searches, such as 19 | // the global prefix or other special directories. This is desirable because if a module is missing 20 | // in the project directory we want to be sure to report it as missing. 21 | // We can't use require.resolve because it fails if the module doesn't have an entry point. 22 | const nodeModulesPaths = Module._nodeModulePaths(cwd); 23 | const possibleModulePaths = nodeModulesPaths.map(x => path.join(x, moduleName)); 24 | const modulePath = possibleModulePaths.find(pathExists.sync); 25 | 26 | // if no existing path was found, return the first tried path anyway 27 | return modulePath || path.join(cwd, moduleName); 28 | } 29 | 30 | module.exports = findModulePath; 31 | -------------------------------------------------------------------------------- /lib/in/get-installed-packages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const _ = require('lodash'); 3 | const globby = require('globby'); 4 | const readPackageJson = require('./read-package-json'); 5 | const path = require('path'); 6 | 7 | module.exports = function (cwd) { 8 | const GLOBBY_PACKAGE_JSON = '{*/package.json,@*/*/package.json}'; 9 | const installedPackages = globby.sync(GLOBBY_PACKAGE_JSON, {cwd: cwd}); 10 | 11 | return _(installedPackages) 12 | .map(pkgPath => { 13 | const pkg = readPackageJson(path.resolve(cwd, pkgPath)); 14 | return [pkg.name, pkg.version]; 15 | }) 16 | .fromPairs() 17 | .valueOf(); 18 | }; 19 | -------------------------------------------------------------------------------- /lib/in/get-latest-from-registry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const bestGuessHomepage = require('./best-guess-homepage'); 5 | const semver = require('semver'); 6 | const packageJson = require('package-json'); 7 | const cpuCount = require('os').cpus().length; 8 | const throat = require('throat')(cpuCount); 9 | 10 | function getNpmInfo(packageName) { 11 | return throat(() => packageJson(packageName, { fullMetadata: true, allVersions: true })) 12 | .then(rawData => { 13 | const CRAZY_HIGH_SEMVER = '8000.0.0'; 14 | 15 | const sortedVersions = _(rawData.versions) 16 | .keys() 17 | .remove(_.partial(semver.gt, CRAZY_HIGH_SEMVER)) 18 | .sort(semver.compare) 19 | .valueOf(); 20 | 21 | const latest = rawData['dist-tags'].latest; 22 | const next = rawData['dist-tags'].next; 23 | const latestStableRelease = semver.satisfies(latest, '*') ? 24 | latest : 25 | semver.maxSatisfying(sortedVersions, '*'); 26 | return { 27 | latest: latestStableRelease, 28 | next: next, 29 | versions: sortedVersions, 30 | homepage: bestGuessHomepage(rawData) 31 | }; 32 | }).catch(err => { 33 | const errorMessage = `Registry error ${err.message}`; 34 | return { 35 | error: errorMessage 36 | }; 37 | }); 38 | } 39 | 40 | module.exports = getNpmInfo; 41 | -------------------------------------------------------------------------------- /lib/in/get-unused-packages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const depcheck = require('depcheck'); 4 | const ora = require('ora'); 5 | const _ = require('lodash'); 6 | const { rcFile } = require('rc-config-loader'); 7 | 8 | function skipUnused(currentState) { 9 | return currentState.get('skipUnused') || // manual option to ignore this 10 | currentState.get('global') || // global modules 11 | currentState.get('update') || // in the process of doing an update 12 | !currentState.get('cwdPackageJson').name; // there's no package.json 13 | } 14 | 15 | function loadRcFile(rcFileName) { 16 | try { 17 | const results = rcFile(rcFileName); 18 | // Not Found 19 | if (!results) { 20 | return {}; 21 | } 22 | return results.config; 23 | } catch (error) { 24 | console.error(`Error parsing rc file; skipping it; error: ${error.message}`); 25 | return {}; // default value 26 | } 27 | } 28 | 29 | function getSpecialParsers(currentState) { 30 | const specialsInput = currentState.get('specials'); 31 | if (!specialsInput) return; 32 | return specialsInput 33 | .split(',') 34 | .map((special) => depcheck.special[special]) 35 | .filter(Boolean); 36 | } 37 | 38 | function checkUnused(currentState) { 39 | const spinner = ora(`Checking for unused packages. --skip-unused if you don't want this.`); 40 | spinner.enabled = spinner.enabled && currentState.get('spinner'); 41 | spinner.start(); 42 | 43 | return new Promise(resolve => { 44 | if (skipUnused(currentState)) { 45 | resolve(currentState); 46 | return; 47 | } 48 | 49 | const depcheckDefaults = { 50 | ignoreDirs: [ 51 | 'sandbox', 52 | 'dist', 53 | 'generated', 54 | '.generated', 55 | 'build', 56 | 'fixtures', 57 | 'jspm_packages' 58 | ], 59 | ignoreMatches: [ 60 | 'gulp-*', 61 | 'grunt-*', 62 | 'karma-*', 63 | 'angular-*', 64 | 'babel-*', 65 | 'metalsmith-*', 66 | 'eslint-plugin-*', 67 | '@types/*', 68 | 'grunt', 69 | 'mocha', 70 | 'ava' 71 | ], 72 | specials: getSpecialParsers(currentState) 73 | }; 74 | 75 | const npmCheckRc = loadRcFile('npmcheck'); 76 | 77 | const depcheckOptions = { 78 | ...depcheckDefaults, 79 | ...npmCheckRc.depcheck 80 | }; 81 | 82 | depcheck(currentState.get('cwd'), depcheckOptions, resolve); 83 | }).then(depCheckResults => { 84 | spinner.stop(); 85 | const unusedDependencies = [].concat(depCheckResults.dependencies, depCheckResults.devDependencies); 86 | currentState.set('unusedDependencies', unusedDependencies); 87 | 88 | const cwdPackageJson = currentState.get('cwdPackageJson'); 89 | 90 | // currently missing will return devDependencies that aren't really missing 91 | const missingFromPackageJson = _.omit(depCheckResults.missing || {}, 92 | Object.keys(cwdPackageJson.dependencies), Object.keys(cwdPackageJson.devDependencies)); 93 | currentState.set('missingFromPackageJson', missingFromPackageJson); 94 | return currentState; 95 | }); 96 | } 97 | 98 | module.exports = checkUnused; 99 | -------------------------------------------------------------------------------- /lib/in/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const co = require('co'); 3 | const extend = require('xtend'); 4 | const ora = require('ora'); 5 | const getUnusedPackages = require('./get-unused-packages'); 6 | const createPackageSummary = require('./create-package-summary'); 7 | 8 | module.exports = function (currentState) { 9 | return co(function *() { 10 | yield getUnusedPackages(currentState); 11 | 12 | const spinner = ora(`Checking npm registries for updated packages.`); 13 | spinner.enabled = spinner.enabled && currentState.get('spinner'); 14 | spinner.start(); 15 | 16 | const cwdPackageJson = currentState.get('cwdPackageJson'); 17 | 18 | function dependencies(pkg) { 19 | if (currentState.get('global')) { 20 | return currentState.get('globalPackages'); 21 | } 22 | 23 | if (currentState.get('ignoreDev')) { 24 | return pkg.dependencies; 25 | } 26 | 27 | if (currentState.get('devOnly')) { 28 | return pkg.devDependencies; 29 | } 30 | 31 | return extend(pkg.dependencies, pkg.devDependencies); 32 | } 33 | 34 | const allDependencies = dependencies(cwdPackageJson); 35 | const allDependenciesIncludingMissing = Object.keys(extend(allDependencies, currentState.get('missingFromPackageJson'))); 36 | 37 | const arrayOfPackageInfo = yield allDependenciesIncludingMissing 38 | .map(moduleName => createPackageSummary(moduleName, currentState)) 39 | .filter(Boolean); 40 | 41 | currentState.set('packages', arrayOfPackageInfo); 42 | 43 | spinner.stop(); 44 | return currentState; 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /lib/in/read-package-json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const extend = require('xtend'); 4 | 5 | function readPackageJson(filename) { 6 | let pkg; 7 | let error; 8 | try { 9 | pkg = require(filename); 10 | } catch (e) { 11 | if (e.code === 'MODULE_NOT_FOUND') { 12 | error = new Error(`A package.json was not found at ${filename}`); 13 | } else { 14 | error = new Error(`A package.json was found at ${filename}, but it is not valid.`); 15 | } 16 | } 17 | return extend({devDependencies: {}, dependencies: {}, error: error}, pkg) 18 | } 19 | 20 | module.exports = readPackageJson; 21 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const npmCheck = require('./in'); 4 | const createState = require('./state/state'); 5 | 6 | async function init(userOptions) { 7 | return npmCheck(await createState(userOptions)); 8 | } 9 | 10 | module.exports = init; 11 | -------------------------------------------------------------------------------- /lib/out/emoji.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emoji = require('node-emoji'); 4 | 5 | let emojiEnabled = true; 6 | 7 | function output(name) { 8 | if (emojiEnabled) { 9 | return emoji.emojify(name); 10 | } 11 | 12 | return ''; 13 | } 14 | 15 | function enabled(val) { 16 | emojiEnabled = val; 17 | } 18 | 19 | module.exports = output; 20 | module.exports.enabled = enabled; 21 | -------------------------------------------------------------------------------- /lib/out/install-packages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chalk = require('chalk'); 4 | const execa = require('execa'); 5 | const ora = require('ora'); 6 | 7 | function install(packages, currentState) { 8 | if (!packages.length) { 9 | return Promise.resolve(currentState); 10 | } 11 | 12 | const installer = currentState.get('installer'); 13 | const color = chalk.supportsColor ? '--color=always' : null; 14 | 15 | const isYarn = installer === 'yarn'; 16 | 17 | const installGlobal = currentState.get('global') ? (isYarn ? 'global' : '--global'): null; 18 | const saveExact = currentState.get('saveExact') ? (isYarn ? '--exact' : '--save-exact') : null; 19 | 20 | const installCmd = isYarn ? 'add' : 'install'; 21 | 22 | const npmArgs = [installCmd] 23 | .concat(installGlobal) 24 | .concat(saveExact) 25 | .concat(packages) 26 | .concat(color) 27 | .filter(Boolean); 28 | 29 | console.log(''); 30 | console.log(`$ ${chalk.green(installer)} ${chalk.green(npmArgs.join(' '))}`); 31 | const spinner = ora(`Installing using ${chalk.green(installer)}...`); 32 | spinner.enabled = spinner.enabled && currentState.get('spinner'); 33 | spinner.start(); 34 | 35 | return execa(installer, npmArgs, {cwd: currentState.get('cwd')}).then(output => { 36 | spinner.stop(); 37 | console.log(output.stdout); 38 | console.log(output.stderr); 39 | 40 | return currentState; 41 | }).catch(err => { 42 | spinner.stop(); 43 | throw err; 44 | }); 45 | } 46 | 47 | module.exports = install; 48 | -------------------------------------------------------------------------------- /lib/out/interactive-update.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const inquirer = require('inquirer'); 5 | const chalk = require('chalk'); 6 | const table = require('text-table'); 7 | const installPackages = require('./install-packages'); 8 | const emoji = require('./emoji'); 9 | const stripAnsi = require('strip-ansi'); 10 | 11 | const UI_GROUPS = [ 12 | { 13 | title: chalk.bold.underline.green('Update package.json to match version installed.'), 14 | filter: {mismatch: true, bump: null} 15 | }, 16 | { 17 | title: `${chalk.bold.underline.green('Missing.')} ${chalk.green('You probably want these.')}`, 18 | filter: {notInstalled: true, bump: null} 19 | }, 20 | { 21 | title: `${chalk.bold.underline.green('Patch Update')} ${chalk.green('Backwards-compatible bug fixes.')}`, 22 | filter: {bump: 'patch'} 23 | }, 24 | { 25 | title: `${chalk.yellow.underline.bold('Minor Update')} ${chalk.yellow('New backwards-compatible features.')}`, 26 | bgColor: 'yellow', 27 | filter: {bump: 'minor'} 28 | }, 29 | { 30 | title: `${chalk.red.underline.bold('Major Update')} ${chalk.red('Potentially breaking API changes. Use caution.')}`, 31 | filter: {bump: 'major'} 32 | }, 33 | { 34 | title: `${chalk.magenta.underline.bold('Non-Semver')} ${chalk.magenta('Versions less than 1.0.0, caution.')}`, 35 | filter: {bump: 'nonSemver'} 36 | } 37 | ]; 38 | 39 | function label(pkg) { 40 | const bumpInstalled = pkg.bump ? pkg.installed : ''; 41 | const installed = pkg.mismatch ? pkg.packageJson : bumpInstalled; 42 | const name = chalk.yellow(pkg.moduleName); 43 | const type = pkg.devDependency ? chalk.green(' devDep') : ''; 44 | const missing = pkg.notInstalled ? chalk.red(' missing') : ''; 45 | const homepage = pkg.homepage ? chalk.blue.underline(pkg.homepage) : ''; 46 | return [ 47 | name + type + missing, 48 | installed, 49 | installed && '❯', 50 | chalk.bold(pkg.latest || ''), 51 | pkg.latest ? homepage : pkg.regError || pkg.pkgError 52 | ]; 53 | } 54 | 55 | function short(pkg) { 56 | return `${pkg.moduleName}@${pkg.latest}`; 57 | } 58 | 59 | function choice(pkg) { 60 | if (!pkg.mismatch && !pkg.bump && !pkg.notInstalled) { 61 | return false; 62 | } 63 | 64 | return { 65 | value: pkg, 66 | name: label(pkg), 67 | short: short(pkg) 68 | }; 69 | } 70 | 71 | function unselectable(options) { 72 | return new inquirer.Separator(chalk.reset(options ? options.title : ' ')); 73 | } 74 | 75 | function createChoices(packages, options) { 76 | const filteredChoices = _.filter(packages, options.filter); 77 | 78 | const choices = filteredChoices.map(choice) 79 | .filter(Boolean); 80 | 81 | const choicesAsATable = table(_.map(choices, 'name'), { 82 | align: ['l', 'l', 'l'], 83 | stringLength: function (str) { 84 | return stripAnsi(str).length; 85 | } 86 | }).split('\n'); 87 | 88 | const choicesWithTableFormating = _.map(choices, (choice, i) => { 89 | choice.name = choicesAsATable[i]; 90 | return choice; 91 | }); 92 | 93 | if (choicesWithTableFormating.length) { 94 | choices.unshift(unselectable(options)); 95 | choices.unshift(unselectable()); 96 | return choices; 97 | } 98 | } 99 | 100 | function buildPackageToUpdate(moduleName, version, isYarn, saveExact) { 101 | // handle adding ^ for yarn, npm seems to handle this if not exact 102 | return (isYarn && !saveExact) ? moduleName + '@^' + version : moduleName + '@' + version; 103 | } 104 | 105 | function interactive(currentState) { 106 | const packages = currentState.get('packages'); 107 | 108 | if (currentState.get('debug')) { 109 | console.log('packages', packages); 110 | } 111 | 112 | const choicesGrouped = UI_GROUPS.map(group => createChoices(packages, group)) 113 | .filter(Boolean); 114 | 115 | const choices = _.flatten(choicesGrouped); 116 | 117 | if (!choices.length) { 118 | console.log(`${emoji(':heart: ')}Your modules look ${chalk.bold('amazing')}. Keep up the great work.${emoji(' :heart:')}`); 119 | return; 120 | } 121 | 122 | choices.push(unselectable()); 123 | choices.push(unselectable({title: 'Space to select. Enter to start upgrading. Control-C to cancel.'})); 124 | 125 | const questions = [ 126 | { 127 | name: 'packages', 128 | message: 'Choose which packages to update.', 129 | type: 'checkbox', 130 | choices: choices.concat(unselectable()), 131 | pageSize: process.stdout.rows - 2 132 | } 133 | ]; 134 | 135 | return inquirer.prompt(questions).then(answers => { 136 | const packagesToUpdate = answers.packages; 137 | const isYarn = currentState.get('installer') === 'yarn'; 138 | const saveExact = currentState.get('saveExact'); 139 | 140 | if (!packagesToUpdate || !packagesToUpdate.length) { 141 | console.log('No packages selected for update.'); 142 | return false; 143 | } 144 | 145 | const saveDependencies = packagesToUpdate 146 | .filter(pkg => !pkg.devDependency) 147 | .map(pkg => buildPackageToUpdate(pkg.moduleName, pkg.latest, isYarn, saveExact)); 148 | 149 | const saveDevDependencies = packagesToUpdate 150 | .filter(pkg => pkg.devDependency) 151 | .map(pkg => buildPackageToUpdate(pkg.moduleName, pkg.latest, isYarn, saveExact)); 152 | 153 | const updatedPackages = packagesToUpdate 154 | .map(pkg => buildPackageToUpdate(pkg.moduleName, pkg.latest, isYarn, saveExact)).join(', '); 155 | 156 | if (!currentState.get('global')) { 157 | if (saveDependencies.length) { 158 | !isYarn && saveDependencies.push('--save'); 159 | } 160 | 161 | if (saveDevDependencies.length) { 162 | isYarn ? saveDevDependencies.push('--dev') : saveDevDependencies.push('--save-dev'); 163 | } 164 | } 165 | 166 | return installPackages(saveDependencies, currentState) 167 | .then(currentState => installPackages(saveDevDependencies, currentState)) 168 | .then(currentState => { 169 | console.log(''); 170 | console.log(chalk.green(`[npm-check] Update complete!`)); 171 | console.log(chalk.green('[npm-check] ' + updatedPackages)); 172 | console.log(chalk.green(`[npm-check] You should re-run your tests to make sure everything works with the updates.`)); 173 | return currentState; 174 | }); 175 | }); 176 | } 177 | 178 | module.exports = interactive; 179 | -------------------------------------------------------------------------------- /lib/out/static-output.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chalk = require('chalk'); 4 | const _ = require('lodash'); 5 | const table = require('text-table'); 6 | const emoji = require('./emoji'); 7 | const stripAnsi = require('strip-ansi'); 8 | 9 | function uppercaseFirstLetter(str) { 10 | return str[0].toUpperCase() + str.substr(1); 11 | } 12 | 13 | function render(pkg, currentState) { 14 | const packageName = pkg.moduleName; 15 | const rows = []; 16 | 17 | const indent = ' ' + emoji(' '); 18 | 19 | const installer = currentState.get('installer'); 20 | const isYarn = installer === 'yarn'; 21 | 22 | const args = [isYarn ? 'add' : 'install']; 23 | if (currentState.get('global')) { 24 | isYarn ? args.unshift('global') : args.push('--global'); 25 | } 26 | 27 | const flags = []; 28 | if (isYarn) { 29 | pkg.devDependency && flags.push('--dev'); 30 | } else { 31 | pkg.devDependency ? flags.push('--save-dev') : flags.push('--save'); 32 | } 33 | 34 | const upgradeCommand = `${installer} ${args.join(' ')} ${packageName}@${pkg.latest} ${flags.join(' ')}`; 35 | const upgradeMessage = `${chalk.green(upgradeCommand)} to go from ${pkg.installed} to ${pkg.latest}`; 36 | // DYLAN: clean this up 37 | const status = _([ 38 | pkg.notInstalled ? chalk.bgRed.white.bold(emoji(' :worried: ') + ' MISSING! ') + ' Not installed.' : '', 39 | pkg.notInPackageJson ? chalk.bgRed.white.bold(emoji(' :worried: ') + ' PKG ERR! ') + ' Not in the package.json. ' + pkg.notInPackageJson : '', 40 | pkg.pkgError && !pkg.notInstalled ? chalk.bgGreen.white.bold(emoji(' :worried: ') + ' PKG ERR! ') + ' ' + chalk.red(pkg.pkgError.message) : '', 41 | pkg.bump && pkg.easyUpgrade ? [ 42 | chalk.bgGreen.white.bold(emoji(' :heart_eyes: ') + ' UPDATE! ') + ' Your local install is out of date. ' + chalk.blue.underline(pkg.homepage || ''), 43 | indent + upgradeMessage 44 | ] : '', 45 | pkg.bump && !pkg.easyUpgrade ? [ 46 | chalk.white.bold.bgGreen((pkg.bump === 'nonSemver' ? emoji(' :sunglasses: ') + ' new ver! '.toUpperCase() : emoji(' :sunglasses: ') + ' ' + pkg.bump.toUpperCase() + ' UP ')) + ' ' + uppercaseFirstLetter(pkg.bump) + ' update available. ' + chalk.blue.underline(pkg.homepage || ''), 47 | indent + upgradeMessage 48 | ] : '', 49 | pkg.unused ? [ 50 | chalk.black.bold.bgWhite(emoji(' :confused: ') + ' NOTUSED? ') + ` ${chalk.yellow(`Still using ${packageName}?`)}`, 51 | indent + `Depcheck did not find code similar to ${chalk.green(`require('${packageName}')`)} or ${chalk.green(`import from '${packageName}'`)}.`, 52 | indent + `Check your code before removing as depcheck isn't able to foresee all ways dependencies can be used.`, 53 | indent + `Use rc file options to remove unused check, but still monitor for outdated version:`, 54 | indent + ` .npmcheckrc {"depcheck": {"ignoreMatches": ["${packageName}"]}}`, 55 | indent + `Use ${chalk.green('--skip-unused')} to skip this check.`, 56 | indent + `To remove this package: ${chalk.green( 57 | isYarn 58 | ? `yarn remove ${packageName} ${pkg.devDependency ? '--dev' : ''}` 59 | : `npm uninstall ${packageName} --save${pkg.devDependency ? '-dev' : ''}` 60 | )}` 61 | ] : '', 62 | pkg.mismatch && !pkg.bump ? chalk.bgRed.yellow.bold(emoji(' :interrobang: ') + ' MISMATCH ') + ' Installed version does not match package.json. ' + pkg.installed + ' ≠ ' + pkg.packageJson : '', 63 | pkg.regError ? chalk.bgRed.white.bold(emoji(' :no_entry: ') + ' NPM ERR! ') + ' ' + chalk.red(pkg.regError) : '' 64 | ]) 65 | .flatten() 66 | .compact() 67 | .valueOf(); 68 | 69 | if (!status.length) { 70 | return false; 71 | } 72 | 73 | rows.push( 74 | [ 75 | chalk.yellow(packageName), 76 | status.shift() 77 | ]); 78 | 79 | while (status.length) { 80 | rows.push([ 81 | ' ', 82 | status.shift() 83 | ]); 84 | } 85 | 86 | rows.push( 87 | [ 88 | ' ' 89 | ]); 90 | 91 | return rows; 92 | } 93 | 94 | function outputConsole(currentState) { 95 | const packages = currentState.get('packages'); 96 | 97 | const rows = packages.reduce((acc, pkg) => { 98 | return acc.concat(render(pkg, currentState)); 99 | }, []) 100 | .filter(Boolean); 101 | 102 | if (rows.length) { 103 | const renderedTable = table(rows, { 104 | stringLength: s => stripAnsi(s).length 105 | }); 106 | 107 | console.log(''); 108 | console.log(renderedTable); 109 | console.log(`Use ${chalk.green(`npm-check -${currentState.get('global') ? 'g' : ''}u`)} for interactive update.`); 110 | process.exitCode = 1; 111 | } else { 112 | console.log(`${emoji(':heart: ')}Your modules look ${chalk.bold('amazing')}. Keep up the great work.${emoji(' :heart:')}`); 113 | process.exitCode = 0; 114 | } 115 | } 116 | 117 | module.exports = outputConsole; 118 | -------------------------------------------------------------------------------- /lib/out/update-all.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chalk = require('chalk'); 4 | const installPackages = require('./install-packages'); 5 | const emoji = require('./emoji'); 6 | 7 | function buildPackageToUpdate(moduleName, version, isYarn, saveExact) { 8 | // handle adding ^ for yarn, npm seems to handle this if not exact 9 | return (isYarn && !saveExact) ? moduleName + '@^' + version : moduleName + '@' + version; 10 | } 11 | 12 | function updateAll(currentState) { 13 | const packages = currentState.get('packages'); 14 | 15 | if (currentState.get('debug')) { 16 | console.log('packages', packages); 17 | } 18 | 19 | const packagesToUpdate = packages.filter(packageEntry => packageEntry.mismatch || packageEntry.notInstalled || packageEntry.bump ); 20 | 21 | if (!packagesToUpdate.length) { 22 | console.log(`${emoji(':heart: ')}Your modules look ${chalk.bold('amazing')}. Keep up the great work.${emoji(' :heart:')}`); 23 | return; 24 | } 25 | 26 | const isYarn = currentState.get('installer') === 'yarn'; 27 | const saveExact = currentState.get('saveExact'); 28 | 29 | const saveDependencies = packagesToUpdate 30 | .filter(pkg => !pkg.devDependency) 31 | .map(pkg => buildPackageToUpdate(pkg.moduleName, pkg.latest, isYarn, saveExact)); 32 | 33 | const saveDevDependencies = packagesToUpdate 34 | .filter(pkg => pkg.devDependency) 35 | .map(pkg => buildPackageToUpdate(pkg.moduleName, pkg.latest, isYarn, saveExact)); 36 | 37 | const updatedPackages = packagesToUpdate 38 | .map(pkg => buildPackageToUpdate(pkg.moduleName, pkg.latest, isYarn, saveExact)).join(', '); 39 | 40 | if (!currentState.get('global')) { 41 | if (saveDependencies.length) { 42 | !isYarn && saveDependencies.push('--save'); 43 | } 44 | 45 | if (saveDevDependencies.length) { 46 | isYarn 47 | ? saveDevDependencies.push('--dev') 48 | : saveDevDependencies.push('--save-dev'); 49 | } 50 | } 51 | 52 | return installPackages(saveDependencies, currentState) 53 | .then(currentState => installPackages(saveDevDependencies, currentState)) 54 | .then(currentState => { 55 | console.log(''); 56 | console.log(chalk.green(`[npm-check] Update complete!`)); 57 | console.log(chalk.green('[npm-check] ' + updatedPackages)); 58 | console.log(chalk.green(`[npm-check] You should re-run your tests to make sure everything works with the updates.`)); 59 | return currentState; 60 | }); 61 | } 62 | 63 | module.exports = updateAll; 64 | -------------------------------------------------------------------------------- /lib/state/debug.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const chalk = require('chalk'); 3 | 4 | function debug() { 5 | console.log(chalk.green('[npm-check] debug')); 6 | console.log.apply(console, arguments); 7 | console.log(`${chalk.green('===============================')}`); 8 | } 9 | 10 | module.exports = debug; 11 | -------------------------------------------------------------------------------- /lib/state/init.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const _ = require('lodash'); 3 | const path = require('path'); 4 | const globalModulesPath = require('global-modules'); 5 | const readPackageJson = require('../in/read-package-json'); 6 | const globalPackages = require('../in/get-installed-packages'); 7 | const emoji = require('../out/emoji'); 8 | const fs = require('fs'); 9 | const chalk = require('chalk'); 10 | 11 | function init(currentState, userOptions) { 12 | return new Promise((resolve, reject) => { 13 | _.each(userOptions, (value, key) => currentState.set(key, value)); 14 | 15 | if (currentState.get('global')) { 16 | let modulesPath = globalModulesPath; 17 | 18 | if (process.env.NODE_PATH) { 19 | if (process.env.NODE_PATH.indexOf(path.delimiter) !== -1) { 20 | modulesPath = process.env.NODE_PATH.split(path.delimiter)[0]; 21 | console.log(chalk.yellow('warning: Using the first of multiple paths specified in NODE_PATH')); 22 | } else { 23 | modulesPath = process.env.NODE_PATH; 24 | } 25 | } 26 | 27 | if (!fs.existsSync(modulesPath)) { 28 | throw new Error('Path "' + modulesPath + '" does not exist. Please check the NODE_PATH environment variable.'); 29 | } 30 | 31 | console.log(chalk.green('The global path you are searching is: ' + modulesPath)); 32 | 33 | currentState.set('cwd', globalModulesPath); 34 | currentState.set('globalPackages', globalPackages(modulesPath)); 35 | } else { 36 | const cwd = path.resolve(currentState.get('cwd')); 37 | const pkg = readPackageJson(path.join(cwd, 'package.json')); 38 | currentState.set('cwdPackageJson', pkg); 39 | currentState.set('cwd', cwd); 40 | } 41 | 42 | emoji.enabled(currentState.get('emoji')); 43 | 44 | if (currentState.get('cwdPackageJson').error) { 45 | return reject(currentState.get('cwdPackageJson').error); 46 | } 47 | 48 | return resolve(currentState); 49 | }); 50 | } 51 | 52 | module.exports = init; 53 | -------------------------------------------------------------------------------- /lib/state/state.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const extend = require('xtend'); 3 | const init = require('./init'); 4 | const debug = require('./debug'); 5 | 6 | const defaultOptions = { 7 | update: false, 8 | updateAll: false, 9 | global: false, 10 | cwd: process.cwd(), 11 | skipUnused: false, 12 | 13 | ignoreDev: false, 14 | devOnly: false, 15 | forceColor: false, 16 | saveExact: false, 17 | specials: '', 18 | debug: false, 19 | emoji: true, 20 | spinner: false, 21 | installer: 'npm', 22 | ignore: [], 23 | 24 | globalPackages: {}, 25 | cwdPackageJson: {devDependencies: {}, dependencies: {}}, 26 | 27 | packages: false, 28 | unusedDependencies: false, 29 | missingFromPackageJson: {} 30 | }; 31 | 32 | function state(userOptions) { 33 | const currentStateObject = extend(defaultOptions); 34 | 35 | function get(key) { 36 | if (!currentStateObject.hasOwnProperty(key)) { 37 | throw new Error(`Can't get unknown option "${key}".`); 38 | } 39 | return currentStateObject[key]; 40 | } 41 | 42 | function set(key, value) { 43 | if (get('debug')) { 44 | debug('set key', key, 'to value', value); 45 | } 46 | 47 | if (currentStateObject.hasOwnProperty(key)) { 48 | currentStateObject[key] = value; 49 | } else { 50 | throw new Error(`unknown option "${key}" setting to "${JSON.stringify(value, false, 4)}".`); 51 | } 52 | } 53 | 54 | function inspectIfDebugMode() { 55 | if (get('debug')) { 56 | inspect(); 57 | } 58 | } 59 | 60 | function inspect() { 61 | debug('current state', all()); 62 | } 63 | 64 | function all() { 65 | return currentStateObject; 66 | } 67 | 68 | const currentState = { 69 | get: get, 70 | set: set, 71 | all, 72 | inspectIfDebugMode 73 | }; 74 | 75 | return init(currentState, userOptions); 76 | } 77 | module.exports = state; 78 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Dylan Greene 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-check", 3 | "version": "6.0.1", 4 | "description": "Check for outdated, incorrect, and unused dependencies.", 5 | "main": "lib", 6 | "engines": { 7 | "node": ">=22.16.0" 8 | }, 9 | "types": "./index.d.ts", 10 | "typings": "./index.d.ts", 11 | "scripts": { 12 | "lint": "xo ./lib/*.js", 13 | "test": "npm run lint && ./bin/cli.js || echo Exit Status: $?.", 14 | "transpile": "babel lib --out-dir lib-es5", 15 | "watch": "babel lib --out-dir lib-es5 --watch", 16 | "prepublish": "npm run transpile" 17 | }, 18 | "xo": { 19 | "space": 4, 20 | "rules": { 21 | "no-warning-comments": [ 22 | 0 23 | ], 24 | "global-require": [ 25 | 0 26 | ] 27 | } 28 | }, 29 | "bin": { 30 | "npm-check": "bin/cli.js" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/dylang/npm-check.git" 35 | }, 36 | "keywords": [ 37 | "npm", 38 | "outdated", 39 | "dependencies", 40 | "unused", 41 | "changelog", 42 | "check", 43 | "updates", 44 | "api", 45 | "interactive", 46 | "cli", 47 | "safe", 48 | "updating", 49 | "updater", 50 | "installer", 51 | "devDependencies" 52 | ], 53 | "author": { 54 | "name": "Dylan Greene", 55 | "email": "dylang@gmail.com" 56 | }, 57 | "license": "MIT", 58 | "bugs": { 59 | "url": "https://github.com/dylang/npm-check/issues" 60 | }, 61 | "homepage": "https://github.com/dylang/npm-check", 62 | "files": [ 63 | "bin", 64 | "lib", 65 | "lib-es5" 66 | ], 67 | "dependencies": { 68 | "callsite-record": "^4.1.5", 69 | "chalk": "^5.4.1", 70 | "co": "^4.6.0", 71 | "depcheck": "^1.4.7", 72 | "execa": "^9.6.0", 73 | "giturl": "^2.0.0", 74 | "global-modules": "^2.0.0", 75 | "globby": "^14.1.0", 76 | "inquirer": "^12.6.3", 77 | "is-ci": "^4.1.0", 78 | "lodash": "^4.17.21", 79 | "meow": "^13.2.0", 80 | "minimatch": "^10.0.1", 81 | "node-emoji": "^2.2.0", 82 | "ora": "^8.2.0", 83 | "package-json": "^10.0.1", 84 | "path-exists": "^5.0.0", 85 | "pkg-dir": "^8.0.0", 86 | "preferred-pm": "^4.1.1", 87 | "rc-config-loader": "^4.1.3", 88 | "semver": "^7.7.2", 89 | "semver-diff": "^4.0.0", 90 | "strip-ansi": "^7.1.0", 91 | "text-table": "^0.2.0", 92 | "throat": "^6.0.2", 93 | "update-notifier": "^7.3.1", 94 | "xtend": "^4.0.2" 95 | }, 96 | "devDependencies": { 97 | "babel-runtime": "^6.26.0", 98 | "babel-cli": "^6.26.0", 99 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 100 | "babel-plugin-transform-runtime": "^6.23.0", 101 | "babel-preset-es2015": "^6.24.1", 102 | "xo": "^0.37.1" 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/renovate", 3 | "extends": [ 4 | "config:base", 5 | ":rebaseStalePrs", 6 | ":semanticCommits", 7 | ":automergeAll", 8 | ":maintainLockFilesWeekly", 9 | ":prHourlyLimitNone" 10 | ], 11 | "timezone": "America/New_York", 12 | "rangeStrategy": "bump", 13 | "schedule": [ "every weekend"], 14 | "updateNotScheduled": false, 15 | "lockFileMaintenance": { 16 | "enabled": true, 17 | "schedule": [ "every weekend" ] 18 | } 19 | } 20 | --------------------------------------------------------------------------------