knip 0.13.1 → 1.0.0-alpha.0

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 (135) hide show
  1. package/README.md +92 -148
  2. package/dist/cli.js +11 -21
  3. package/dist/configuration-chief.d.ts +36 -0
  4. package/dist/configuration-chief.js +190 -0
  5. package/dist/dependency-deputy.d.ts +59 -0
  6. package/dist/dependency-deputy.js +134 -0
  7. package/dist/index.d.ts +6 -5
  8. package/dist/index.js +223 -103
  9. package/dist/issue-collector.d.ts +34 -0
  10. package/dist/issue-collector.js +92 -0
  11. package/dist/issues/initializers.d.ts +4 -0
  12. package/dist/issues/initializers.js +41 -0
  13. package/dist/npm-scripts/helpers.d.ts +2 -0
  14. package/dist/npm-scripts/helpers.js +54 -0
  15. package/dist/npm-scripts/index.d.ts +5 -0
  16. package/dist/npm-scripts/index.js +39 -0
  17. package/dist/plugins/babel/helpers.d.ts +2 -0
  18. package/dist/plugins/babel/helpers.js +22 -0
  19. package/dist/plugins/babel/index.d.ts +4 -0
  20. package/dist/plugins/babel/index.js +41 -0
  21. package/dist/plugins/babel/types.d.ts +4 -0
  22. package/dist/{types.js → plugins/babel/types.js} +0 -0
  23. package/dist/plugins/capacitor/index.d.ts +4 -0
  24. package/dist/plugins/capacitor/index.js +11 -0
  25. package/dist/plugins/capacitor/types.d.ts +3 -0
  26. package/dist/plugins/capacitor/types.js +1 -0
  27. package/dist/plugins/changesets/index.d.ts +4 -0
  28. package/dist/plugins/changesets/index.js +13 -0
  29. package/dist/plugins/cypress/index.d.ts +4 -0
  30. package/dist/plugins/cypress/index.js +7 -0
  31. package/dist/plugins/eslint/helpers.d.ts +2 -0
  32. package/dist/plugins/eslint/helpers.js +17 -0
  33. package/dist/plugins/eslint/index.d.ts +4 -0
  34. package/dist/plugins/eslint/index.js +39 -0
  35. package/dist/plugins/eslint/types.d.ts +10 -0
  36. package/dist/plugins/eslint/types.js +1 -0
  37. package/dist/plugins/gatsby/index.d.ts +5 -0
  38. package/dist/plugins/gatsby/index.js +30 -0
  39. package/dist/plugins/gatsby/types.d.ts +15 -0
  40. package/dist/plugins/gatsby/types.js +1 -0
  41. package/dist/plugins/index.d.ts +15 -0
  42. package/dist/plugins/index.js +15 -0
  43. package/dist/plugins/jest/index.d.ts +5 -0
  44. package/dist/plugins/jest/index.js +40 -0
  45. package/dist/plugins/next/index.d.ts +5 -0
  46. package/dist/plugins/next/index.js +6 -0
  47. package/dist/plugins/nx/index.d.ts +4 -0
  48. package/dist/plugins/nx/index.js +14 -0
  49. package/dist/plugins/playwright/index.d.ts +4 -0
  50. package/dist/plugins/playwright/index.js +3 -0
  51. package/dist/plugins/postcss/index.d.ts +5 -0
  52. package/dist/plugins/postcss/index.js +15 -0
  53. package/dist/plugins/postcss/types.d.ts +3 -0
  54. package/dist/plugins/postcss/types.js +1 -0
  55. package/dist/plugins/remark/index.d.ts +4 -0
  56. package/dist/plugins/remark/index.js +10 -0
  57. package/dist/plugins/remix/index.d.ts +5 -0
  58. package/dist/plugins/remix/index.js +11 -0
  59. package/dist/plugins/rollup/index.d.ts +4 -0
  60. package/dist/plugins/rollup/index.js +3 -0
  61. package/dist/plugins/storybook/index.d.ts +6 -0
  62. package/dist/plugins/storybook/index.js +23 -0
  63. package/dist/plugins/storybook/types.d.ts +6 -0
  64. package/dist/plugins/storybook/types.js +1 -0
  65. package/dist/project-principal.d.ts +16 -0
  66. package/dist/project-principal.js +70 -0
  67. package/dist/reporters/codeowners.d.ts +1 -1
  68. package/dist/reporters/compact.d.ts +1 -1
  69. package/dist/reporters/index.d.ts +4 -4
  70. package/dist/reporters/index.js +2 -2
  71. package/dist/reporters/json.d.ts +1 -1
  72. package/dist/reporters/json.js +1 -1
  73. package/dist/reporters/symbols.d.ts +1 -1
  74. package/dist/reporters/symbols.js +2 -3
  75. package/dist/source-lab.d.ts +22 -0
  76. package/dist/source-lab.js +156 -0
  77. package/dist/types/cli.d.ts +7 -0
  78. package/dist/types/cli.js +1 -0
  79. package/dist/types/config.d.ts +41 -0
  80. package/dist/types/config.js +1 -0
  81. package/dist/types/issues.d.ts +39 -0
  82. package/dist/types/issues.js +1 -0
  83. package/dist/types/plugins.d.ts +11 -0
  84. package/dist/types/plugins.js +1 -0
  85. package/dist/types/validate.d.ts +1081 -0
  86. package/dist/types/validate.js +54 -0
  87. package/dist/types/workspace.d.ts +13 -0
  88. package/dist/types/workspace.js +1 -0
  89. package/dist/util/array.d.ts +2 -0
  90. package/dist/util/array.js +2 -0
  91. package/dist/util/constants.d.ts +4 -0
  92. package/dist/util/constants.js +4 -0
  93. package/dist/util/debug.d.ts +5 -8
  94. package/dist/util/debug.js +48 -17
  95. package/dist/util/externalImports.d.ts +2 -0
  96. package/dist/util/externalImports.js +24 -0
  97. package/dist/util/fs.d.ts +2 -2
  98. package/dist/util/fs.js +9 -11
  99. package/dist/util/glob.d.ts +13 -2
  100. package/dist/util/glob.js +26 -3
  101. package/dist/{help.d.ts → util/help.d.ts} +0 -0
  102. package/dist/{help.js → util/help.js} +7 -10
  103. package/dist/util/loader.d.ts +2 -2
  104. package/dist/util/loader.js +7 -5
  105. package/dist/{log.d.ts → util/log.d.ts} +0 -0
  106. package/dist/{log.js → util/log.js} +0 -0
  107. package/dist/util/members.d.ts +1 -1
  108. package/dist/util/members.js +1 -1
  109. package/dist/util/modules.d.ts +1 -0
  110. package/dist/util/modules.js +10 -0
  111. package/dist/util/parseArgs.d.ts +6 -3
  112. package/dist/util/parseArgs.js +6 -3
  113. package/dist/util/path.js +1 -1
  114. package/dist/util/performance.d.ts +1 -1
  115. package/dist/util/performance.js +1 -1
  116. package/dist/util/project.d.ts +1 -1
  117. package/dist/util/project.js +10 -10
  118. package/dist/util/resolveIncludedIssueTypes.d.ts +9 -0
  119. package/dist/util/resolveIncludedIssueTypes.js +35 -0
  120. package/dist/util/sort.d.ts +1 -0
  121. package/dist/util/sort.js +9 -0
  122. package/dist/util/tsconfig-loader.d.ts +2 -0
  123. package/dist/util/tsconfig-loader.js +10 -0
  124. package/dist/workspace-worker.d.ts +70 -0
  125. package/dist/workspace-worker.js +226 -0
  126. package/package.json +21 -9
  127. package/dist/progress.d.ts +0 -5
  128. package/dist/progress.js +0 -35
  129. package/dist/runner.d.ts +0 -5
  130. package/dist/runner.js +0 -190
  131. package/dist/types.d.ts +0 -87
  132. package/dist/util/config.d.ts +0 -6
  133. package/dist/util/config.js +0 -49
  134. package/dist/util/dependencies.d.ts +0 -7
  135. package/dist/util/dependencies.js +0 -50
package/README.md CHANGED
@@ -19,7 +19,9 @@ The dots don't connect themselves. This is where Knip comes in:
19
19
  - [x] Finds **unused files, dependencies and exports**.
20
20
  - [x] Finds used dependencies not listed in `package.json`.
21
21
  - [x] Finds duplicate exports.
22
- - [x] Find unused members of classes and enums
22
+ - [x] Finds unused members of classes and enums
23
+ - [x] Supports workspaces/monorepos
24
+ - [x] Growing list of plugins (= less to configure and custom dependency resolvers)
23
25
  - [x] Supports JavaScript (without `tsconfig.json`, or TypeScript `allowJs: true`).
24
26
  - [x] Features multiple [reporters][1] and supports [custom reporters][2] (think JSON and `CODEOWNERS`)
25
27
  - [x] Run Knip as part of your CI environment to detect issues and prevent regressions.
@@ -40,10 +42,7 @@ over new features:
40
42
 
41
43
  ### Upcoming Features
42
44
 
43
- - [ ] Custom dependency resolvers: find dependencies used in npm scripts.
44
- - [ ] Custom dependency resolvers: find unused and unlisted plugins for Webpack, ESLint & Babel, etc. (#7)
45
45
  - [ ] Smart default configurations and more fine-grained configuration options.
46
- - [ ] Full support for monorepos (partial [monorepos support][7] with `--dir` exists).
47
46
  - [ ] Fix issues: remove `export` keyword, uninstall unused dependencies, delete files (like `--fix` of ESLint).
48
47
  - [ ] Add more reporters and report customization options (#3).
49
48
 
@@ -60,12 +59,12 @@ Create a configuration file, let's give it the default name `knip.json` with the
60
59
  ```json
61
60
  {
62
61
  "entryFiles": ["src/index.ts"],
63
- "projectFiles": ["src/**/*.ts", "!**/*.spec.ts"]
62
+ "projectFiles": ["src/**/*.ts"]
64
63
  }
65
64
  ```
66
65
 
67
- The `entryFiles` target the starting point(s) to resolve production code dependencies. The `projectFiles` should contain
68
- all files it should match them against, including potentially unused files.
66
+ The `entryFiles` target the starting point(s) to resolve code dependencies. The `projectFiles` should contain all files
67
+ it should match them against, including potentially unused files.
69
68
 
70
69
  Then run the checks:
71
70
 
@@ -77,10 +76,10 @@ This will analyze the project and output unused files, exports, types and duplic
77
76
 
78
77
  Knip works by creating two sets of files:
79
78
 
80
- 1. Production code is the set of files resolved from the `entryFiles`.
81
- 2. They are matched against the set of `projectFiles`.
82
- 3. The subset of project files that is not production code will be reported as unused files (in red).
83
- 4. Then the production code (in blue) will be analyzed for unused exports.
79
+ 1. The set of files resolved from the `entryFiles`. In other words, the files that the entry files depend upon.
80
+ 2. The set of `projectFiles`.
81
+ 3. The project files that are part of entry files and its dependencies will be reported as unused files (in red).
82
+ 4. Then everything else (in blue) will be analyzed for unused exports and dependencies.
84
83
 
85
84
  ![How it works][8]
86
85
 
@@ -90,15 +89,12 @@ Knip works by creating two sets of files:
90
89
  knip [options]
91
90
 
92
91
  Options:
93
- -c/--config [file] Configuration file path (default: ./knip.json or package.json#knip)
92
+ -c/--config [file] Configuration file path (default: ./knip.json, knip.jsonc or package.json#knip)
94
93
  -t/--tsConfig [file] TypeScript configuration path (default: ./tsconfig.json)
95
- --dir Working directory (default: current working directory)
94
+ --production Analyze only production source files (e.g. no tests, devDependencies, exported types)
95
+ --workspace Analyze a single workspace (default: analyze all configured workspaces)
96
96
  --include Report only listed issue type(s), can be repeated
97
97
  --exclude Exclude issue type(s) from report, can be repeated
98
- --ignore Ignore files matching this glob pattern, can be repeated
99
- --no-gitignore Don't use .gitignore
100
- --dev Include `devDependencies` in report(s)
101
- --include-entry-files Report unused exports and types for entry files
102
98
  --no-progress Don't show dynamic progress updates
103
99
  --max-issues Maximum number of issues before non-zero exit code (default: 0)
104
100
  --reporter Select reporter: symbols, compact, codeowners, json (default: symbols)
@@ -112,9 +108,9 @@ Knip works by creating two sets of files:
112
108
  Examples:
113
109
 
114
110
  $ knip
115
- $ knip --dir packages/client --include files
111
+ $ knip --production
112
+ $ knip --workspace packages/client --include files
116
113
  $ knip -c ./knip.js --reporter compact
117
- $ knip --ignore 'lib/**/*.ts' --ignore build
118
114
  $ knip --reporter codeowners --reporter-options '{"path":".github/CODEOWNERS"}'
119
115
 
120
116
  More info: https://github.com/webpro/knip
@@ -180,6 +176,68 @@ Use `--exclude` to ignore reports you're not interested in:
180
176
 
181
177
  Use `--performance` to see where most of the time is spent.
182
178
 
179
+ ## Workspaces & Monorepos
180
+
181
+ Workspaces and monorepos are handled out-of-the-box by Knip. Every workspace that is part of the Knip configuration will
182
+ be part of the analysis. Here's a simple example:
183
+
184
+ ```jsonc
185
+ {
186
+ "ignoreFiles": "**/fixtures/**",
187
+ "ignoreBinaries": ["deno", "git"],
188
+ "ignoreWorkspaces": ["packages/ignore-me"],
189
+ "workspaces": {
190
+ "packages/*": {
191
+ "entryFiles": "{index,cli}.ts!",
192
+ "projectFiles": "**/*.ts"
193
+ },
194
+ "packages/exception": {
195
+ "entryFiles": "something/different.js"
196
+ },
197
+ "not-a-workspace/in-package.json/but-has-package.json": {
198
+ "entryFiles": ["src/index.ts"],
199
+ "projectFiles": "src/**/*.ts"
200
+ }
201
+ }
202
+ }
203
+ ```
204
+
205
+ All `workspaces` or `workspaces.packages` in `package.json` with a match in `workspaces` of `Knip.json` are part of the
206
+ analysis.
207
+
208
+ Extra "workspaces" not cnfigured as a workspace in the root `package.json` can be configured as well, Knip is happy to
209
+ analyze unused dependencies and exports from any directory with a `package.json`.
210
+
211
+ ## Plugins
212
+
213
+ Knip contains a growing list of plugins:
214
+
215
+ - Babel
216
+ - Capacitor
217
+ - Changesets
218
+ - Cypress
219
+ - ESLint
220
+ - Gatsby
221
+ - Jest
222
+ - Next.js
223
+ - Nx
224
+ - Playwright
225
+ - PostCSS
226
+ - Remark
227
+ - Remix
228
+ - Rollup
229
+ - Storybook
230
+
231
+ Plugins are automatically activated, no need to enable anything. Each plugin is automatically enabled based on simple
232
+ heuristics. Most of them check whether one or one of a few (dev) dependencies are listed in `package.json`. Once
233
+ enabled, they add the right set of configuration and entry files for Knip to analyze.
234
+
235
+ Most configuration files are easy to find dependencies in. Yet some of the plugins include custom dependency resolvers.
236
+ For instance, the `eslint` plugin tells Knip that an `"import"` entry in the array of plugins means that the
237
+ `eslint-plugin-import` dependency should be installed. Or the `storybook` plugin understands that
238
+ `core.builder: 'webpack5'` in `main.js` means that the `@storybook/builder-webpack5` and `@storybook/manager-webpack5`
239
+ dependencies are required.
240
+
183
241
  ## Configuration
184
242
 
185
243
  ### Libraries versus Applications
@@ -200,128 +258,31 @@ export const merge = function () {};
200
258
 
201
259
  Knip does not report public exports and types as unused.
202
260
 
203
- ### Production versus non-production code
204
-
205
- Feels like you're getting too many false positives? Let's talk about `entryFiles` and `projectFiles`.
206
-
207
- #### Production code
208
-
209
- The default configuration for Knip is very strict and targets production code. Non-production files such as tests should
210
- not be part of the `entryFiles` and `projectFiles`. Here's why: test and other non-production files often import
211
- production files, which will prevent the production files from being reported as unused. For best results:
212
-
213
- - Include only production entry files to the `entryFiles`.
214
- - Include only and all production files to the `projectFiles`.
215
- - If necessary, add globs to exclude non-production files from the `projectFiles` (using negation pattern).
261
+ ### Production mode
216
262
 
217
- This will ensure Knip understands what production code can be removed.
218
-
219
- #### Non-production code
263
+ The default configuration for Knip is loose and targets all project code. Non-production files such as tests often
264
+ import production files, which will prevent the production files from being reported as unused.
220
265
 
221
266
  Non-production code includes files such as unit tests, end-to-end tests, tooling, scripts, Storybook stories, etc. Think
222
267
  of it the same way as the convention to split `dependencies` and `devDependencies` in `package.json`.
223
268
 
224
- To analyze the project as a whole:
225
-
226
- - Include both production entry files and test files to the `entryFiles`.
227
- - Include all production files to the `projectFiles`.
228
- - If necessary, add globs for non-production files to the `projectFiles`.
229
- - Set `dev: true` in the configuration or add `--dev` as a command line flag (to add `devDependencies`).
230
-
231
- Here's an example:
269
+ To exclude non-production code such as configuration files and tests, use production mode with `--production`. This will
270
+ only include the files meant for production use. Add an exclamation mark behind the patterns that are meant for
271
+ production like so:
232
272
 
233
273
  ```json
234
274
  {
235
- "dev": true,
236
- "entryFiles": ["src/index.ts", "src/**/*.spec.ts", "src/**/*.e2e.ts"],
275
+ "entryFiles": ["build/script.js", "src/index.ts!"],
237
276
  "projectFiles": ["src/**/*.ts"]
238
277
  }
239
278
  ```
240
279
 
241
- Now use `-c knip.dev.json` to find unused files, dependencies and exports for the project as a whole.
242
-
243
- An alternative way to store `dev` configuration is in this example `package.json`:
244
-
245
- ```json
246
- {
247
- "name": "my-package",
248
- "scripts": {
249
- "knip": "knip"
250
- },
251
- "knip": {
252
- "entryFiles": ["src/index.ts"],
253
- "projectFiles": ["src/**/*.ts", "!**/*.spec.ts"],
254
- "dev": {
255
- "entryFiles": ["src/index.ts", "src/**/*.spec.ts", "src/**/*.e2e.ts"],
256
- "projectFiles": ["src/**/*.ts"]
257
- }
258
- }
259
- }
260
- ```
261
-
262
- Using the `--dev` flag will now switch to the non-production analysis.
263
-
264
- Depending on the complexity of the project, be aware that it might require some fine-tuning on your end.
280
+ Configuration, build and test files are not included. Knip finds unused files and export values in production code only.
265
281
 
266
- ## Zero-config
267
-
268
- Knip can work without any configuration. Then an existing `tsconfig.json` file is required. Since `entryFiles` and
269
- `projectFiles` are now the same, Knip is unable to report unused files.
270
-
271
- ## More configuration examples
272
-
273
- ### Monorepos
274
-
275
- #### Separate packages
276
-
277
- In repos with multiple (publishable) packages, the `--dir` option comes in handy. With similar package structures, the
278
- packages can be configured using globs:
279
-
280
- ```json
281
- {
282
- "packages/*": {
283
- "entryFiles": ["src/index.ts"],
284
- "projectFiles": ["src/**/*.{ts,tsx}", "!**/*.spec.{ts,tsx}"]
285
- }
286
- }
287
- ```
288
-
289
- Packages can also be explicitly configured per package directory.
290
-
291
- To analyze the packages separately, using the matching pattern from the configuration file:
292
-
293
- knip --dir packages/client
294
- knip --dir packages/services
295
-
296
- #### Connected projects
297
-
298
- Let's take another example Nx project configuration using Next.js, Jest and Storybook, which has multiple apps and libs.
299
- They are not published separately and don't have their own `package.json`.
300
-
301
- This configuration file can also be a JavaScript file, which allows to add logic and/or comments (e.g. `knip.js`):
302
-
303
- ```js
304
- const entryFiles = ['apps/**/pages/**/*.{js,ts,tsx}'];
305
-
306
- const projectFiles = [
307
- '{apps,libs}/**/*.{ts,tsx}',
308
- // Next.js
309
- '!**/next.config.js',
310
- '!**/apps/**/public/**',
311
- '!**/apps/**/next-env.d.ts'
312
- // Jest
313
- '!**/jest.config.ts',
314
- '!**/*.spec.{ts,tsx}',
315
- // Storybook
316
- '!**/.storybook/**',
317
- '!**/*.stories.tsx',
318
- ];
319
-
320
- module.exports = { entryFiles, projectFiles };
321
- ```
322
-
323
- This should give good results about unused files, dependencies and exports for the monorepo. After the first run, the
324
- configuration can be tweaked further to the project structure.
282
+ Plugins also have this distinction. For instance, Next.js entry files for pages (`pages/**/*.tsx`) and Remix routes
283
+ (`app/routes/**/*.tsx`) are production code, while Jest and Playwright entry files (e.g. `*.spec.ts`) are not. All of
284
+ this is handled automatically by Knip and its plugins. You only need to point Knip to additional files or custom file
285
+ locations. The more plugins Knip will have, the more projects can be analyzed out of the box!
325
286
 
326
287
  ## Reporters
327
288
 
@@ -346,7 +307,7 @@ type ReporterOptions = {
346
307
  issues: Issues;
347
308
  cwd: string;
348
309
  workingDir: string;
349
- isDev: boolean;
310
+ isProduction: boolean;
350
311
  options: string;
351
312
  };
352
313
  ```
@@ -511,7 +472,7 @@ This table is a work in progress, but here's a first impression. Based on their
511
472
  | Unused files | ✅ | - | ✅ | - | - | - |
512
473
  | Unused dependencies | ✅ | ✅ | ✅ | - | - | - |
513
474
  | Unlisted dependencies | ✅ | ✅ | ✅ | - | - | - |
514
- | [Custom dependency resolvers][21] | | ✅ | ❌ | - | - | - |
475
+ | [Custom dependency resolvers][21] | | ✅ | ❌ | - | - | - |
515
476
  | Unused exports | ✅ | - | - | ✅ | ✅ | ✅ |
516
477
  | Unused class members | ✅ | - | - | - | - | - |
517
478
  | Unused enum members | ✅ | - | - | - | - | - |
@@ -520,28 +481,11 @@ This table is a work in progress, but here's a first impression. Based on their
520
481
  | Custom reporters | ✅ | - | - | - | - | - |
521
482
  | JavaScript support | ✅ | ✅ | ✅ | - | - | ✅ |
522
483
  | Configure entry files | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
523
- | [Support monorepos][22] | 🟠 | - | - | - | - | - |
484
+ | [Support monorepos][22] | | - | - | - | - | - |
524
485
  | ESLint plugin available | - | - | - | ✅ | - | - |
525
486
 
526
487
  ✅ = Supported, ❌ = Not supported, - = Out of scope
527
488
 
528
- ## Monorepos
529
-
530
- Knip wants to [support monorepos][14] properly, the first steps in this direction are implemented.
531
-
532
- ## Custom dependency resolvers
533
-
534
- Using a string like `"plugin:cypress/recommended"` in the `extends` property of a `.eslintrc.json` in a package
535
- directory of a monorepo is nice for DX. But Knip will need some help to find it and to understand this resolves to the
536
- `eslint-plugin-cypress` dependency. Or see it is not listed in `package.json`. Or that the dependency is still listed,
537
- but no longer in use. Many popular projects reference plugins in similar ways, such as Babel, Webpack and Storybook.
538
-
539
- Big compliments to [depcheck][23] which already does this! They call this "specials". This is on [Knip's roadmap][24],
540
- as well, with the additional ambition to also find used dependencies that are not listed in `package.json`.
541
-
542
- unimported is strict in this regard and works based on production files and `dependencies`, so does not have custom
543
- dependency resolvers which are usually only needed for `devDependencies`.
544
-
545
489
  ## TypeScript language services
546
490
 
547
491
  TypeScript language services could play a major role in most of the "unused" areas, as they have an overview of the
package/dist/cli.js CHANGED
@@ -1,41 +1,31 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'node:path';
3
- import parsedArgs from './util/parseArgs.js';
4
- import { main } from './index.js';
5
- import { printHelp } from './help.js';
3
+ import { register } from 'esbuild-register/dist/node.js';
4
+ import { printHelp } from './util/help.js';
6
5
  import reporters from './reporters/index.js';
7
6
  import { ConfigurationError } from './util/errors.js';
7
+ import parsedArgs from './util/parseArgs.js';
8
8
  import { measure } from './util/performance.js';
9
- import load from './util/loader.js';
10
- const { values: { help, dir, config: configFilePath, tsConfig: tsConfigFilePath, include = [], exclude = [], ignore = [], 'no-gitignore': isNoGitIgnore = false, dev: isDev = false, 'include-entry-files': isIncludeEntryFiles = false, 'no-progress': noProgress = false, reporter = 'symbols', 'reporter-options': reporterOptions = '', 'max-issues': maxIssues = '0', debug: isDebug = false, 'debug-level': debugLevel = '1', }, } = parsedArgs;
9
+ import { main } from './index.js';
10
+ register();
11
+ const { values: { help, 'no-gitignore': isNoGitIgnore = false, strict: isStrict = false, production: isProduction = false, 'no-progress': noProgress = false, reporter = 'symbols', 'reporter-options': reporterOptions = '', 'max-issues': maxIssues = '0', debug: isDebug = false, }, } = parsedArgs;
11
12
  if (help) {
12
13
  printHelp();
13
14
  process.exit(0);
14
15
  }
15
16
  const cwd = process.cwd();
16
- const workingDir = dir ? path.resolve(dir) : cwd;
17
17
  const isShowProgress = !isDebug && noProgress === false && process.stdout.isTTY && typeof process.stdout.cursorTo === 'function';
18
- const printReport = reporter in reporters ? reporters[reporter] : await load(path.join(workingDir, reporter));
18
+ const printReport = reporter in reporters ? reporters[reporter] : await import(path.join(cwd, reporter));
19
19
  const run = async () => {
20
20
  try {
21
21
  const { report, issues, counters } = await main({
22
22
  cwd,
23
- workingDir,
24
- configFilePath,
25
- tsConfigFilePath,
26
- include,
27
- exclude,
28
- ignore,
29
23
  gitignore: !isNoGitIgnore,
30
- isIncludeEntryFiles,
31
- isDev,
24
+ isStrict,
25
+ isProduction,
32
26
  isShowProgress,
33
- debug: {
34
- isEnabled: isDebug,
35
- level: isDebug ? Number(debugLevel) : 0,
36
- },
37
27
  });
38
- await printReport({ report, issues, cwd, workingDir, isDev, options: reporterOptions });
28
+ await printReport({ report, issues, cwd, isProduction, options: reporterOptions });
39
29
  const totalErrorCount = Object.keys(report)
40
30
  .filter(reportGroup => report[reportGroup])
41
31
  .reduce((errorCount, reportGroup) => errorCount + counters[reportGroup], 0);
@@ -52,4 +42,4 @@ const run = async () => {
52
42
  throw error;
53
43
  }
54
44
  };
55
- run();
45
+ await run();
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+ import { LocalConfiguration } from './types/validate.js';
3
+ import type { Configuration, WorkspaceConfiguration } from './types/config.js';
4
+ import type { PackageJson } from 'type-fest';
5
+ type ConfigurationManagerOptions = {
6
+ cwd?: string;
7
+ };
8
+ export default class ConfigurationChief {
9
+ cwd: string;
10
+ config: Configuration;
11
+ manifestPath: undefined | string;
12
+ manifest: undefined | PackageJson;
13
+ constructor({ cwd }: ConfigurationManagerOptions);
14
+ loadLocalConfig(): Promise<void>;
15
+ normalize(rawLocalConfig: z.infer<typeof LocalConfiguration>): {
16
+ include: string[];
17
+ exclude: string[];
18
+ ignoreBinaries: string[];
19
+ ignoreFiles: string[];
20
+ ignoreWorkspaces: string[];
21
+ workspaces: Record<string, WorkspaceConfiguration>;
22
+ };
23
+ private getManifestWorkspaces;
24
+ private getConfiguredWorkspaces;
25
+ getActiveWorkspaces(): Promise<{
26
+ name: string;
27
+ dir: string;
28
+ config: WorkspaceConfiguration;
29
+ ancestors: string[];
30
+ }[]>;
31
+ private getConfigKeyForWorkspace;
32
+ private hasConfigForWorkspace;
33
+ getConfigForWorkspace(workspaceName: string): WorkspaceConfiguration;
34
+ resolveIncludedIssueTypes(): import("./types/issues.js").Report;
35
+ }
36
+ export {};
@@ -0,0 +1,190 @@
1
+ import path from 'node:path';
2
+ import micromatch from 'micromatch';
3
+ import { LocalConfiguration } from './types/validate.js';
4
+ import { arrayify } from './util/array.js';
5
+ import { ROOT_WORKSPACE_NAME } from './util/constants.js';
6
+ import { ConfigurationError } from './util/errors.js';
7
+ import { findFile, loadJSON } from './util/fs.js';
8
+ import { _dirGlob } from './util/glob.js';
9
+ import parsedArgs from './util/parseArgs.js';
10
+ import { resolveIncludedIssueTypes } from './util/resolveIncludedIssueTypes.js';
11
+ import { workspaceSorter } from './util/sort.js';
12
+ const { values: { workspace: rawWorkspaceArg, config: rawConfigArg, include = [], exclude = [], strict: isStrict = false, production: isProduction = false, }, } = parsedArgs;
13
+ const defaultConfig = {
14
+ include: [],
15
+ exclude: [],
16
+ ignoreBinaries: [],
17
+ ignoreFiles: [],
18
+ ignoreWorkspaces: [],
19
+ workspaces: {
20
+ [ROOT_WORKSPACE_NAME]: {
21
+ entryFiles: ['index.{js,ts}', 'src/index.{js,ts}'],
22
+ projectFiles: ['**/*.ts'],
23
+ ignore: [],
24
+ paths: {},
25
+ },
26
+ },
27
+ };
28
+ const PLUGIN_NAMES = [
29
+ 'babel',
30
+ 'capacitor',
31
+ 'changesets',
32
+ 'cypress',
33
+ 'eslint',
34
+ 'gatsby',
35
+ 'jest',
36
+ 'next',
37
+ 'nx',
38
+ 'playwright',
39
+ 'postcss',
40
+ 'remark',
41
+ 'remix',
42
+ 'rollup',
43
+ 'storybook',
44
+ ];
45
+ export default class ConfigurationChief {
46
+ cwd = process.cwd();
47
+ config;
48
+ manifestPath;
49
+ manifest;
50
+ constructor({ cwd }) {
51
+ this.cwd = cwd ?? this.cwd;
52
+ this.config = defaultConfig;
53
+ }
54
+ async loadLocalConfig() {
55
+ const manifestPath = await findFile(this.cwd, 'package.json');
56
+ const manifest = manifestPath && (await loadJSON(manifestPath));
57
+ if (!manifestPath || !manifest) {
58
+ throw new ConfigurationError('Unable to find package.json');
59
+ }
60
+ this.manifestPath = manifestPath;
61
+ this.manifest = manifest;
62
+ const configFilePath = rawConfigArg ?? 'knip.json';
63
+ const resolvedConfigFilePath = (await findFile(this.cwd, configFilePath)) ?? (!rawConfigArg && (await findFile(this.cwd, configFilePath + 'c')));
64
+ if (rawConfigArg && !resolvedConfigFilePath && !manifest.knip) {
65
+ throw new ConfigurationError(`Unable to find ${rawConfigArg} or package.json#knip`);
66
+ }
67
+ const rawLocalConfig = resolvedConfigFilePath ? await loadJSON(resolvedConfigFilePath) : manifest.knip;
68
+ if (rawLocalConfig) {
69
+ this.config = this.normalize(LocalConfiguration.parse(rawLocalConfig));
70
+ }
71
+ }
72
+ normalize(rawLocalConfig) {
73
+ const workspaces = rawLocalConfig.workspaces ?? {
74
+ [ROOT_WORKSPACE_NAME]: {
75
+ ...rawLocalConfig,
76
+ },
77
+ };
78
+ const include = rawLocalConfig.include ?? defaultConfig.include;
79
+ const exclude = rawLocalConfig.exclude ?? defaultConfig.exclude;
80
+ const ignoreBinaries = rawLocalConfig.ignoreBinaries ?? defaultConfig.ignoreBinaries;
81
+ const ignoreFiles = arrayify(rawLocalConfig.ignoreFiles ?? defaultConfig.ignoreFiles);
82
+ const ignoreWorkspaces = rawLocalConfig.ignoreWorkspaces ?? defaultConfig.ignoreWorkspaces;
83
+ return {
84
+ include,
85
+ exclude,
86
+ ignoreBinaries,
87
+ ignoreFiles,
88
+ ignoreWorkspaces,
89
+ workspaces: Object.entries(workspaces)
90
+ .filter(([workspaceName]) => !ignoreWorkspaces.includes(workspaceName))
91
+ .reduce((workspaces, workspace) => {
92
+ const [workspaceName, workspaceConfig] = workspace;
93
+ const entryFiles = arrayify(workspaceConfig.entryFiles);
94
+ workspaces[workspaceName] = {
95
+ entryFiles,
96
+ projectFiles: arrayify(workspaceConfig.projectFiles ?? entryFiles),
97
+ ignore: arrayify(workspaceConfig.ignore),
98
+ paths: workspaceConfig.paths ?? {},
99
+ };
100
+ for (const [pluginName, pluginConfig] of Object.entries(workspaceConfig)) {
101
+ if (PLUGIN_NAMES.includes(pluginName)) {
102
+ const isObject = typeof pluginConfig !== 'string' && !Array.isArray(pluginConfig);
103
+ const config = isObject ? arrayify(pluginConfig.config) : arrayify(pluginConfig);
104
+ const entryFiles = isObject && 'entryFiles' in pluginConfig ? arrayify(pluginConfig.entryFiles) : [];
105
+ const projectFiles = isObject && 'projectFiles' in pluginConfig ? arrayify(pluginConfig.projectFiles) : entryFiles;
106
+ workspaces[workspaceName][pluginName] = {
107
+ config,
108
+ entryFiles,
109
+ projectFiles,
110
+ };
111
+ }
112
+ }
113
+ return workspaces;
114
+ }, {}),
115
+ };
116
+ }
117
+ async getManifestWorkspaces() {
118
+ const { workspaces } = this.manifest ?? {};
119
+ const patterns = workspaces ? (Array.isArray(workspaces) ? workspaces : workspaces.packages ?? []) : [];
120
+ return _dirGlob({ patterns, cwd: this.cwd, ignore: this.config.ignoreWorkspaces });
121
+ }
122
+ getConfiguredWorkspaces() {
123
+ return this.config.workspaces ? Object.keys(this.config.workspaces) : [];
124
+ }
125
+ async getActiveWorkspaces() {
126
+ const manifestWorkspaces = await this.getManifestWorkspaces();
127
+ const additionalWorkspaces = Object.keys(this.config.workspaces).filter(name => !name.includes('*') && !manifestWorkspaces.includes(name));
128
+ const rootWorkspace = {
129
+ name: ROOT_WORKSPACE_NAME,
130
+ dir: this.cwd,
131
+ config: this.getConfigForWorkspace(ROOT_WORKSPACE_NAME),
132
+ ancestors: [],
133
+ };
134
+ if (manifestWorkspaces.length === 0 && !rawWorkspaceArg)
135
+ return [rootWorkspace];
136
+ if (rawWorkspaceArg) {
137
+ return [
138
+ rootWorkspace,
139
+ {
140
+ name: rawWorkspaceArg,
141
+ dir: path.resolve(this.cwd, rawWorkspaceArg),
142
+ config: this.getConfigForWorkspace(rawWorkspaceArg),
143
+ ancestors: [ROOT_WORKSPACE_NAME],
144
+ },
145
+ ];
146
+ }
147
+ const workspaces = [...manifestWorkspaces, ...additionalWorkspaces];
148
+ const activeWorkspaces = workspaces.filter(workspaceName => this.hasConfigForWorkspace(workspaceName));
149
+ return activeWorkspaces.sort(workspaceSorter).map(name => ({
150
+ name,
151
+ dir: path.resolve(this.cwd, name),
152
+ config: this.getConfigForWorkspace(name),
153
+ ancestors: activeWorkspaces.reduce((ancestors, ancestorName) => {
154
+ if (name === ancestorName)
155
+ return ancestors;
156
+ if (ancestorName === ROOT_WORKSPACE_NAME || name.startsWith(ancestorName)) {
157
+ if (this.hasConfigForWorkspace(ancestorName)) {
158
+ ancestors.push(ancestorName);
159
+ }
160
+ }
161
+ return ancestors;
162
+ }, []),
163
+ }));
164
+ }
165
+ getConfigKeyForWorkspace(workspaceName) {
166
+ const configuredWorkspaces = this.getConfiguredWorkspaces();
167
+ return configuredWorkspaces
168
+ .sort(workspaceSorter)
169
+ .reverse()
170
+ .find(pattern => micromatch.isMatch(workspaceName, pattern));
171
+ }
172
+ hasConfigForWorkspace(workspaceName) {
173
+ return Boolean(this.getConfigKeyForWorkspace(workspaceName));
174
+ }
175
+ getConfigForWorkspace(workspaceName) {
176
+ const key = this.getConfigKeyForWorkspace(workspaceName);
177
+ if (key) {
178
+ return this.config?.workspaces?.[key] ?? { entryFiles: [], projectFiles: [], paths: {}, ignore: [] };
179
+ }
180
+ return { entryFiles: [], projectFiles: [], paths: {}, ignore: [] };
181
+ }
182
+ resolveIncludedIssueTypes() {
183
+ return resolveIncludedIssueTypes(include, exclude, {
184
+ include: this.config.include ?? [],
185
+ exclude: this.config.exclude ?? [],
186
+ isProduction,
187
+ isStrict,
188
+ });
189
+ }
190
+ }