├── .editorconfig ├── .eslintrc ├── .github ├── FUNDING.yml └── workflows │ ├── codeql-analysis.yml │ ├── dependency-review.yml │ ├── exit-silently-on-unsupported.yml │ ├── lint.yml │ └── nodejs.yml ├── .gitignore ├── .husky └── pre-push ├── .npmrc ├── LICENSE ├── README.md ├── cli-wrapper.cjs ├── cli.js ├── installed-check.svg ├── package.json ├── renovate.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@voxpelli/eslint-config/esm", 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [voxpelli] 4 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | schedule: 9 | - cron: '44 1 * * 3' 10 | 11 | permissions: 12 | actions: read 13 | contents: read 14 | security-events: write 15 | 16 | jobs: 17 | analyze: 18 | uses: voxpelli/ghatemplates/.github/workflows/codeql-analysis.yml@main 19 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | name: 'Dependency Review' 2 | 3 | on: [pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | dependency-review: 10 | uses: voxpelli/ghatemplates/.github/workflows/dependency-review.yml@main 11 | 12 | -------------------------------------------------------------------------------- /.github/workflows/exit-silently-on-unsupported.yml: -------------------------------------------------------------------------------- 1 | name: Exit silently on unsupported 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '*' 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | lint: 18 | uses: voxpelli/ghatemplates/.github/workflows/exit-silently-on-unsupported.yml@main 19 | with: 20 | files: './cli-wrapper.cjs' 21 | node-versions: '12' 22 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '*' 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | lint: 18 | uses: voxpelli/ghatemplates/.github/workflows/lint.yml@main 19 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '*' 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | test: 18 | uses: voxpelli/ghatemplates/.github/workflows/test.yml@main 19 | with: 20 | node-versions: '18,20,22' 21 | os: 'ubuntu-latest,windows-latest' 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Basic ones 2 | /coverage 3 | /docs 4 | /node_modules 5 | /.env 6 | /.nyc_output 7 | 8 | # We're a library, so please, no lock files 9 | /package-lock.json 10 | /yarn.lock 11 | 12 | # Generated types 13 | *.d.ts 14 | *.d.ts.map 15 | 16 | # Library specific ones 17 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | npm test 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Pelle Wessman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | installed-check 8 |
9 | 10 |
11 | 12 | [![npm version](https://img.shields.io/npm/v/installed-check.svg?style=flat)](https://www.npmjs.com/package/installed-check) 13 | [![npm downloads](https://img.shields.io/npm/dm/installed-check.svg?style=flat)](https://www.npmjs.com/package/installed-check) 14 | [![dependents](https://img.shields.io/librariesio/dependents/npm/installed-check?style=flat)](https://libraries.io/npm/installed-check) 15 | [![js-semistandard-style](https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg)](https://github.com/voxpelli/eslint-config) 16 | [![Follow @voxpelli@mastodon.social](https://img.shields.io/mastodon/follow/109247025527949675?domain=https%3A%2F%2Fmastodon.social&style=social)](https://mastodon.social/@voxpelli) 17 | 18 |
19 | 20 | Verifies that installed modules comply with the requirements specified in `package.json`. 21 | 22 | By default checks engine ranges, peer dependency ranges and installed versions and, in mono-repos using workspaces, by default checks all workspaces as well as the workspace root. 23 | 24 | ## Usage 25 | 26 | ### Command line 27 | 28 | ```sh 29 | npm install -g installed-check 30 | ``` 31 | 32 | Then run it at the root of your project to validate the installed dependencies: 33 | 34 | ```sh 35 | installed-check 36 | ``` 37 | 38 | ### As npm script 39 | 40 | ```sh 41 | npm install --save-dev installed-check 42 | ``` 43 | 44 | ```json 45 | "scripts": { 46 | "test": "installed-check" 47 | } 48 | ``` 49 | 50 | ### Programmatic use 51 | 52 | Use [installed-check-core](https://github.com/voxpelli/node-installed-check-core) 53 | 54 | ## Checks 55 | 56 | * `--engine-check` / `-e` – if set `installed-check` will check that the installed modules doesn't have stricter [`engines`](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#engines) ranges than those in the `package.json` and suggests an alternative requirement if they do. If set, the default checks will be overriden. 57 | * `--peer-check` / `-p` – if set `installed-check` will check that the installed modules doesn't have stricter [`peerDependencies`](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#peerdependencies) ranges than those in the `package.json` and suggests an alternative requirement if they do. If set, the default checks will be overriden. 58 | * `--version-check` / `-c` – if set `installed-check` will check that the installed modules comply with the version requirements set for them the `package.json`. If set, the default checks will be overriden. 59 | 60 | ## Check options 61 | 62 | * `--ignore ARG` / `-i ARG` – excludes the named dependency from non-version checks. Supports [`picomatch`](https://www.npmjs.com/package/picomatch) globbing syntax, eg. `@types/*` (but be sure to provide the pattern in a way that avoids your shell from matching it against files first) 63 | * `--ignore-dev` / `-d` – if set then dev dependencies won't be included in the non-version checks. 64 | * `--strict` / `-s` – treats warnings as errors 65 | 66 | ## Fix options 67 | * `--fix` – tries to apply all suggestions and write them back to disk 68 | 69 | ## Workspace options 70 | 71 | * `--no-include-workspace-root` – excludes the workspace root package. Negated equivalent of npm's [`--include-workspace-root`](https://docs.npmjs.com/cli/v10/commands/npm-run-script#include-workspace-root) 72 | * `--no-workspaces` – excludes workspace packages. Negated equivalent of npm's [`--workspaces`](https://docs.npmjs.com/cli/v10/commands/npm-run-script#workspaces) 73 | * `--workspace=ARG` / `-w ARG` – excludes all workspace packages not matching these names / paths. Equivalent to npm's [`--workspace` / `-w`](https://docs.npmjs.com/cli/v10/commands/npm-run-script#workspace) 74 | * `--workspace-ignore=ARG` – xcludes the specified paths from workspace lookup. (Supports globs) 75 | 76 | ### Additional command line options 77 | 78 | * `--debug` – prints debug info 79 | * `--verbose` / `-v` – prints warnings and notices 80 | * `--help` / `-h` – prints help and exits 81 | * `--version` – prints current version and exits 82 | 83 | ## Similar modules 84 | 85 | * [`knip`](https://github.com/webpro/knip) – finds unused files, dependencies and exports in your JavaScript and TypeScript projects – a great companion module to `installed-check` 86 | -------------------------------------------------------------------------------- /cli-wrapper.cjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | require('version-guard')('./cli.js', 18, 6); 6 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console, unicorn/no-process-exit */ 2 | 3 | import chalk from 'chalk'; 4 | import meow from 'meow'; 5 | import { messageWithCauses, stackWithCauses } from 'pony-cause'; 6 | import { installedCheck, ROOT } from 'installed-check-core'; 7 | 8 | const EXIT_CODE_ERROR_RESULT = 1; 9 | const EXIT_CODE_INVALID_INPUT = 2; 10 | const EXIT_CODE_UNEXPECTED_ERROR = 4; 11 | 12 | const cli = meow(` 13 | Usage 14 | $ installed-check 15 | 16 | Defaults to current folder and to perform all checks. 17 | 18 | Checks 19 | -e, --engine-check Override default checks and explicitly request an engine range check 20 | -p, --peer-check Override default checks and explicitly request a peer dependency range check 21 | -c, --version-check Override default checks and explicitly request a check of installed versions 22 | 23 | Check options 24 | -i ARG, --ignore=ARG Excludes the named dependency from non-version checks.(Supports globs) 25 | -d, --ignore-dev Excludes dev dependencies from non-version checks 26 | -s, --strict Treat warnings as errors 27 | 28 | Fix options 29 | --fix Tries to apply all suggestions and write them back to disk 30 | 31 | Workspace options 32 | --no-include-workspace-root Will exclude the workspace root package 33 | --no-workspaces Will exclude workspace packages 34 | -w ARG, --workspace=ARG Excludes all workspace packages not matching these names / paths 35 | --workspace-ignore=ARG Excludes the specified paths from workspace lookup. (Supports globs) 36 | 37 | Options 38 | --debug Prints debug info 39 | --help Print this help and exits 40 | --version Prints current version and exits 41 | -v, --verbose Shows warnings 42 | 43 | Examples 44 | $ installed-check 45 | `, { 46 | flags: { 47 | debug: { type: 'boolean' }, 48 | engineCheck: { shortFlag: 'e', type: 'boolean' }, 49 | engineIgnore: { type: 'string', isMultiple: true }, 50 | engineNoDev: { type: 'boolean' }, 51 | fix: { type: 'boolean' }, 52 | ignore: { shortFlag: 'i', type: 'string', isMultiple: true }, 53 | ignoreDev: { shortFlag: 'd', type: 'boolean' }, 54 | includeWorkspaceRoot: { type: 'boolean', 'default': true }, 55 | peerCheck: { shortFlag: 'p', type: 'boolean' }, 56 | strict: { shortFlag: 's', type: 'boolean' }, 57 | verbose: { shortFlag: 'v', type: 'boolean' }, 58 | versionCheck: { shortFlag: 'c', type: 'boolean' }, 59 | workspace: { shortFlag: 'w', type: 'string', isMultiple: true }, 60 | workspaceIgnore: { type: 'string', isMultiple: true }, 61 | workspaces: { type: 'boolean', 'default': true }, 62 | }, 63 | importMeta: import.meta, 64 | }); 65 | 66 | if (cli.input.length > 1) { 67 | console.error(chalk.bgRed('Invalid input:') + ` Can only handle a single folder path, but received ${cli.input.length} paths: "${cli.input.join('", "')}"` + '\n'); 68 | process.exit(EXIT_CODE_INVALID_INPUT); 69 | } 70 | 71 | const { 72 | debug, 73 | engineCheck, 74 | engineIgnore, // deprecated 75 | engineNoDev, // deprecated 76 | fix = false, 77 | includeWorkspaceRoot, 78 | peerCheck, 79 | strict, 80 | verbose, 81 | versionCheck, 82 | workspace, 83 | workspaceIgnore, 84 | workspaces, 85 | } = cli.flags; 86 | 87 | let { 88 | ignore, 89 | ignoreDev, 90 | } = cli.flags; 91 | 92 | // Handle deprecated flags 93 | if (engineIgnore?.length) { 94 | ignore = [...ignore || [], ...engineIgnore]; 95 | console.error(chalk.bgRed.black('DEPRECATED:') + ' --engine-ignore is replace by --ignore'); 96 | } 97 | if (engineNoDev) { 98 | ignoreDev = engineNoDev; 99 | console.error(chalk.bgRed.black('DEPRECATED:') + ' --engine-no-dev is replace by --ignore-dev'); 100 | } 101 | 102 | /** @type {import('installed-check-core').InstalledChecks[]} */ 103 | let checks = [ 104 | ...engineCheck ? /** @type {const} */ (['engine']) : [], 105 | ...peerCheck ? /** @type {const} */ (['peer']) : [], 106 | ...versionCheck ? /** @type {const} */ (['version']) : [], 107 | ]; 108 | 109 | /** @type {import('installed-check-core').LookupOptions} */ 110 | const lookupOptions = { 111 | cwd: cli.input[0], 112 | ignorePaths: workspaceIgnore, 113 | includeWorkspaceRoot, 114 | skipWorkspaces: !workspaces, 115 | workspace, 116 | }; 117 | 118 | /** @type {import('installed-check-core').InstalledCheckOptions} */ 119 | const checkOptions = { 120 | noDev: ignoreDev, 121 | ignore, 122 | strict, 123 | }; 124 | 125 | if (checks.length === 0) { 126 | checks = ['engine', 'peer', 'version']; 127 | } 128 | 129 | if (debug) { 130 | const { inspect } = await import('node:util'); 131 | console.log(chalk.blue('Checks:') + ' ' + inspect(checks, { colors: true, compact: true })); 132 | console.log(chalk.blue('Lookup options:') + ' ' + inspect(lookupOptions, { colors: true, compact: true })); 133 | console.log(chalk.blue('Check options:') + ' ' + inspect(checkOptions, { colors: true, compact: true })); 134 | } 135 | 136 | try { 137 | const result = await installedCheck(checks, lookupOptions, { ...checkOptions, fix }); 138 | 139 | if (verbose && result.warnings.length) { 140 | console.log('\n' + chalk.bgYellow.black('Warnings:') + '\n\n' + result.warnings.join('\n') + '\n'); 141 | } else if (result.errors.length) { 142 | console.log(''); 143 | } 144 | 145 | if (result.errors.length) { 146 | console.error(chalk.bgRed.black('Errors:') + '\n\n' + result.errors.join('\n') + '\n'); 147 | } 148 | 149 | if (result.suggestions.length) { 150 | console.error(chalk.bgCyanBright.black('Suggestions:') + '\n\n' + result.suggestions.join('\n') + '\n'); 151 | } 152 | 153 | const workspaceSuccess = /** @type {const} */ ([ 154 | ...Object.entries(result.workspaceSuccess), 155 | ...(result.workspaceSuccess[ROOT] === undefined ? [] : /** @type {const} */ ([['root', result.workspaceSuccess[ROOT]]])), 156 | ]); 157 | 158 | if (verbose && workspaceSuccess.length) { 159 | if (result.errors.length === 0 && workspaceSuccess.length === 1 && result.workspaceSuccess[ROOT]) { 160 | console.log(chalk.bgGreen.black('Successful!') + '\n'); 161 | } else { 162 | const success = workspaceSuccess.filter(([, value]) => value); 163 | const failure = workspaceSuccess.filter(([, value]) => !value); 164 | 165 | if (success.length) { 166 | console.log(chalk.bgGreen.black('Successful workspaces:') + ' ' + success.map(([key]) => key).join(', ') + '\n'); 167 | } 168 | if (failure.length) { 169 | console.log(chalk.bgRed.black('Unsuccessful workspaces:') + ' ' + failure.map(([key]) => key).join(', ') + '\n'); 170 | } 171 | } 172 | } 173 | 174 | if (result.errors.length) { 175 | process.exit(EXIT_CODE_ERROR_RESULT); 176 | } 177 | } catch (err) { 178 | console.error(chalk.bgRed('Unexpected error:') + ' ' + (err instanceof Error ? messageWithCauses(err) + '\n\n' + stackWithCauses(err) : err) + '\n'); 179 | process.exit(EXIT_CODE_UNEXPECTED_ERROR); 180 | } 181 | -------------------------------------------------------------------------------- /installed-check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "installed-check", 3 | "version": "9.3.0", 4 | "description": "Verifies that installed modules comply with the requirements specified in package.json", 5 | "homepage": "http://github.com/voxpelli/node-installed-check", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/voxpelli/node-installed-check.git" 9 | }, 10 | "type": "module", 11 | "bin": { 12 | "installed-check": "cli-wrapper.cjs" 13 | }, 14 | "files": [ 15 | "cli-wrapper.cjs", 16 | "cli.js" 17 | ], 18 | "scripts": { 19 | "check:lint": "eslint --report-unused-disable-directives .", 20 | "check:installed-check": "node cli-wrapper.cjs -i eslint-plugin-jsdoc -i knip", 21 | "check:knip": "knip", 22 | "check:tsc": "tsc", 23 | "check:type-coverage": "type-coverage --detail --strict --at-least 95", 24 | "check": "run-p check:*", 25 | "prepare": "husky", 26 | "test-ci": "run-s check:installed-check", 27 | "test": "run-s check" 28 | }, 29 | "keywords": [ 30 | "cli", 31 | "dependencies", 32 | "devDependencies", 33 | "peerDependencies", 34 | "lint", 35 | "maintenance", 36 | "monorepo", 37 | "scan", 38 | "workspace" 39 | ], 40 | "author": "Pelle Wessman (http://kodfabrik.se/)", 41 | "license": "MIT", 42 | "engines": { 43 | "node": ">=18.6.0" 44 | }, 45 | "dependencies": { 46 | "chalk": "^5.3.0", 47 | "installed-check-core": "^8.3.0", 48 | "meow": "^13.0.0", 49 | "pony-cause": "^2.1.10", 50 | "version-guard": "^1.1.1" 51 | }, 52 | "devDependencies": { 53 | "@types/node": "^18.19.29", 54 | "@voxpelli/eslint-config": "^19.0.0", 55 | "@voxpelli/tsconfig": "^11.0.0", 56 | "eslint": "^8.57.0", 57 | "eslint-plugin-es-x": "^7.6.0", 58 | "eslint-plugin-import": "^2.29.1", 59 | "eslint-plugin-jsdoc": "^46.10.1", 60 | "eslint-plugin-mocha": "^10.4.3", 61 | "eslint-plugin-n": "^16.6.2", 62 | "eslint-plugin-promise": "^6.1.1", 63 | "eslint-plugin-security": "^1.7.1", 64 | "eslint-plugin-sort-destructure-keys": "^1.6.0", 65 | "eslint-plugin-unicorn": "^48.0.1", 66 | "husky": "^9.0.11", 67 | "knip": "^5.13.0", 68 | "npm-run-all2": "^6.1.2", 69 | "type-coverage": "^2.28.2", 70 | "typescript": "~5.4.5" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "github>voxpelli/renovate-config:default" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@voxpelli/tsconfig/node18.json", 3 | "files": [ 4 | "cli-wrapper.cjs", 5 | "cli.js" 6 | ] 7 | } 8 | --------------------------------------------------------------------------------