knip 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +67 -65
  2. package/dist/cli.js +2 -1
  3. package/dist/configuration-chief.d.ts +2 -4
  4. package/dist/configuration-validator.d.ts +56 -0
  5. package/dist/configuration-validator.js +1 -0
  6. package/dist/constants.d.ts +0 -1
  7. package/dist/constants.js +0 -1
  8. package/dist/index.d.ts +2 -0
  9. package/dist/manifest/helpers.d.ts +0 -1
  10. package/dist/manifest/helpers.js +0 -32
  11. package/dist/manifest/index.js +3 -5
  12. package/dist/plugins/github-actions/index.d.ts +6 -0
  13. package/dist/plugins/github-actions/index.js +25 -0
  14. package/dist/plugins/index.d.ts +1 -0
  15. package/dist/plugins/index.js +1 -0
  16. package/dist/plugins/lint-staged/index.js +3 -3
  17. package/dist/reporters/index.d.ts +4 -4
  18. package/dist/types/config.d.ts +3 -0
  19. package/dist/types/plugins.d.ts +2 -1
  20. package/dist/util/binaries/binaries/cross-env.d.ts +1 -0
  21. package/dist/util/binaries/binaries/cross-env.js +5 -0
  22. package/dist/util/binaries/binaries/dotenv.d.ts +1 -0
  23. package/dist/util/binaries/binaries/dotenv.js +5 -0
  24. package/dist/util/binaries/binaries/index.d.ts +2 -0
  25. package/dist/util/binaries/binaries/index.js +2 -0
  26. package/dist/util/binaries/globals/index.d.ts +4 -0
  27. package/dist/util/binaries/globals/index.js +4 -0
  28. package/dist/util/binaries/globals/node.d.ts +1 -0
  29. package/dist/util/binaries/globals/node.js +5 -0
  30. package/dist/util/binaries/globals/npx.d.ts +1 -0
  31. package/dist/util/binaries/globals/npx.js +8 -0
  32. package/dist/util/binaries/globals/pnpm.d.ts +2 -0
  33. package/dist/util/binaries/globals/pnpm.js +43 -0
  34. package/dist/util/binaries/globals/yarn.d.ts +2 -0
  35. package/dist/util/binaries/globals/yarn.js +42 -0
  36. package/dist/util/binaries/index.d.ts +7 -0
  37. package/dist/util/binaries/index.js +46 -0
  38. package/dist/util/debug.js +0 -10
  39. package/dist/util/fs.d.ts +2 -2
  40. package/dist/util/fs.js +8 -12
  41. package/dist/util/object.d.ts +1 -0
  42. package/dist/util/object.js +15 -0
  43. package/dist/workspace-worker.js +1 -0
  44. package/package.json +7 -6
  45. package/schema.json +5 -1
package/README.md CHANGED
@@ -233,26 +233,27 @@ Knip contains a growing list of plugins:
233
233
  - [Cypress][14]
234
234
  - [ESLint][15]
235
235
  - [Gatsby][16]
236
- - [Jest][17]
237
- - [lint-staged][18]
238
- - [markdownlint][19]
239
- - [Mocha][20]
240
- - [Next.js][21]
241
- - [npm-package-json-lint][22]
242
- - [Nx][23]
243
- - [nyc][24]
244
- - [Playwright][25]
245
- - [PostCSS][26]
246
- - [Prettier][27]
247
- - [Release It][28]
248
- - [Remark][29]
249
- - [Remix][30]
250
- - [Rollup][31]
251
- - [Sentry][32]
252
- - [Storybook][33]
253
- - [Stryker][34]
254
- - [TypeScript][35]
255
- - [Webpack][36]
236
+ - [GitHub Actions][17]
237
+ - [Jest][18]
238
+ - [lint-staged][19]
239
+ - [markdownlint][20]
240
+ - [Mocha][21]
241
+ - [Next.js][22]
242
+ - [npm-package-json-lint][23]
243
+ - [Nx][24]
244
+ - [nyc][25]
245
+ - [Playwright][26]
246
+ - [PostCSS][27]
247
+ - [Prettier][28]
248
+ - [Release It][29]
249
+ - [Remark][30]
250
+ - [Remix][31]
251
+ - [Rollup][32]
252
+ - [Sentry][33]
253
+ - [Storybook][34]
254
+ - [Stryker][35]
255
+ - [TypeScript][36]
256
+ - [Webpack][37]
256
257
 
257
258
  Plugins are automatically activated, no need to enable anything. Each plugin is automatically enabled based on simple
258
259
  heuristics. Most of them check whether one or one of a few (dev) dependencies are listed in `package.json`. Once
@@ -260,7 +261,7 @@ enabled, they add a set of configuration and/or entry files for Knip to analyze.
260
261
 
261
262
  Most plugins use one or both of the following file types:
262
263
 
263
- - `config` - custom dependency resolvers are applied to the [config files][37]
264
+ - `config` - custom dependency resolvers are applied to the [config files][38]
264
265
  - `entry` - files to include with the analysis of the rest of the source code
265
266
 
266
267
  See each plugin's documentation for its default values.
@@ -340,10 +341,10 @@ locations. The more plugins Knip will have, the more projects can be analyzed ou
340
341
 
341
342
  Knip provides the following built-in reporters:
342
343
 
343
- - [`codeowners`][38]
344
- - [`compact`][39]
345
- - [`json`][40]
346
- - [`symbol`][41] (default)
344
+ - [`codeowners`][39]
345
+ - [`compact`][40]
346
+ - [`json`][41]
347
+ - [`symbol`][42] (default)
347
348
 
348
349
  The `compact` reporter shows the sorted files first, and then a list of symbols:
349
350
 
@@ -370,7 +371,7 @@ type ReporterOptions = {
370
371
 
371
372
  The data can then be used to write issues to `stdout`, a JSON or CSV file, or sent to a service.
372
373
 
373
- Find more details and ideas in [custom reporters][42].
374
+ Find more details and ideas in [custom reporters][43].
374
375
 
375
376
  ## Libraries and "unused" exports
376
377
 
@@ -427,14 +428,14 @@ When unused dependencies are related to dependencies having a Knip [plugin][1],
427
428
  for that dependency are at custom locations. The default values are at the plugin's documentation, and can be overridden
428
429
  to match the custom location(s).
429
430
 
430
- When the dependencies don't have a Knip plugin yet, please file an issue or [create a new plugin][43].
431
+ When the dependencies don't have a Knip plugin yet, please file an issue or [create a new plugin][44].
431
432
 
432
433
  #### Too many unused exports
433
434
 
434
435
  When the project is a library and the exports are meant to be used by consumers of the library, there are two options:
435
436
 
436
437
  1. By default, unused exports of `entry` files are not reported, so you can add the containing file to it.
437
- 2. The exported values or types can be marked [using the JSDoc `@public` tag][44].
438
+ 2. The exported values or types can be marked [using the JSDoc `@public` tag][45].
438
439
 
439
440
  ### How to start using Knip in CI while having too many issues to sort out?
440
441
 
@@ -451,7 +452,7 @@ All of this is hiding problems, so please make sure to plan for fixing them and/
451
452
 
452
453
  This table is an ongoing comparison. Based on their docs (please report any mistakes):
453
454
 
454
- | Feature | **knip** | [depcheck][45] | [unimported][46] | [ts-unused-exports][47] | [ts-prune][48] |
455
+ | Feature | **knip** | [depcheck][46] | [unimported][47] | [ts-unused-exports][48] | [ts-prune][49] |
455
456
  | :--------------------------------- | :------: | :------------: | :--------------: | :---------------------: | :------------: |
456
457
  | Unused files | ✅ | - | ✅ | - | - |
457
458
  | Unused dependencies | ✅ | ✅ | ✅ | - | - |
@@ -465,7 +466,7 @@ This table is an ongoing comparison. Based on their docs (please report any mist
465
466
  | Custom reporters | ✅ | - | - | - | - |
466
467
  | JavaScript support | ✅ | ✅ | ✅ | - | - |
467
468
  | Configure entry files | ✅ | ❌ | ✅ | ❌ | ❌ |
468
- | [Support workspaces/monorepos][49] | ✅ | ❌ | ❌ | - | - |
469
+ | [Support workspaces/monorepos][50] | ✅ | ❌ | ❌ | - | - |
469
470
  | ESLint plugin available | - | - | - | ✅ | - |
470
471
 
471
472
  ✅ = Supported, ❌ = Not supported, - = Out of scope
@@ -486,7 +487,7 @@ The following commands are similar:
486
487
  unimported
487
488
  knip --production --dependencies --include files
488
489
 
489
- Also see [production mode][50].
490
+ Also see [production mode][51].
490
491
 
491
492
  ### ts-unused-exports
492
493
 
@@ -532,37 +533,38 @@ for the job. I'm motivated to make knip perfectly suited for the job of cutting
532
533
  [14]: ./src/plugins/cypress
533
534
  [15]: ./src/plugins/eslint
534
535
  [16]: ./src/plugins/gatsby
535
- [17]: ./src/plugins/jest
536
- [18]: ./src/plugins/lint-staged
537
- [19]: ./src/plugins/markdownlint
538
- [20]: ./src/plugins/mocha
539
- [21]: ./src/plugins/next
540
- [22]: ./src/plugins/npm-package-json-lint
541
- [23]: ./src/plugins/nx
542
- [24]: ./src/plugins/nyc
543
- [25]: ./src/plugins/playwright
544
- [26]: ./src/plugins/postcss
545
- [27]: ./src/plugins/prettier
546
- [28]: ./src/plugins/release-it
547
- [29]: ./src/plugins/remark
548
- [30]: ./src/plugins/remix
549
- [31]: ./src/plugins/rollup
550
- [32]: ./src/plugins/sentry
551
- [33]: ./src/plugins/storybook
552
- [34]: ./src/plugins/stryker
553
- [35]: ./src/plugins/typescript
554
- [36]: ./src/plugins/webpack
555
- [37]: #config
556
- [38]: #code-owners
557
- [39]: #compact
558
- [40]: #json
559
- [41]: #symbol-default
560
- [42]: ./docs/custom-reporters.md
561
- [43]: #create-a-new-plugin
562
- [44]: #libraries-and-unused-exports
563
- [45]: https://github.com/depcheck/depcheck
564
- [46]: https://github.com/smeijer/unimported
565
- [47]: https://github.com/pzavolinsky/ts-unused-exports
566
- [48]: https://github.com/nadeesha/ts-prune
567
- [49]: #workspaces--monorepos
568
- [50]: #production-mode
536
+ [17]: ./src/plugins/github-actions
537
+ [18]: ./src/plugins/jest
538
+ [19]: ./src/plugins/lint-staged
539
+ [20]: ./src/plugins/markdownlint
540
+ [21]: ./src/plugins/mocha
541
+ [22]: ./src/plugins/next
542
+ [23]: ./src/plugins/npm-package-json-lint
543
+ [24]: ./src/plugins/nx
544
+ [25]: ./src/plugins/nyc
545
+ [26]: ./src/plugins/playwright
546
+ [27]: ./src/plugins/postcss
547
+ [28]: ./src/plugins/prettier
548
+ [29]: ./src/plugins/release-it
549
+ [30]: ./src/plugins/remark
550
+ [31]: ./src/plugins/remix
551
+ [32]: ./src/plugins/rollup
552
+ [33]: ./src/plugins/sentry
553
+ [34]: ./src/plugins/storybook
554
+ [35]: ./src/plugins/stryker
555
+ [36]: ./src/plugins/typescript
556
+ [37]: ./src/plugins/webpack
557
+ [38]: #config
558
+ [39]: #code-owners
559
+ [40]: #compact
560
+ [41]: #json
561
+ [42]: #symbol-default
562
+ [43]: ./docs/custom-reporters.md
563
+ [44]: #create-a-new-plugin
564
+ [45]: #libraries-and-unused-exports
565
+ [46]: https://github.com/depcheck/depcheck
566
+ [47]: https://github.com/smeijer/unimported
567
+ [48]: https://github.com/pzavolinsky/ts-unused-exports
568
+ [49]: https://github.com/nadeesha/ts-prune
569
+ [50]: #workspaces--monorepos
570
+ [51]: #production-mode
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import { register } from 'esbuild-register/dist/node.js';
4
4
  import reporters from './reporters/index.js';
5
5
  import parsedArgs, { helpText } from './util/cli-arguments.js';
6
6
  import { ConfigurationError } from './util/errors.js';
7
+ import { _load } from './util/loader.js';
7
8
  import { measure } from './util/performance.js';
8
9
  import { main } from './index.js';
9
10
  register();
@@ -14,7 +15,7 @@ if (help) {
14
15
  }
15
16
  const cwd = process.cwd();
16
17
  const isShowProgress = !isDebug && isNoProgress === false && process.stdout.isTTY && typeof process.stdout.cursorTo === 'function';
17
- const printReport = reporter in reporters ? reporters[reporter] : await import(path.join(cwd, reporter));
18
+ const printReport = reporter in reporters ? reporters[reporter] : await _load(path.join(cwd, reporter));
18
19
  const run = async () => {
19
20
  try {
20
21
  const { report, issues, counters } = await main({
@@ -1,7 +1,5 @@
1
1
  /// <reference types="npmcli__package-json" />
2
- import { z } from 'zod';
3
- import { ConfigurationValidator } from './configuration-validator.js';
4
- import type { Configuration, WorkspaceConfiguration } from './types/config.js';
2
+ import type { RawConfiguration, Configuration, WorkspaceConfiguration } from './types/config.js';
5
3
  import type { PackageJson } from '@npmcli/package-json';
6
4
  type ConfigurationManagerOptions = {
7
5
  cwd: string;
@@ -16,7 +14,7 @@ export default class ConfigurationChief {
16
14
  manifestWorkspaces: undefined | string[];
17
15
  constructor({ cwd, isProduction }: ConfigurationManagerOptions);
18
16
  loadLocalConfig(): Promise<void>;
19
- normalize(rawLocalConfig: z.infer<typeof ConfigurationValidator>): {
17
+ normalize(rawLocalConfig: RawConfiguration): {
20
18
  include: string[];
21
19
  exclude: string[];
22
20
  ignore: string[];
@@ -119,6 +119,19 @@ export declare const ConfigurationValidator: z.ZodObject<z.extendShape<z.extendS
119
119
  entry?: string | string[] | undefined;
120
120
  project?: string | string[] | undefined;
121
121
  }>]>>;
122
+ 'github-actions': z.ZodOptional<z.ZodUnion<[z.ZodLiteral<false>, z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>, z.ZodObject<{
123
+ config: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
124
+ entry: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
125
+ project: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
126
+ }, "strip", z.ZodTypeAny, {
127
+ config?: string | string[] | undefined;
128
+ entry?: string | string[] | undefined;
129
+ project?: string | string[] | undefined;
130
+ }, {
131
+ config?: string | string[] | undefined;
132
+ entry?: string | string[] | undefined;
133
+ project?: string | string[] | undefined;
134
+ }>]>>;
122
135
  'lint-staged': z.ZodOptional<z.ZodUnion<[z.ZodLiteral<false>, z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>, z.ZodObject<{
123
136
  config: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
124
137
  entry: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
@@ -397,6 +410,11 @@ export declare const ConfigurationValidator: z.ZodObject<z.extendShape<z.extendS
397
410
  entry?: string | string[] | undefined;
398
411
  project?: string | string[] | undefined;
399
412
  } | undefined;
413
+ 'github-actions'?: string | false | string[] | {
414
+ config?: string | string[] | undefined;
415
+ entry?: string | string[] | undefined;
416
+ project?: string | string[] | undefined;
417
+ } | undefined;
400
418
  'lint-staged'?: string | false | string[] | {
401
419
  config?: string | string[] | undefined;
402
420
  entry?: string | string[] | undefined;
@@ -531,6 +549,11 @@ export declare const ConfigurationValidator: z.ZodObject<z.extendShape<z.extendS
531
549
  entry?: string | string[] | undefined;
532
550
  project?: string | string[] | undefined;
533
551
  } | undefined;
552
+ 'github-actions'?: string | false | string[] | {
553
+ config?: string | string[] | undefined;
554
+ entry?: string | string[] | undefined;
555
+ project?: string | string[] | undefined;
556
+ } | undefined;
534
557
  'lint-staged'?: string | false | string[] | {
535
558
  config?: string | string[] | undefined;
536
559
  entry?: string | string[] | undefined;
@@ -727,6 +750,19 @@ export declare const ConfigurationValidator: z.ZodObject<z.extendShape<z.extendS
727
750
  entry?: string | string[] | undefined;
728
751
  project?: string | string[] | undefined;
729
752
  }>]>>;
753
+ 'github-actions': z.ZodOptional<z.ZodUnion<[z.ZodLiteral<false>, z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>, z.ZodObject<{
754
+ config: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
755
+ entry: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
756
+ project: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
757
+ }, "strip", z.ZodTypeAny, {
758
+ config?: string | string[] | undefined;
759
+ entry?: string | string[] | undefined;
760
+ project?: string | string[] | undefined;
761
+ }, {
762
+ config?: string | string[] | undefined;
763
+ entry?: string | string[] | undefined;
764
+ project?: string | string[] | undefined;
765
+ }>]>>;
730
766
  'lint-staged': z.ZodOptional<z.ZodUnion<[z.ZodLiteral<false>, z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>, z.ZodObject<{
731
767
  config: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
732
768
  entry: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
@@ -1010,6 +1046,11 @@ export declare const ConfigurationValidator: z.ZodObject<z.extendShape<z.extendS
1010
1046
  entry?: string | string[] | undefined;
1011
1047
  project?: string | string[] | undefined;
1012
1048
  } | undefined;
1049
+ 'github-actions'?: string | false | string[] | {
1050
+ config?: string | string[] | undefined;
1051
+ entry?: string | string[] | undefined;
1052
+ project?: string | string[] | undefined;
1053
+ } | undefined;
1013
1054
  'lint-staged'?: string | false | string[] | {
1014
1055
  config?: string | string[] | undefined;
1015
1056
  entry?: string | string[] | undefined;
@@ -1144,6 +1185,11 @@ export declare const ConfigurationValidator: z.ZodObject<z.extendShape<z.extendS
1144
1185
  entry?: string | string[] | undefined;
1145
1186
  project?: string | string[] | undefined;
1146
1187
  } | undefined;
1188
+ 'github-actions'?: string | false | string[] | {
1189
+ config?: string | string[] | undefined;
1190
+ entry?: string | string[] | undefined;
1191
+ project?: string | string[] | undefined;
1192
+ } | undefined;
1147
1193
  'lint-staged'?: string | false | string[] | {
1148
1194
  config?: string | string[] | undefined;
1149
1195
  entry?: string | string[] | undefined;
@@ -1284,6 +1330,11 @@ export declare const ConfigurationValidator: z.ZodObject<z.extendShape<z.extendS
1284
1330
  entry?: string | string[] | undefined;
1285
1331
  project?: string | string[] | undefined;
1286
1332
  } | undefined;
1333
+ 'github-actions'?: string | false | string[] | {
1334
+ config?: string | string[] | undefined;
1335
+ entry?: string | string[] | undefined;
1336
+ project?: string | string[] | undefined;
1337
+ } | undefined;
1287
1338
  'lint-staged'?: string | false | string[] | {
1288
1339
  config?: string | string[] | undefined;
1289
1340
  entry?: string | string[] | undefined;
@@ -1418,6 +1469,11 @@ export declare const ConfigurationValidator: z.ZodObject<z.extendShape<z.extendS
1418
1469
  entry?: string | string[] | undefined;
1419
1470
  project?: string | string[] | undefined;
1420
1471
  } | undefined;
1472
+ 'github-actions'?: string | false | string[] | {
1473
+ config?: string | string[] | undefined;
1474
+ entry?: string | string[] | undefined;
1475
+ project?: string | string[] | undefined;
1476
+ } | undefined;
1421
1477
  'lint-staged'?: string | false | string[] | {
1422
1478
  config?: string | string[] | undefined;
1423
1479
  entry?: string | string[] | undefined;
@@ -30,6 +30,7 @@ const pluginsSchema = z.object({
30
30
  eslint: pluginSchema,
31
31
  gatsby: pluginSchema,
32
32
  jest: pluginSchema,
33
+ 'github-actions': pluginSchema,
33
34
  'lint-staged': pluginSchema,
34
35
  mocha: pluginSchema,
35
36
  next: pluginSchema,
@@ -9,6 +9,5 @@ export declare const DEFAULT_WORKSPACE_CONFIG: {
9
9
  export declare const TEST_FILE_PATTERNS: string[];
10
10
  export declare const IGNORED_GLOBAL_BINARIES: string[];
11
11
  export declare const IGNORE_DEFINITELY_TYPED: string[];
12
- export declare const FIRST_ARGUMENT_AS_BINARY_EXCEPTIONS: string[];
13
12
  export declare const ISSUE_TYPES: IssueType[];
14
13
  export declare const ISSUE_TYPE_TITLE: Record<IssueType, string>;
package/dist/constants.js CHANGED
@@ -8,7 +8,6 @@ export const DEFAULT_WORKSPACE_CONFIG = {
8
8
  export const TEST_FILE_PATTERNS = ['**/*.{test,spec}.{js,jsx,ts,tsx}', '**/__tests__/**/*.{js,jsx,ts,tsx}'];
9
9
  export const IGNORED_GLOBAL_BINARIES = ['npm', 'npx', 'node', 'yarn', 'pnpm', 'deno', 'git'];
10
10
  export const IGNORE_DEFINITELY_TYPED = ['node'];
11
- export const FIRST_ARGUMENT_AS_BINARY_EXCEPTIONS = ['npx', 'cross-env', 'dotenv'];
12
11
  export const ISSUE_TYPES = [
13
12
  'files',
14
13
  'dependencies',
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { CommandLineOptions } from './types/cli.js';
2
2
  import type { Report } from './types/issues.js';
3
+ export type { RawConfiguration as KnipConfig } from './types/config.js';
4
+ export type { Reporter, ReporterOptions } from './types/issues.js';
3
5
  export declare const main: (unresolvedConfiguration: CommandLineOptions) => Promise<{
4
6
  report: Report;
5
7
  issues: import("./types/issues.js").Issues;
@@ -1,2 +1 @@
1
- export declare const getBinariesFromScripts: (npmScripts: string[]) => string[];
2
1
  export declare const getPackageManifest: (workingDir: string, packageName: string, isRoot: boolean, cwd: string) => Promise<any>;
@@ -1,37 +1,5 @@
1
1
  import path from 'node:path';
2
- import { FIRST_ARGUMENT_AS_BINARY_EXCEPTIONS } from '../constants.js';
3
- import { getArgumentValues } from '../util/plugin.js';
4
2
  import { require } from '../util/require.js';
5
- const normalizeBinaries = (command) => command.replace(/(\.\/)?node_modules\/\.bin\/(\w+)/, '$2').replace(/\$\(npm bin\)\/(\w+)/, '$1');
6
- const stripEnvironmentVariables = (value) => value.replace(/([A-Z][^ ]*)=([^ ])+ /g, '');
7
- const getLoaderArgumentValues = (value) => getArgumentValues(value, / (--(experimental-)?loader|--require|-r)[ =]([^ ]+)/g);
8
- const getDependenciesFromLoaderArguments = (args) => getLoaderArgumentValues(' ' + args.join(' '))
9
- .filter(scripts => !scripts.startsWith('.'))
10
- .map(script => {
11
- if (script.startsWith('@')) {
12
- const [scope, packageName] = script.split('/');
13
- return [scope, packageName].join('/');
14
- }
15
- return script.split('/')[0];
16
- });
17
- export const getBinariesFromScripts = (npmScripts) => Array.from(npmScripts.reduce((binaries, script) => {
18
- script
19
- .split(' && ')
20
- .flatMap(command => command.split(' -- '))
21
- .map(normalizeBinaries)
22
- .filter(command => /^\w/.test(command))
23
- .map(stripEnvironmentVariables)
24
- .flatMap(command => {
25
- const [binary, ...args] = command.trim().split(' ');
26
- if (binary === 'npx' && /-y|--yes/.test(args[0]))
27
- return [binary];
28
- const firstArgument = FIRST_ARGUMENT_AS_BINARY_EXCEPTIONS.includes(binary) && args.find(arg => !arg.startsWith('-'));
29
- const dependenciesFromArguments = getDependenciesFromLoaderArguments(args);
30
- return [binary, firstArgument, ...dependenciesFromArguments];
31
- })
32
- .forEach(binary => binary && binaries.add(binary));
33
- return binaries;
34
- }, new Set()));
35
3
  export const getPackageManifest = async (workingDir, packageName, isRoot, cwd) => {
36
4
  try {
37
5
  return require(path.join(workingDir, 'node_modules', packageName, 'package.json'));
@@ -1,6 +1,6 @@
1
- import { IGNORED_GLOBAL_BINARIES } from '../constants.js';
1
+ import { getBinariesFromScripts } from '../util/binaries/index.js';
2
2
  import { timerify } from '../util/performance.js';
3
- import { getBinariesFromScripts, getPackageManifest } from './helpers.js';
3
+ import { getPackageManifest } from './helpers.js';
4
4
  const findManifestDependencies = async ({ rootConfig, manifest, isRoot, isProduction, dir, cwd }) => {
5
5
  const { ignoreBinaries } = rootConfig;
6
6
  const scriptFilter = isProduction ? ['start', 'postinstall'] : [];
@@ -12,9 +12,7 @@ const findManifestDependencies = async ({ rootConfig, manifest, isRoot, isProduc
12
12
  }
13
13
  return scripts;
14
14
  }, []);
15
- const referencedBinaries = getBinariesFromScripts(scripts)
16
- .filter(binaryName => !IGNORED_GLOBAL_BINARIES.includes(binaryName))
17
- .filter(binaryName => !ignoreBinaries.includes(binaryName));
15
+ const referencedBinaries = getBinariesFromScripts(scripts, { manifest, ignore: rootConfig.ignoreBinaries });
18
16
  const installedBinaries = new Map();
19
17
  const packageNames = [...Object.keys(manifest.dependencies ?? {}), ...Object.keys(manifest.devDependencies ?? {})];
20
18
  for (const packageName of packageNames) {
@@ -0,0 +1,6 @@
1
+ import type { IsPluginEnabledCallback, GenericPluginCallback } from '../../types/plugins.js';
2
+ export declare const NAME = "GitHub Actions";
3
+ export declare const ENABLERS = "This plugin is enabled when a `.yml` file is found in the `.github/workflows` folder.";
4
+ export declare const isEnabled: IsPluginEnabledCallback;
5
+ export declare const CONFIG_FILE_PATTERNS: string[];
6
+ export declare const findDependencies: GenericPluginCallback;
@@ -0,0 +1,25 @@
1
+ import { getBinariesFromScripts } from '../../util/binaries/index.js';
2
+ import { _firstGlob } from '../../util/glob.js';
3
+ import { _load } from '../../util/loader.js';
4
+ import { getValuesByKeyDeep } from '../../util/object.js';
5
+ import { timerify } from '../../util/performance.js';
6
+ export const NAME = 'GitHub Actions';
7
+ export const ENABLERS = 'This plugin is enabled when a `.yml` file is found in the `.github/workflows` folder.';
8
+ export const isEnabled = () => Boolean(_firstGlob({ cwd: process.cwd(), patterns: ['.github/workflows/*.yml'] }));
9
+ export const CONFIG_FILE_PATTERNS = ['.github/workflows/*.yml'];
10
+ const findGithubActionsDependencies = async (configFilePath, { manifest, rootConfig }) => {
11
+ const config = await _load(configFilePath);
12
+ if (!config)
13
+ return [];
14
+ const scripts = getValuesByKeyDeep(config, 'run')
15
+ .filter((value) => typeof value === 'string')
16
+ .flatMap(script => script.split('\n'))
17
+ .map(script => script.trim());
18
+ const binaries = getBinariesFromScripts(scripts, {
19
+ manifest,
20
+ ignore: rootConfig.ignoreBinaries,
21
+ knownGlobalsOnly: true,
22
+ });
23
+ return binaries;
24
+ };
25
+ export const findDependencies = timerify(findGithubActionsDependencies);
@@ -5,6 +5,7 @@ export * as commitlint from './commitlint/index.js';
5
5
  export * as cypress from './cypress/index.js';
6
6
  export * as eslint from './eslint/index.js';
7
7
  export * as gatsby from './gatsby/index.js';
8
+ export * as githubActions from './github-actions/index.js';
8
9
  export * as jest from './jest/index.js';
9
10
  export * as lintStaged from './lint-staged/index.js';
10
11
  export * as markdownlint from './markdownlint/index.js';
@@ -5,6 +5,7 @@ export * as commitlint from './commitlint/index.js';
5
5
  export * as cypress from './cypress/index.js';
6
6
  export * as eslint from './eslint/index.js';
7
7
  export * as gatsby from './gatsby/index.js';
8
+ export * as githubActions from './github-actions/index.js';
8
9
  export * as jest from './jest/index.js';
9
10
  export * as lintStaged from './lint-staged/index.js';
10
11
  export * as markdownlint from './markdownlint/index.js';
@@ -1,4 +1,4 @@
1
- import { getBinariesFromScripts } from '../../manifest/helpers.js';
1
+ import { getBinariesFromScripts } from '../../util/binaries/index.js';
2
2
  import { _load } from '../../util/loader.js';
3
3
  import { timerify } from '../../util/performance.js';
4
4
  import { hasDependency } from '../../util/plugin.js';
@@ -13,7 +13,7 @@ export const CONFIG_FILE_PATTERNS = [
13
13
  'lint-staged.config.{js,mjs,cjs}',
14
14
  'package.json',
15
15
  ];
16
- const findLintStagedDependencies = async (configFilePath, { manifest }) => {
16
+ const findLintStagedDependencies = async (configFilePath, { manifest, rootConfig }) => {
17
17
  let config = configFilePath.endsWith('package.json')
18
18
  ? manifest['lint-staged']
19
19
  : await _load(configFilePath);
@@ -25,7 +25,7 @@ const findLintStagedDependencies = async (configFilePath, { manifest }) => {
25
25
  const binaries = new Set();
26
26
  for (const entry of Object.values(config).flat()) {
27
27
  const scripts = [typeof entry === 'function' ? await entry([]) : entry].flat();
28
- getBinariesFromScripts(scripts).forEach(binary => binaries.add(binary));
28
+ getBinariesFromScripts(scripts, { manifest, ignore: rootConfig.ignoreBinaries }).forEach(bin => binaries.add(bin));
29
29
  }
30
30
  return Array.from(binaries);
31
31
  };
@@ -1,7 +1,7 @@
1
1
  declare const _default: {
2
- symbols: ({ report, issues }: import("../types/issues.js").ReporterOptions) => void;
3
- compact: ({ report, issues }: import("../types/issues.js").ReporterOptions) => void;
4
- codeowners: ({ report, issues, options }: import("../types/issues.js").ReporterOptions) => void;
5
- json: ({ report, issues, options }: import("../types/issues.js").ReporterOptions) => Promise<void>;
2
+ symbols: ({ report, issues }: import("../index.js").ReporterOptions) => void;
3
+ compact: ({ report, issues }: import("../index.js").ReporterOptions) => void;
4
+ codeowners: ({ report, issues, options }: import("../index.js").ReporterOptions) => void;
5
+ json: ({ report, issues, options }: import("../index.js").ReporterOptions) => Promise<void>;
6
6
  };
7
7
  export default _default;
@@ -1,4 +1,7 @@
1
+ import { z } from 'zod';
2
+ import { ConfigurationValidator } from '../configuration-validator.js';
1
3
  import * as Plugins from '../plugins/index.js';
4
+ export type RawConfiguration = z.infer<typeof ConfigurationValidator>;
2
5
  type NormalizedGlob = string[];
3
6
  export type PluginName = keyof typeof Plugins;
4
7
  export type PluginConfiguration = {
@@ -1,4 +1,4 @@
1
- import { PluginConfiguration, WorkspaceConfiguration } from './config.js';
1
+ import { Configuration, PluginConfiguration, WorkspaceConfiguration } from './config.js';
2
2
  import type { PackageJson } from 'type-fest';
3
3
  type IsPluginEnabledCallbackOptions = {
4
4
  manifest: PackageJson;
@@ -10,6 +10,7 @@ type GenericPluginCallbackOptions = {
10
10
  manifest: PackageJson;
11
11
  config: PluginConfiguration;
12
12
  workspaceConfig: WorkspaceConfiguration;
13
+ rootConfig: Configuration;
13
14
  isProduction: boolean;
14
15
  };
15
16
  export type GenericPluginCallback = (configFilePath: string, { cwd, manifest, config, workspaceConfig, isProduction }: GenericPluginCallbackOptions) => Promise<string[]> | string[];
@@ -0,0 +1 @@
1
+ export declare const resolve: (binary: string, args: string[]) => string[];
@@ -0,0 +1,5 @@
1
+ import { getDependenciesFromLoaderArguments } from '../index.js';
2
+ export const resolve = (binary, args) => {
3
+ const dependenciesFromArguments = getDependenciesFromLoaderArguments(args);
4
+ return [binary, args[0], ...dependenciesFromArguments];
5
+ };
@@ -0,0 +1 @@
1
+ export declare const resolve: (binary: string, args: string[]) => string[];
@@ -0,0 +1,5 @@
1
+ import { getDependenciesFromLoaderArguments } from '../index.js';
2
+ export const resolve = (binary, args) => {
3
+ const dependenciesFromArguments = getDependenciesFromLoaderArguments(args);
4
+ return [binary, args[0], ...dependenciesFromArguments];
5
+ };
@@ -0,0 +1,2 @@
1
+ export * as crossEnv from './cross-env.js';
2
+ export * as dotenv from './dotenv.js';
@@ -0,0 +1,2 @@
1
+ export * as crossEnv from './cross-env.js';
2
+ export * as dotenv from './dotenv.js';
@@ -0,0 +1,4 @@
1
+ export * as node from './node.js';
2
+ export * as npx from './npx.js';
3
+ export * as pnpm from './pnpm.js';
4
+ export * as yarn from './yarn.js';
@@ -0,0 +1,4 @@
1
+ export * as node from './node.js';
2
+ export * as npx from './npx.js';
3
+ export * as pnpm from './pnpm.js';
4
+ export * as yarn from './yarn.js';
@@ -0,0 +1 @@
1
+ export declare const resolve: (binary: string, args: string[]) => string[];
@@ -0,0 +1,5 @@
1
+ import { getDependenciesFromLoaderArguments } from '../index.js';
2
+ export const resolve = (binary, args) => {
3
+ const dependenciesFromArguments = getDependenciesFromLoaderArguments(args);
4
+ return dependenciesFromArguments;
5
+ };
@@ -0,0 +1 @@
1
+ export declare const resolve: (binary: string, args: string[]) => (string | undefined)[];
@@ -0,0 +1,8 @@
1
+ import { getDependenciesFromLoaderArguments } from '../index.js';
2
+ export const resolve = (binary, args) => {
3
+ if (/-y|--yes/.test(args[0]))
4
+ return [];
5
+ const dependenciesFromArguments = getDependenciesFromLoaderArguments(args);
6
+ const firstArgument = args.find(arg => !arg.startsWith('-'));
7
+ return [firstArgument, ...dependenciesFromArguments];
8
+ };
@@ -0,0 +1,2 @@
1
+ import type { PackageJson } from 'type-fest';
2
+ export declare const resolve: (binary: string, args: string[], manifest: PackageJson) => string | never[] | undefined;
@@ -0,0 +1,43 @@
1
+ const commands = [
2
+ 'add',
3
+ 'i',
4
+ 'install',
5
+ 'up',
6
+ 'update',
7
+ 'upgrade',
8
+ 'remove',
9
+ 'rm',
10
+ 'uninstall',
11
+ 'un',
12
+ 'link',
13
+ 'ln',
14
+ 'unlink',
15
+ 'import',
16
+ 'rebuild',
17
+ 'rb',
18
+ 'prune',
19
+ 'fetch',
20
+ 'install-test',
21
+ 'it',
22
+ 'patch',
23
+ 'patch-commit',
24
+ 'audit',
25
+ 'list',
26
+ 'ls',
27
+ 'outdated',
28
+ 'why',
29
+ 'test',
30
+ 't',
31
+ 'tst',
32
+ ];
33
+ export const resolve = (binary, args, manifest) => {
34
+ const scripts = manifest.scripts ? Object.keys(manifest.scripts) : [];
35
+ const [command, ...commandArgs] = args;
36
+ if (scripts.includes(command) || commands.includes(command))
37
+ return [];
38
+ if (command === 'run' && scripts.includes(commandArgs[0]))
39
+ return [];
40
+ if (command === 'run' || command === 'exec')
41
+ return commandArgs.find(arg => !arg.startsWith('-'));
42
+ return args.find(arg => !arg.startsWith('-'));
43
+ };
@@ -0,0 +1,2 @@
1
+ import type { PackageJson } from 'type-fest';
2
+ export declare const resolve: (binary: string, args: string[], manifest: PackageJson) => string | never[] | undefined;
@@ -0,0 +1,42 @@
1
+ const commands = [
2
+ 'add',
3
+ 'bin',
4
+ 'cache',
5
+ 'config',
6
+ 'constraints',
7
+ 'dedupe',
8
+ 'dlx',
9
+ 'explain',
10
+ 'info',
11
+ 'init',
12
+ 'install',
13
+ 'link',
14
+ 'pack',
15
+ 'patch',
16
+ 'patch-commit',
17
+ 'plugin',
18
+ 'rebuild',
19
+ 'remove',
20
+ 'search',
21
+ 'set',
22
+ 'stage',
23
+ 'unlink',
24
+ 'unplug',
25
+ 'up',
26
+ 'upgrade-interactive',
27
+ 'version',
28
+ 'why',
29
+ 'workspace',
30
+ 'workspaces',
31
+ ];
32
+ export const resolve = (binary, args, manifest) => {
33
+ const scripts = manifest.scripts ? Object.keys(manifest.scripts) : [];
34
+ const [command, ...commandArgs] = args;
35
+ if (scripts.includes(command) || commands.includes(command))
36
+ return [];
37
+ if (command === 'run' && scripts.includes(commandArgs[0]))
38
+ return [];
39
+ if (command === 'run' || command === 'exec')
40
+ return commandArgs.find(arg => !arg.startsWith('-'));
41
+ return args.find(arg => !arg.startsWith('-'));
42
+ };
@@ -0,0 +1,7 @@
1
+ import type { PackageJson } from 'type-fest';
2
+ export declare const getDependenciesFromLoaderArguments: (args: string[]) => string[];
3
+ export declare const getBinariesFromScripts: (npmScripts: string[], { manifest, ignore, knownGlobalsOnly, }: {
4
+ manifest: PackageJson;
5
+ ignore?: string[] | undefined;
6
+ knownGlobalsOnly?: boolean | undefined;
7
+ }) => string[];
@@ -0,0 +1,46 @@
1
+ import { IGNORED_GLOBAL_BINARIES } from '../../constants.js';
2
+ import { getArgumentValues } from '../plugin.js';
3
+ import { toCamelCase } from '../plugin.js';
4
+ import * as BinaryResolvers from './binaries/index.js';
5
+ import * as GlobalBinaryResolvers from './globals/index.js';
6
+ const normalizeBinaries = (command) => command
7
+ .replace(/(\.\/)?node_modules\/\.bin\/(\w+)/, '$2')
8
+ .replace(/\$\(npm bin\)\/(\w+)/, '$1')
9
+ .replace(/(\S+)@.*/, '$1');
10
+ const stripEnvironmentVariables = (value) => value.replace(/([A-Z][^ ]*)=([^ ])+ /g, '');
11
+ const getLoaderArgumentValues = (value) => getArgumentValues(value, / (--(experimental-)?loader|--require|-r)[ =]([^ ]+)/g);
12
+ export const getDependenciesFromLoaderArguments = (args) => getLoaderArgumentValues(' ' + args.join(' '))
13
+ .filter(scripts => !scripts.startsWith('.'))
14
+ .map(script => {
15
+ if (script.startsWith('@')) {
16
+ const [scope, packageName] = script.split('/');
17
+ return [scope, packageName].join('/');
18
+ }
19
+ return script.split('/')[0];
20
+ });
21
+ export const getBinariesFromScripts = (npmScripts, { manifest, ignore = [], knownGlobalsOnly = false, }) => Array.from(npmScripts.reduce((binaries, script) => {
22
+ script
23
+ .split(' && ')
24
+ .flatMap(command => command.split(' -- '))
25
+ .map(normalizeBinaries)
26
+ .filter(command => /^\w/.test(command))
27
+ .map(stripEnvironmentVariables)
28
+ .flatMap(command => {
29
+ const [binary, ...args] = command.trim().split(' ');
30
+ const camelCased = toCamelCase(binary);
31
+ if (['bun', 'deno'].includes(binary))
32
+ return [];
33
+ if (camelCased in GlobalBinaryResolvers) {
34
+ return GlobalBinaryResolvers[camelCased].resolve(binary, args, manifest);
35
+ }
36
+ if (knownGlobalsOnly)
37
+ return [];
38
+ if (camelCased in BinaryResolvers) {
39
+ return BinaryResolvers[camelCased].resolve(binary, args);
40
+ }
41
+ const dependenciesFromArguments = getDependenciesFromLoaderArguments(args);
42
+ return [binary, ...dependenciesFromArguments];
43
+ })
44
+ .forEach(binary => binary && binaries.add(binary));
45
+ return binaries;
46
+ }, new Set())).filter(binaryName => !IGNORED_GLOBAL_BINARIES.includes(binaryName) && !ignore.includes(binaryName));
@@ -47,13 +47,3 @@ export const debugLogIssues = (name, issues) => {
47
47
  console.debug(`[knip] ${name} (${symbols.length})`);
48
48
  logArray(symbols);
49
49
  };
50
- const debugLogDiff = (name, arrA, arrB) => {
51
- const onlyInA = arrA.filter(itemA => !arrB.includes(itemA)).sort();
52
- const onlyInB = arrB.filter(itemB => !arrA.includes(itemB)).sort();
53
- console.log(`[knip] ${name}`);
54
- console.log(`[knip] Only in left:`);
55
- logArray(onlyInA);
56
- console.log();
57
- console.log(`[knip] Only in right:`);
58
- logArray(onlyInB);
59
- };
package/dist/util/fs.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export declare const isFile: (filePath: string) => Promise<boolean>;
2
- export declare const findFile: (workingDir: string, fileName: string) => Promise<string | undefined>;
1
+ export declare const isFile: (filePath: string) => boolean;
2
+ export declare const findFile: (workingDir: string, fileName: string) => string | undefined;
3
3
  export declare const loadJSON: (filePath: string) => Promise<any>;
package/dist/util/fs.js CHANGED
@@ -1,20 +1,16 @@
1
- import fs from 'node:fs/promises';
1
+ import { statSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
2
3
  import path from 'node:path';
3
4
  import stripJsonComments from 'strip-json-comments';
4
- export const isFile = async (filePath) => {
5
- try {
6
- const stats = await fs.stat(filePath);
7
- return stats.isFile();
8
- }
9
- catch {
10
- return false;
11
- }
5
+ export const isFile = (filePath) => {
6
+ const stat = statSync(filePath, { throwIfNoEntry: false });
7
+ return stat !== undefined && stat.isFile();
12
8
  };
13
- export const findFile = async (workingDir, fileName) => {
9
+ export const findFile = (workingDir, fileName) => {
14
10
  const filePath = path.join(workingDir, fileName);
15
- return (await isFile(filePath)) ? filePath : undefined;
11
+ return isFile(filePath) ? filePath : undefined;
16
12
  };
17
13
  export const loadJSON = async (filePath) => {
18
- const contents = await fs.readFile(filePath);
14
+ const contents = await readFile(filePath);
19
15
  return JSON.parse(stripJsonComments(contents.toString()));
20
16
  };
@@ -0,0 +1 @@
1
+ export declare const getValuesByKeyDeep: (obj: any, key: string) => unknown[];
@@ -0,0 +1,15 @@
1
+ export const getValuesByKeyDeep = (obj, key) => {
2
+ const objects = [];
3
+ if (obj && typeof obj === 'object') {
4
+ for (const i in obj) {
5
+ if (obj[i] && typeof obj[i] === 'object') {
6
+ const values = getValuesByKeyDeep(obj[i], key);
7
+ objects.push(...values);
8
+ }
9
+ else if (i === key) {
10
+ objects.push(obj[i]);
11
+ }
12
+ }
13
+ }
14
+ return objects;
15
+ };
@@ -229,6 +229,7 @@ export default class WorkspaceWorker {
229
229
  cwd,
230
230
  manifest: this.manifest,
231
231
  config: pluginConfig,
232
+ rootConfig: this.rootConfig,
232
233
  workspaceConfig: this.config,
233
234
  isProduction: this.isProduction,
234
235
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Find unused files, dependencies and exports in your TypeScript and JavaScript projects",
5
5
  "homepage": "https://github.com/webpro/knip",
6
6
  "repository": "github:webpro/knip",
@@ -10,11 +10,12 @@
10
10
  "name": "Lars Kappert",
11
11
  "email": "lars@webpro.nl"
12
12
  },
13
- "main": "dist/index.js",
13
+ "main": "./dist/index.js",
14
14
  "bin": {
15
- "knip": "dist/cli.js"
15
+ "knip": "./dist/cli.js"
16
16
  },
17
17
  "type": "module",
18
+ "types": "./dist/index.d.ts",
18
19
  "scripts": {
19
20
  "knip": "node ./dist/cli.js",
20
21
  "knip:production": "node ./dist/cli.js --production --strict",
@@ -42,9 +43,9 @@
42
43
  "@snyk/github-codeowners": "1.1.0",
43
44
  "chalk": "5.2.0",
44
45
  "easy-table": "1.2.0",
45
- "esbuild": "0.16.17",
46
+ "esbuild": "0.17.0",
46
47
  "esbuild-register": "3.4.2",
47
- "eslint": "8.31.0",
48
+ "eslint": "8.32.0",
48
49
  "fast-glob": "3.2.12",
49
50
  "get-tsconfig": "4.3.0",
50
51
  "globby": "13.1.3",
@@ -77,7 +78,7 @@
77
78
  "remark-cli": "11.0.0",
78
79
  "remark-preset-webpro": "0.0.1",
79
80
  "tsx": "3.12.2",
80
- "type-fest": "3.5.1",
81
+ "type-fest": "3.5.2",
81
82
  "typescript": "4.9.4"
82
83
  },
83
84
  "engines": {
package/schema.json CHANGED
@@ -23,7 +23,7 @@
23
23
  "properties": {
24
24
  "ignoreBinaries": {
25
25
  "title": "Binaries to ignore",
26
- "examples": ["rm", "docker-compose", "eslint"],
26
+ "examples": ["rm", "docker-compose", "curl"],
27
27
  "$ref": "#/definitions/list"
28
28
  },
29
29
  "ignoreDependencies": {
@@ -184,6 +184,10 @@
184
184
  "title": "Gatsby plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/gatsby/README.md)",
185
185
  "$ref": "#/definitions/plugin"
186
186
  },
187
+ "github-actions": {
188
+ "title": "github-actions plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/github-actions/README.md)",
189
+ "$ref": "#/definitions/plugin"
190
+ },
187
191
  "jest": {
188
192
  "title": "Jest plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/jest/README.md)",
189
193
  "$ref": "#/definitions/plugin"