├── .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 | [](https://travis-ci.org/dylang/npm-check)
4 | [](http://badge.fury.io/js/npm-check)
5 | []()
6 |
7 | > Check for outdated, incorrect, and unused dependencies.
8 |
9 |
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 |
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 | 
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 |
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/). [](https://github.com/dylang) [](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. | [](https://www.npmjs.org/package/grunt-notify) |
298 | | [`shortid`](https://github.com/dylang/shortid) | Amazingly short non-sequential url-friendly unique id generator. | [](https://www.npmjs.org/package/shortid) |
299 | | [`space‑hogs`](https://github.com/dylang/space-hogs) | Discover surprisingly large directories from the command line. | [](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. | [](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. | [](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. | [](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. | [](https://www.npmjs.org/package/changelog) |
304 | | [`grunt‑attention`](https://github.com/dylang/grunt-attention) | Display attention-grabbing messages in the terminal | [](https://www.npmjs.org/package/grunt-attention) |
305 | | [`observatory`](https://github.com/dylang/observatory) | Beautiful UI for showing tasks running on the command line. | [](https://www.npmjs.org/package/observatory) |
306 | | [`anthology`](https://github.com/dylang/anthology) | Module information and stats for any @npmjs user | [](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. | [](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 |
--------------------------------------------------------------------------------