@macrulez/devtoolz 0.1.0 → 0.2.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.
- package/README.md +197 -2
- package/dist/cli.js +314 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/circular-imports/core.d.ts +15 -0
- package/dist/commands/circular-imports/core.js +95 -0
- package/dist/commands/circular-imports/core.js.map +1 -0
- package/dist/commands/circular-imports/report.d.ts +3 -0
- package/dist/commands/circular-imports/report.js +41 -0
- package/dist/commands/circular-imports/report.js.map +1 -0
- package/dist/commands/circular-imports/run.d.ts +19 -0
- package/dist/commands/circular-imports/run.js +29 -0
- package/dist/commands/circular-imports/run.js.map +1 -0
- package/dist/commands/dead-exports/core.d.ts +1 -1
- package/dist/commands/dead-exports/core.js +2 -2
- package/dist/commands/dead-exports/core.js.map +1 -1
- package/dist/commands/dead-exports/entry.d.ts +0 -1
- package/dist/commands/dead-exports/entry.js +1 -14
- package/dist/commands/dead-exports/entry.js.map +1 -1
- package/dist/commands/dead-exports/run.js +3 -2
- package/dist/commands/dead-exports/run.js.map +1 -1
- package/dist/commands/empty-catch/core.d.ts +8 -0
- package/dist/commands/empty-catch/core.js +61 -0
- package/dist/commands/empty-catch/core.js.map +1 -0
- package/dist/commands/empty-catch/report.d.ts +3 -0
- package/dist/commands/empty-catch/report.js +27 -0
- package/dist/commands/empty-catch/report.js.map +1 -0
- package/dist/commands/empty-catch/run.d.ts +17 -0
- package/dist/commands/empty-catch/run.js +34 -0
- package/dist/commands/empty-catch/run.js.map +1 -0
- package/dist/commands/empty-catch/vue.d.ts +2 -0
- package/dist/commands/empty-catch/vue.js +16 -0
- package/dist/commands/empty-catch/vue.js.map +1 -0
- package/dist/commands/full-check/report.d.ts +3 -0
- package/dist/commands/full-check/report.js +61 -0
- package/dist/commands/full-check/report.js.map +1 -0
- package/dist/commands/full-check/run.d.ts +32 -0
- package/dist/commands/full-check/run.js +251 -0
- package/dist/commands/full-check/run.js.map +1 -0
- package/dist/commands/orphan-tests/core.d.ts +9 -0
- package/dist/commands/orphan-tests/core.js +42 -0
- package/dist/commands/orphan-tests/core.js.map +1 -0
- package/dist/commands/orphan-tests/report.d.ts +3 -0
- package/dist/commands/orphan-tests/report.js +30 -0
- package/dist/commands/orphan-tests/report.js.map +1 -0
- package/dist/commands/orphan-tests/run.d.ts +25 -0
- package/dist/commands/orphan-tests/run.js +55 -0
- package/dist/commands/orphan-tests/run.js.map +1 -0
- package/dist/commands/readme-check/run.js +8 -3
- package/dist/commands/readme-check/run.js.map +1 -1
- package/dist/commands/scripts-check/core.d.ts +18 -0
- package/dist/commands/scripts-check/core.js +121 -0
- package/dist/commands/scripts-check/core.js.map +1 -0
- package/dist/commands/scripts-check/report.d.ts +3 -0
- package/dist/commands/scripts-check/report.js +34 -0
- package/dist/commands/scripts-check/report.js.map +1 -0
- package/dist/commands/scripts-check/run.d.ts +15 -0
- package/dist/commands/scripts-check/run.js +81 -0
- package/dist/commands/scripts-check/run.js.map +1 -0
- package/dist/commands/stale-ts-ignore/check.d.ts +23 -0
- package/dist/commands/stale-ts-ignore/check.js +104 -0
- package/dist/commands/stale-ts-ignore/check.js.map +1 -0
- package/dist/commands/stale-ts-ignore/core.d.ts +11 -0
- package/dist/commands/stale-ts-ignore/core.js +42 -0
- package/dist/commands/stale-ts-ignore/core.js.map +1 -0
- package/dist/commands/stale-ts-ignore/report.d.ts +3 -0
- package/dist/commands/stale-ts-ignore/report.js +30 -0
- package/dist/commands/stale-ts-ignore/report.js.map +1 -0
- package/dist/commands/stale-ts-ignore/run.d.ts +23 -0
- package/dist/commands/stale-ts-ignore/run.js +101 -0
- package/dist/commands/stale-ts-ignore/run.js.map +1 -0
- package/dist/commands/stale-ts-ignore/vue.d.ts +9 -0
- package/dist/commands/stale-ts-ignore/vue.js +16 -0
- package/dist/commands/stale-ts-ignore/vue.js.map +1 -0
- package/dist/commands/strip-comments/core.js +2 -56
- package/dist/commands/strip-comments/core.js.map +1 -1
- package/dist/commands/todo-report/core.d.ts +16 -0
- package/dist/commands/todo-report/core.js +112 -0
- package/dist/commands/todo-report/core.js.map +1 -0
- package/dist/commands/todo-report/report.d.ts +3 -0
- package/dist/commands/todo-report/report.js +31 -0
- package/dist/commands/todo-report/report.js.map +1 -0
- package/dist/commands/todo-report/run.d.ts +22 -0
- package/dist/commands/todo-report/run.js +41 -0
- package/dist/commands/todo-report/run.js.map +1 -0
- package/dist/commands/todo-report/vue.d.ts +2 -0
- package/dist/commands/todo-report/vue.js +71 -0
- package/dist/commands/todo-report/vue.js.map +1 -0
- package/dist/commands/unused-deps/config-files.d.ts +9 -0
- package/dist/commands/unused-deps/config-files.js +64 -0
- package/dist/commands/unused-deps/config-files.js.map +1 -0
- package/dist/commands/unused-deps/core.d.ts +25 -0
- package/dist/commands/unused-deps/core.js +121 -0
- package/dist/commands/unused-deps/core.js.map +1 -0
- package/dist/commands/unused-deps/disk-resolve.d.ts +11 -0
- package/dist/commands/unused-deps/disk-resolve.js +40 -0
- package/dist/commands/unused-deps/disk-resolve.js.map +1 -0
- package/dist/commands/unused-deps/package-name.d.ts +8 -0
- package/dist/commands/unused-deps/package-name.js +20 -0
- package/dist/commands/unused-deps/package-name.js.map +1 -0
- package/dist/commands/unused-deps/report.d.ts +3 -0
- package/dist/commands/unused-deps/report.js +32 -0
- package/dist/commands/unused-deps/report.js.map +1 -0
- package/dist/commands/unused-deps/run.d.ts +18 -0
- package/dist/commands/unused-deps/run.js +68 -0
- package/dist/commands/unused-deps/run.js.map +1 -0
- package/dist/index.d.ts +54 -5
- package/dist/index.js +35 -3
- package/dist/index.js.map +1 -1
- package/dist/utils/escape-regexp.d.ts +1 -0
- package/dist/utils/escape-regexp.js +4 -0
- package/dist/utils/escape-regexp.js.map +1 -0
- package/dist/utils/find-comments.d.ts +10 -0
- package/dist/utils/find-comments.js +62 -0
- package/dist/utils/find-comments.js.map +1 -0
- package/dist/utils/find-package-dir.d.ts +2 -0
- package/dist/utils/find-package-dir.js +15 -0
- package/dist/utils/find-package-dir.js.map +1 -0
- package/dist/utils/line-of.d.ts +2 -0
- package/dist/utils/line-of.js +12 -0
- package/dist/utils/line-of.js.map +1 -0
- package/dist/utils/load-tsconfig.d.ts +20 -0
- package/dist/utils/load-tsconfig.js +33 -0
- package/dist/utils/load-tsconfig.js.map +1 -0
- package/dist/utils/parse-module.d.ts +32 -0
- package/dist/utils/parse-module.js +178 -0
- package/dist/utils/parse-module.js.map +1 -0
- package/dist/utils/resolve-specifier.d.ts +15 -0
- package/dist/utils/resolve-specifier.js +63 -0
- package/dist/utils/resolve-specifier.js.map +1 -0
- package/dist/utils/walk.js +20 -1
- package/dist/utils/walk.js.map +1 -1
- package/dist/utils/workspace.d.ts +14 -0
- package/dist/utils/workspace.js +101 -0
- package/dist/utils/workspace.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,23 @@ looking for is never touched.
|
|
|
37
37
|
symbol is really declared, and auto-detects a pnpm/npm/yarn workspace
|
|
38
38
|
so a sibling package importing your export doesn't read as dead either.
|
|
39
39
|
`--strict` checks the entry points too, for when you actually want that.
|
|
40
|
+
- **`unused-deps`** — finds `package.json` dependencies nothing imports,
|
|
41
|
+
and the reverse: a package genuinely imported but never declared (a
|
|
42
|
+
"phantom" dependency, working only because something else hoisted it
|
|
43
|
+
into `node_modules`). Knows about the usual ways a dependency is used
|
|
44
|
+
without ever being imported — invoked from `scripts`/`lint-staged` by
|
|
45
|
+
its bin name, referenced as a string or bare key in a config file
|
|
46
|
+
(`vitest.config.ts`'s `environment: 'happy-dom'`,
|
|
47
|
+
`postcss.config.js`'s `plugins: { autoprefixer: {} }`) — plus a small,
|
|
48
|
+
explicit list of packages no static analysis could ever catch
|
|
49
|
+
(`@types/*`, `typescript`, `@vitest/coverage-*`, `postcss`/`sass`).
|
|
50
|
+
`--strict` checks that last list too, for a manual audit.
|
|
51
|
+
- **`circular-imports`** — finds import cycles (`A → B → … → A`) in your
|
|
52
|
+
own code — the kind that can silently produce `undefined` at module
|
|
53
|
+
load time in ESM. Reports every cycle found, not just the first, as
|
|
54
|
+
its full chain. A cycle made entirely of `import type` is harmless at
|
|
55
|
+
runtime (types are erased) and hidden by default — `--include-types`
|
|
56
|
+
shows those too, clearly marked.
|
|
40
57
|
- **`case-check`** — finds imports whose case doesn't match the real
|
|
41
58
|
file on disk. Windows and macOS are case-insensitive by default, so
|
|
42
59
|
`import './foo'` against a real `Foo.ts` works fine right up until it
|
|
@@ -64,12 +81,71 @@ looking for is never touched.
|
|
|
64
81
|
dropped rather than reported as noise (see `--help`/features.md for
|
|
65
82
|
exactly which). `--lang` opts into `tsx`/`js`/`jsx` (default: `ts`
|
|
66
83
|
only — `tsx` needs the package to actually configure JSX itself).
|
|
84
|
+
- **`empty-catch`** — finds `catch` blocks that do nothing with the
|
|
85
|
+
error, or do so little it's effectively swallowed: fully empty, or a
|
|
86
|
+
body that's nothing but `console.*` calls with no `throw`, no write to
|
|
87
|
+
an outer-scope variable, no meaningful `return`. Syntactically valid
|
|
88
|
+
code that ordinary lint rules can't catch — needs a semantic check, not
|
|
89
|
+
a syntactic one. A comment inside the block (found via a real token
|
|
90
|
+
scan, not text matching) exempts the finding, same principle as
|
|
91
|
+
ESLint's `no-empty` for documented empty blocks. Handles `.vue` files.
|
|
92
|
+
- **`todo-report`** — summarizes `TODO`/`FIXME`/`HACK` comments across
|
|
93
|
+
the project — file:line plus the note's actual text, including one
|
|
94
|
+
wrapped across several `//` lines or a multi-line `/* */` block, joined
|
|
95
|
+
back into one readable line. `--tags` configures the list (default
|
|
96
|
+
`TODO,FIXME,HACK`); `--max <n>` turns the default "fail on any finding"
|
|
97
|
+
into a ratchet for a project that's knowingly living with existing
|
|
98
|
+
debt. Handles `.vue` files, including `<!-- -->` template comments.
|
|
99
|
+
- **`scripts-check`** — cross-checks `package.json`'s `scripts` against
|
|
100
|
+
README/docs and `.github/workflows/*.yml`: a mentioned-but-undeclared
|
|
101
|
+
script (`npm run X`, `npm test`/`start`, `yarn`/`pnpm run X`) is a
|
|
102
|
+
real broken link — renamed a script, forgot to update the docs or CI —
|
|
103
|
+
and, as a weaker signal, a declared script nothing documents. npm's
|
|
104
|
+
own reserved lifecycle names (`prepublishOnly`,
|
|
105
|
+
`preinstall`, `postinstall`, `prepare`, any `pre*`/`post*` for another
|
|
106
|
+
declared script) are exempt from the second direction — npm calls
|
|
107
|
+
those itself. A CI step under its own `working-directory:` (a `demo/`
|
|
108
|
+
subproject, say) is understood to belong to a different `package.json`
|
|
109
|
+
entirely, not flagged against this one's.
|
|
110
|
+
- **`orphan-tests`** — finds test files whose source disappeared —
|
|
111
|
+
renamed or deleted, the test still green, testing nothing real
|
|
112
|
+
anymore. Reliable only under a simple naming convention: co-located
|
|
113
|
+
(`Foo.test.ts` next to `Foo.ts`, including a sibling `__tests__`/`tests`
|
|
114
|
+
directory one level down), or an explicit `--source-dir`/`--test-dir`
|
|
115
|
+
mirror. A scenario/integration test with no single corresponding source
|
|
116
|
+
file is a known false positive under that strict rule — `--ignore`
|
|
117
|
+
is the documented way to exclude it, not something this command tries
|
|
118
|
+
to guess.
|
|
119
|
+
- **`stale-ts-ignore`** — finds a `// @ts-ignore` that no longer
|
|
120
|
+
suppresses anything: the code below it was fixed, the comment wasn't
|
|
121
|
+
removed. There's no compiler API for "was this specific directive
|
|
122
|
+
needed" — this runs a real project-wide typecheck twice (once as-is,
|
|
123
|
+
once with every directive masked out) and compares the delta at each
|
|
124
|
+
directive's own line, so it's the most expensive command here, and
|
|
125
|
+
says so up front when there's real work to do. Skips the expensive
|
|
126
|
+
part entirely when there's not a single `@ts-ignore` in the project.
|
|
127
|
+
`.vue` files get an isolated per-file check (same technique as
|
|
128
|
+
`readme-check`'s own virtual-file typechecking) — a plain
|
|
129
|
+
`ts.Program` can't include `.vue` in a whole-project run at all.
|
|
130
|
+
- **`full-check`** — runs all twelve other commands in one sweep, each in
|
|
131
|
+
its own safe read-only mode — the three that can write to disk
|
|
132
|
+
(`strip-comments`/`console-strip`/`case-check`) are always called as a
|
|
133
|
+
preview, `-y`/`--fix` are never passed. One summary table: which
|
|
134
|
+
commands are clean, which found something, which errored. Since
|
|
135
|
+
`stale-ts-ignore` is the one command that can make a bare `full-check`
|
|
136
|
+
noticeably slower, running it with no flags in a real terminal asks
|
|
137
|
+
first — `--skip stale-ts-ignore` skips both the question and the
|
|
138
|
+
command, `--no-prompt` runs everything without asking (the default
|
|
139
|
+
answer either way).
|
|
67
140
|
|
|
68
141
|
`strip-comments`, `console-strip`, and `case-check` share the same safety
|
|
69
142
|
model: `--dry-run` (or just running with neither `--dry-run` nor `-y`)
|
|
70
143
|
only previews, `-y`/`--yes` is required to actually write anything,
|
|
71
|
-
`--diff` shows a real unified diff per file. `dead-exports
|
|
72
|
-
|
|
144
|
+
`--diff` shows a real unified diff per file. `dead-exports`,
|
|
145
|
+
`unused-deps`, `circular-imports`, `exports-doctor`, `readme-check`,
|
|
146
|
+
`empty-catch`, `todo-report`, `scripts-check`, `orphan-tests`,
|
|
147
|
+
`stale-ts-ignore`, and `full-check` are all read-only — none of them ever
|
|
148
|
+
write anything, there's nothing to preview or apply. Every command
|
|
73
149
|
supports `--json` for machine-readable output.
|
|
74
150
|
|
|
75
151
|
## Tone
|
|
@@ -109,6 +185,11 @@ devtoolz console-strip src -y
|
|
|
109
185
|
|
|
110
186
|
devtoolz dead-exports src # report-only, nothing to apply
|
|
111
187
|
|
|
188
|
+
devtoolz unused-deps # package.json deps vs what's actually imported
|
|
189
|
+
devtoolz unused-deps path/to/package --strict # also check @types/*, typescript, etc.
|
|
190
|
+
|
|
191
|
+
devtoolz circular-imports src # find import cycles (A -> B -> ... -> A)
|
|
192
|
+
|
|
112
193
|
devtoolz case-check src --fix --diff --dry-run # preview a case fix
|
|
113
194
|
devtoolz case-check src --fix -y # apply it
|
|
114
195
|
|
|
@@ -117,6 +198,20 @@ devtoolz exports-doctor path/to/package # or a specific package director
|
|
|
117
198
|
|
|
118
199
|
devtoolz readme-check # typecheck ts blocks in ./README.md
|
|
119
200
|
devtoolz readme-check --file docs/guide.md # check another doc instead/as well
|
|
201
|
+
|
|
202
|
+
devtoolz empty-catch src # find catch blocks that swallow the error
|
|
203
|
+
|
|
204
|
+
devtoolz todo-report src # summarize TODO/FIXME/HACK comments
|
|
205
|
+
devtoolz todo-report src --max 20 # ratchet: fail only once findings exceed 20
|
|
206
|
+
|
|
207
|
+
devtoolz scripts-check # package.json scripts vs README/CI mentions
|
|
208
|
+
|
|
209
|
+
devtoolz orphan-tests src # test files whose source disappeared
|
|
210
|
+
|
|
211
|
+
devtoolz stale-ts-ignore # find @ts-ignore comments suppressing nothing
|
|
212
|
+
|
|
213
|
+
devtoolz full-check # run every command, one summary table
|
|
214
|
+
devtoolz full-check --skip stale-ts-ignore # same, minus the expensive one
|
|
120
215
|
```
|
|
121
216
|
|
|
122
217
|
Every command has built-in `--help` — `devtoolz --help` lists every
|
|
@@ -175,6 +270,33 @@ Scanned 3 files.
|
|
|
175
270
|
src/types.ts:1 Options (type)
|
|
176
271
|
```
|
|
177
272
|
|
|
273
|
+
`devtoolz unused-deps` against a package with one truly unused dependency
|
|
274
|
+
and one phantom (imported but never declared):
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
🧰 devtoolz, reporting for duty
|
|
278
|
+
|
|
279
|
+
Scanned 1 file.
|
|
280
|
+
|
|
281
|
+
2 problems found:
|
|
282
|
+
phantom dotenv resolves from C:\tmp\demo-pkg\node_modules\dotenv — not declared in package.json
|
|
283
|
+
unused left-pad (dependencies, not imported anywhere)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`devtoolz circular-imports src` against two files that import each other:
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
🧰 devtoolz — chores, automated
|
|
290
|
+
|
|
291
|
+
Scanned 2 files.
|
|
292
|
+
|
|
293
|
+
1 circular import found:
|
|
294
|
+
|
|
295
|
+
src/order.ts
|
|
296
|
+
→ src/user.ts
|
|
297
|
+
→ src/order.ts
|
|
298
|
+
```
|
|
299
|
+
|
|
178
300
|
`devtoolz case-check src --fix --diff --dry-run` after a file got renamed
|
|
179
301
|
`Helper.ts` → `helper.ts` on someone's Mac, with the import never updated:
|
|
180
302
|
|
|
@@ -221,6 +343,79 @@ Typechecked 1 code block across 1 file.
|
|
|
221
343
|
README.md:6:7 ts Type 'string' is not assignable to type 'number'.
|
|
222
344
|
```
|
|
223
345
|
|
|
346
|
+
`devtoolz empty-catch src` against a `catch` block that only logs the
|
|
347
|
+
error and never rethrows or handles it:
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
Scanned 1 file.
|
|
351
|
+
|
|
352
|
+
1 problem found:
|
|
353
|
+
src/example.ts:4:5 console-only only logged, never handled — silently swallowed either way
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`devtoolz todo-report src` against a `TODO` wrapped across several `//`
|
|
357
|
+
lines — joined back into one readable note, not cut off mid-sentence:
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
Scanned 1 file.
|
|
361
|
+
|
|
362
|
+
1 comment found (TODO: 1):
|
|
363
|
+
src/example.ts:9:4 TODO TODO: replace the hardcoded timeout below with a value read from config once loadConfig() above actually works end to end
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
`devtoolz scripts-check` against a package whose README has a typo'd
|
|
367
|
+
script name, and two real scripts nothing documents:
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
Checked 1 source against package.json's scripts.
|
|
371
|
+
|
|
372
|
+
3 problems found:
|
|
373
|
+
README.md:4 buld mentioned here, but not in package.json scripts
|
|
374
|
+
package.json:4 build in package.json scripts, but not mentioned anywhere checked
|
|
375
|
+
package.json:6 deploy in package.json scripts, but not mentioned anywhere checked
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
`devtoolz orphan-tests src` after `parseQuery.ts` was renamed/removed but
|
|
379
|
+
its test survived:
|
|
380
|
+
|
|
381
|
+
```
|
|
382
|
+
Scanned 3 files.
|
|
383
|
+
|
|
384
|
+
1 orphan test found:
|
|
385
|
+
src/parseQuery.test.ts orphan no matching source found (tried .ts, .tsx, .js, .jsx, .mjs, .cjs, .vue)
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
`devtoolz stale-ts-ignore` against a `@ts-ignore` above a line that
|
|
389
|
+
typechecks cleanly on its own — the code was fixed, the comment wasn't:
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
Checked 1 @ts-ignore directive.
|
|
393
|
+
|
|
394
|
+
1 stale @ts-ignore found:
|
|
395
|
+
src/example.ts:2 @ts-ignore doesn't suppress anything — the line below it typechecks cleanly without it
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
`devtoolz full-check` against a small package with a few real, unrelated
|
|
399
|
+
problems scattered across it:
|
|
400
|
+
|
|
401
|
+
```
|
|
402
|
+
✖ strip-comments 1 found
|
|
403
|
+
✔ console-strip clean
|
|
404
|
+
✖ dead-exports 1 found
|
|
405
|
+
✔ case-check clean
|
|
406
|
+
✔ exports-doctor clean
|
|
407
|
+
✔ readme-check clean
|
|
408
|
+
✔ unused-deps clean
|
|
409
|
+
✔ circular-imports clean
|
|
410
|
+
✖ empty-catch 1 found
|
|
411
|
+
✔ todo-report clean
|
|
412
|
+
✖ scripts-check 3 found
|
|
413
|
+
✔ orphan-tests clean
|
|
414
|
+
✔ stale-ts-ignore clean
|
|
415
|
+
|
|
416
|
+
9 clean, 4 found something — see `devtoolz <command> --help` for full detail on any of them.
|
|
417
|
+
```
|
|
418
|
+
|
|
224
419
|
## Development
|
|
225
420
|
|
|
226
421
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
4
5
|
import { runStripComments } from './commands/strip-comments/run.js';
|
|
5
6
|
import { renderStripCommentsReport } from './commands/strip-comments/report.js';
|
|
6
7
|
import { runConsoleStrip } from './commands/console-strip/run.js';
|
|
7
8
|
import { renderConsoleStripReport } from './commands/console-strip/report.js';
|
|
8
9
|
import { runDeadExports } from './commands/dead-exports/run.js';
|
|
9
10
|
import { renderDeadExportsReport } from './commands/dead-exports/report.js';
|
|
11
|
+
import { runCircularImports } from './commands/circular-imports/run.js';
|
|
12
|
+
import { renderCircularImportsReport } from './commands/circular-imports/report.js';
|
|
13
|
+
import { runUnusedDeps } from './commands/unused-deps/run.js';
|
|
14
|
+
import { renderUnusedDepsReport } from './commands/unused-deps/report.js';
|
|
10
15
|
import { runCaseCheck } from './commands/case-check/run.js';
|
|
11
16
|
import { renderCaseCheckReport } from './commands/case-check/report.js';
|
|
12
17
|
import { runExportsDoctor } from './commands/exports-doctor/run.js';
|
|
13
18
|
import { renderExportsDoctorReport } from './commands/exports-doctor/report.js';
|
|
14
19
|
import { runReadmeCheck } from './commands/readme-check/run.js';
|
|
15
20
|
import { renderReadmeCheckReport } from './commands/readme-check/report.js';
|
|
21
|
+
import { runEmptyCatch } from './commands/empty-catch/run.js';
|
|
22
|
+
import { renderEmptyCatchReport } from './commands/empty-catch/report.js';
|
|
23
|
+
import { runTodoReport } from './commands/todo-report/run.js';
|
|
24
|
+
import { renderTodoReportReport } from './commands/todo-report/report.js';
|
|
25
|
+
import { DEFAULT_TAGS } from './commands/todo-report/core.js';
|
|
26
|
+
import { runScriptsCheck } from './commands/scripts-check/run.js';
|
|
27
|
+
import { renderScriptsCheckReport } from './commands/scripts-check/report.js';
|
|
28
|
+
import { runOrphanTests } from './commands/orphan-tests/run.js';
|
|
29
|
+
import { renderOrphanTestsReport } from './commands/orphan-tests/report.js';
|
|
30
|
+
import { runStaleTsIgnore } from './commands/stale-ts-ignore/run.js';
|
|
31
|
+
import { renderStaleTsIgnoreReport } from './commands/stale-ts-ignore/report.js';
|
|
32
|
+
import { runFullCheck, FULL_CHECK_COMMAND_NAMES, SLOW_FULL_CHECK_COMMAND, } from './commands/full-check/run.js';
|
|
33
|
+
import { renderFullCheckReport } from './commands/full-check/report.js';
|
|
16
34
|
function wrapText(text, width) {
|
|
17
35
|
const words = text.split(' ');
|
|
18
36
|
const lines = [];
|
|
@@ -241,6 +259,71 @@ program
|
|
|
241
259
|
}
|
|
242
260
|
process.exitCode = report.exitCode;
|
|
243
261
|
});
|
|
262
|
+
program
|
|
263
|
+
.command('circular-imports')
|
|
264
|
+
.description('Find import cycles in the local module graph (A -> B -> ... -> A)')
|
|
265
|
+
.argument('[paths...]', 'files/directories to process', [])
|
|
266
|
+
.option('--cwd <path>', 'root paths are resolved against', process.cwd())
|
|
267
|
+
.option('--ext <list>', 'comma-separated extensions to include', '.ts,.tsx,.js,.jsx,.cjs,.mjs')
|
|
268
|
+
.option('--ignore <glob>', 'extra ignore pattern (repeatable), on top of the built-in defaults', (val, prev) => [...prev, val], [])
|
|
269
|
+
.option('--no-respect-gitignore', "don't also honor the project's .gitignore")
|
|
270
|
+
.option('--include-types', 'also report cycles made entirely of `import type` edges (harmless at runtime by default, so hidden)', false)
|
|
271
|
+
.option('--json', 'machine-readable output', false)
|
|
272
|
+
.option('--quiet', 'suppress output when there is nothing to report', false)
|
|
273
|
+
.option('--plain', 'disable color/banner/celebration copy, even in a real terminal', false)
|
|
274
|
+
.action((paths, options) => {
|
|
275
|
+
const report = runCircularImports({
|
|
276
|
+
paths,
|
|
277
|
+
cwd: resolve(options.cwd),
|
|
278
|
+
extensions: options.ext.split(',').map((e) => e.trim()),
|
|
279
|
+
ignoreGlobs: options.ignore,
|
|
280
|
+
respectGitignore: options.respectGitignore,
|
|
281
|
+
includeTypes: options.includeTypes,
|
|
282
|
+
});
|
|
283
|
+
if (options.json) {
|
|
284
|
+
console.log(JSON.stringify(report, null, 2));
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
const text = renderCircularImportsReport(report, {
|
|
288
|
+
quiet: options.quiet,
|
|
289
|
+
plain: options.plain,
|
|
290
|
+
});
|
|
291
|
+
if (text)
|
|
292
|
+
console.log(text);
|
|
293
|
+
}
|
|
294
|
+
process.exitCode = report.exitCode;
|
|
295
|
+
});
|
|
296
|
+
program
|
|
297
|
+
.command('unused-deps')
|
|
298
|
+
.description('Find package.json dependencies nothing imports, and imports of packages package.json never declared')
|
|
299
|
+
.argument('[dir]', 'package directory to check — also the scan root', '.')
|
|
300
|
+
.option('--ext <list>', 'comma-separated extensions to include', '.ts,.tsx,.js,.jsx,.cjs,.mjs')
|
|
301
|
+
.option('--ignore <glob>', 'extra ignore pattern (repeatable), on top of the built-in defaults', (val, prev) => [...prev, val], [])
|
|
302
|
+
.option('--no-respect-gitignore', "don't also honor the project's .gitignore")
|
|
303
|
+
.option('--strict', 'also check packages that are structurally undetectable as "used" (currently: @types/*)', false)
|
|
304
|
+
.option('--ignore-package <name>', 'exempt a specific dependency from the unused/phantom check (repeatable)', (val, prev) => [...prev, val], [])
|
|
305
|
+
.option('--json', 'machine-readable output', false)
|
|
306
|
+
.option('--quiet', 'suppress output when there is nothing to report', false)
|
|
307
|
+
.option('--plain', 'disable color/banner/celebration copy, even in a real terminal', false)
|
|
308
|
+
.action((dir, options) => {
|
|
309
|
+
const report = runUnusedDeps({
|
|
310
|
+
dir: resolve(dir),
|
|
311
|
+
extensions: options.ext.split(',').map((e) => e.trim()),
|
|
312
|
+
ignoreGlobs: options.ignore,
|
|
313
|
+
respectGitignore: options.respectGitignore,
|
|
314
|
+
strict: options.strict,
|
|
315
|
+
ignorePackages: options.ignorePackage,
|
|
316
|
+
});
|
|
317
|
+
if (options.json) {
|
|
318
|
+
console.log(JSON.stringify(report, null, 2));
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
const text = renderUnusedDepsReport(report, { quiet: options.quiet, plain: options.plain });
|
|
322
|
+
if (text)
|
|
323
|
+
console.log(text);
|
|
324
|
+
}
|
|
325
|
+
process.exitCode = report.exitCode;
|
|
326
|
+
});
|
|
244
327
|
program
|
|
245
328
|
.command('case-check')
|
|
246
329
|
.description("Find imports whose case doesn't match the real file on disk — works on Windows/Mac, breaks on Linux CI")
|
|
@@ -326,5 +409,236 @@ program
|
|
|
326
409
|
}
|
|
327
410
|
process.exitCode = report.exitCode;
|
|
328
411
|
});
|
|
412
|
+
program
|
|
413
|
+
.command('empty-catch')
|
|
414
|
+
.description('Find catch blocks that do nothing with the error, or only log it — both silently swallow it')
|
|
415
|
+
.argument('[paths...]', 'files/directories to process', [])
|
|
416
|
+
.option('--cwd <path>', 'root paths are resolved against', process.cwd())
|
|
417
|
+
.option('--ext <list>', 'comma-separated extensions to include', '.ts,.tsx,.js,.jsx,.cjs,.mjs,.vue')
|
|
418
|
+
.option('--ignore <glob>', 'extra ignore pattern (repeatable), on top of the built-in defaults', (val, prev) => [...prev, val], [])
|
|
419
|
+
.option('--no-respect-gitignore', "don't also honor the project's .gitignore")
|
|
420
|
+
.option('--json', 'machine-readable output', false)
|
|
421
|
+
.option('--quiet', 'suppress output when there is nothing to report', false)
|
|
422
|
+
.option('--plain', 'disable color/banner/celebration copy, even in a real terminal', false)
|
|
423
|
+
.action((paths, options) => {
|
|
424
|
+
const report = runEmptyCatch({
|
|
425
|
+
paths,
|
|
426
|
+
cwd: resolve(options.cwd),
|
|
427
|
+
extensions: options.ext.split(',').map((e) => e.trim()),
|
|
428
|
+
ignoreGlobs: options.ignore,
|
|
429
|
+
respectGitignore: options.respectGitignore,
|
|
430
|
+
});
|
|
431
|
+
if (options.json) {
|
|
432
|
+
console.log(JSON.stringify(report, null, 2));
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
const text = renderEmptyCatchReport(report, { quiet: options.quiet, plain: options.plain });
|
|
436
|
+
if (text)
|
|
437
|
+
console.log(text);
|
|
438
|
+
}
|
|
439
|
+
process.exitCode = report.exitCode;
|
|
440
|
+
});
|
|
441
|
+
program
|
|
442
|
+
.command('todo-report')
|
|
443
|
+
.description('Summarize TODO/FIXME/HACK comments across the project — file:line + the text itself')
|
|
444
|
+
.argument('[paths...]', 'files/directories to process', [])
|
|
445
|
+
.option('--cwd <path>', 'root paths are resolved against', process.cwd())
|
|
446
|
+
.option('--ext <list>', 'comma-separated extensions to include', '.ts,.tsx,.js,.jsx,.cjs,.mjs,.vue')
|
|
447
|
+
.option('--ignore <glob>', 'extra ignore pattern (repeatable), on top of the built-in defaults', (val, prev) => [...prev, val], [])
|
|
448
|
+
.option('--no-respect-gitignore', "don't also honor the project's .gitignore")
|
|
449
|
+
.option('--tags <list>', 'comma-separated tags to look for', DEFAULT_TAGS.join(','))
|
|
450
|
+
.option('--max <n>', "don't fail unless findings exceed this count (a ratchet, not a hard zero)", '0')
|
|
451
|
+
.option('--json', 'machine-readable output', false)
|
|
452
|
+
.option('--quiet', 'suppress output when there is nothing to report', false)
|
|
453
|
+
.option('--plain', 'disable color/banner/celebration copy, even in a real terminal', false)
|
|
454
|
+
.action((paths, options) => {
|
|
455
|
+
const report = runTodoReport({
|
|
456
|
+
paths,
|
|
457
|
+
cwd: resolve(options.cwd),
|
|
458
|
+
extensions: options.ext.split(',').map((e) => e.trim()),
|
|
459
|
+
ignoreGlobs: options.ignore,
|
|
460
|
+
respectGitignore: options.respectGitignore,
|
|
461
|
+
tags: options.tags.split(',').map((t) => t.trim()),
|
|
462
|
+
max: Number(options.max),
|
|
463
|
+
});
|
|
464
|
+
if (options.json) {
|
|
465
|
+
console.log(JSON.stringify(report, null, 2));
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
const text = renderTodoReportReport(report, { quiet: options.quiet, plain: options.plain });
|
|
469
|
+
if (text)
|
|
470
|
+
console.log(text);
|
|
471
|
+
}
|
|
472
|
+
process.exitCode = report.exitCode;
|
|
473
|
+
});
|
|
474
|
+
program
|
|
475
|
+
.command('scripts-check')
|
|
476
|
+
.description("Cross-checks package.json's scripts against README/docs and .github/workflows — a mention of a script that doesn't exist, and (weaker signal) a script nothing documents")
|
|
477
|
+
.argument('[dir]', 'package directory to check', '.')
|
|
478
|
+
.option('--file <path>', 'markdown/doc file to check, relative to [dir] (repeatable) — default: README.md', (val, prev) => [...prev, val], [])
|
|
479
|
+
.option('--json', 'machine-readable output', false)
|
|
480
|
+
.option('--quiet', 'suppress output when there is nothing to report', false)
|
|
481
|
+
.option('--plain', 'disable color/banner/celebration copy, even in a real terminal', false)
|
|
482
|
+
.action((dir, options) => {
|
|
483
|
+
const report = runScriptsCheck({ dir: resolve(dir), files: options.file });
|
|
484
|
+
if (options.json) {
|
|
485
|
+
console.log(JSON.stringify(report, null, 2));
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
const text = renderScriptsCheckReport(report, {
|
|
489
|
+
quiet: options.quiet,
|
|
490
|
+
plain: options.plain,
|
|
491
|
+
});
|
|
492
|
+
if (text)
|
|
493
|
+
console.log(text);
|
|
494
|
+
}
|
|
495
|
+
process.exitCode = report.exitCode;
|
|
496
|
+
});
|
|
497
|
+
program
|
|
498
|
+
.command('orphan-tests')
|
|
499
|
+
.description('Finds test files whose source disappeared — renamed or deleted, test still green, testing nothing real anymore. Reliable only under a co-located (X.test.ts next to X.ts) or --source-dir/--test-dir mirrored naming convention')
|
|
500
|
+
.argument('[paths...]', 'files/directories to process (co-located mode only)', [])
|
|
501
|
+
.option('--cwd <path>', 'root paths are resolved against', process.cwd())
|
|
502
|
+
.option('--ext <list>', 'comma-separated extensions to consider as possible test files', '.ts,.tsx,.js,.jsx,.mjs,.cjs')
|
|
503
|
+
.option('--ignore <glob>', 'extra ignore pattern (repeatable), on top of the built-in defaults', (val, prev) => [...prev, val], [])
|
|
504
|
+
.option('--no-respect-gitignore', "don't also honor the project's .gitignore")
|
|
505
|
+
.option('--test-suffix <list>', 'comma-separated suffixes that mark a test file', '.test,.spec')
|
|
506
|
+
.option('--source-ext <list>', 'comma-separated extensions tried for a matching source file', '.ts,.tsx,.js,.jsx,.mjs,.cjs,.vue')
|
|
507
|
+
.option('--source-dir <path>', 'mirrored layout: source root, relative to --cwd (must be given together with --test-dir)')
|
|
508
|
+
.option('--test-dir <path>', 'mirrored layout: test root, relative to --cwd (must be given together with --source-dir)')
|
|
509
|
+
.option('--json', 'machine-readable output', false)
|
|
510
|
+
.option('--quiet', 'suppress output when there is nothing to report', false)
|
|
511
|
+
.option('--plain', 'disable color/banner/celebration copy, even in a real terminal', false)
|
|
512
|
+
.action((paths, options) => {
|
|
513
|
+
const report = runOrphanTests({
|
|
514
|
+
paths,
|
|
515
|
+
cwd: resolve(options.cwd),
|
|
516
|
+
extensions: options.ext.split(',').map((e) => e.trim()),
|
|
517
|
+
ignoreGlobs: options.ignore,
|
|
518
|
+
respectGitignore: options.respectGitignore,
|
|
519
|
+
testSuffixes: options.testSuffix.split(',').map((s) => s.trim()),
|
|
520
|
+
sourceExtensions: options.sourceExt.split(',').map((e) => e.trim()),
|
|
521
|
+
...(options.sourceDir ? { sourceDir: options.sourceDir } : {}),
|
|
522
|
+
...(options.testDir ? { testDir: options.testDir } : {}),
|
|
523
|
+
});
|
|
524
|
+
if (options.json) {
|
|
525
|
+
console.log(JSON.stringify(report, null, 2));
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
const text = renderOrphanTestsReport(report, { quiet: options.quiet, plain: options.plain });
|
|
529
|
+
if (text)
|
|
530
|
+
console.log(text);
|
|
531
|
+
}
|
|
532
|
+
process.exitCode = report.exitCode;
|
|
533
|
+
});
|
|
534
|
+
program
|
|
535
|
+
.command('stale-ts-ignore')
|
|
536
|
+
.description("Finds a `// @ts-ignore` that no longer suppresses anything — the code below it was fixed, the comment wasn't removed. Runs a full project typecheck twice, so it's the most expensive command here")
|
|
537
|
+
.argument('[dir]', 'package directory to check', '.')
|
|
538
|
+
.option('--tsconfig <path>', 'tsconfig.json to read compiler options from (auto-detected by default)')
|
|
539
|
+
.option('--ignore <glob>', 'extra ignore pattern (repeatable), on top of the built-in defaults — applies to .vue file discovery only', (val, prev) => [...prev, val], [])
|
|
540
|
+
.option('--no-respect-gitignore', "don't also honor the project's .gitignore for .vue discovery")
|
|
541
|
+
.option('--json', 'machine-readable output', false)
|
|
542
|
+
.option('--quiet', 'suppress output when there is nothing to report', false)
|
|
543
|
+
.option('--plain', 'disable color/banner/celebration copy, even in a real terminal', false)
|
|
544
|
+
.action((dir, options) => {
|
|
545
|
+
const report = runStaleTsIgnore({
|
|
546
|
+
dir: resolve(dir),
|
|
547
|
+
...(options.tsconfig ? { tsconfig: options.tsconfig } : {}),
|
|
548
|
+
ignoreGlobs: options.ignore,
|
|
549
|
+
respectGitignore: options.respectGitignore,
|
|
550
|
+
...(options.quiet ? {} : { onProgress: (message) => console.error(message) }),
|
|
551
|
+
});
|
|
552
|
+
if (options.json) {
|
|
553
|
+
console.log(JSON.stringify(report, null, 2));
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
const text = renderStaleTsIgnoreReport(report, {
|
|
557
|
+
quiet: options.quiet,
|
|
558
|
+
plain: options.plain,
|
|
559
|
+
});
|
|
560
|
+
if (text)
|
|
561
|
+
console.log(text);
|
|
562
|
+
}
|
|
563
|
+
process.exitCode = report.exitCode;
|
|
564
|
+
});
|
|
565
|
+
// Only `full-check` ever prompts — every other command here is a single,
|
|
566
|
+
// fast, non-interactive check. `stale-ts-ignore` is the one command a
|
|
567
|
+
// bare `full-check` can turn into a genuinely slow run, so this asks
|
|
568
|
+
// about that ONE command specifically, not a general "run everything?"
|
|
569
|
+
// confirmation. Defaults to running it (Enter alone picks option 1) —
|
|
570
|
+
// matches what happens when there's no TTY to ask at all (see
|
|
571
|
+
// `shouldPrompt` below), so a script and an interactive "just press
|
|
572
|
+
// enter" run behave the same way.
|
|
573
|
+
// Deliberately a SINGLE `question()` call, no retry-on-invalid-input loop
|
|
574
|
+
// — found via real testing that a second `question()` on the same (or
|
|
575
|
+
// even a fresh) `readline/promises` interface can silently lose the
|
|
576
|
+
// answer and hang forever once more than one line was already available
|
|
577
|
+
// on stdin when the first question resolved (a real, reproducible
|
|
578
|
+
// readline quirk with piped/non-interactive input, not something specific
|
|
579
|
+
// to this code). One question, with an explicit default on anything else
|
|
580
|
+
// typed, sidesteps the whole bug class instead of risking it.
|
|
581
|
+
async function promptStaleTsIgnoreChoice() {
|
|
582
|
+
process.stderr.write('stale-ts-ignore runs a full project typecheck twice — this can take noticeably longer than the other commands on a large project.\n\n' +
|
|
583
|
+
' 1) Run it anyway (full sweep)\n' +
|
|
584
|
+
' 2) Skip it for this run\n\n');
|
|
585
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
586
|
+
let answer;
|
|
587
|
+
try {
|
|
588
|
+
answer = (await rl.question('Choice [1/2, Enter = 1]: ')).trim();
|
|
589
|
+
}
|
|
590
|
+
finally {
|
|
591
|
+
rl.close();
|
|
592
|
+
}
|
|
593
|
+
if (answer === '2')
|
|
594
|
+
return false;
|
|
595
|
+
if (answer !== '' && answer !== '1') {
|
|
596
|
+
process.stderr.write(`(didn't recognize "${answer}" — running it, same as Enter)\n`);
|
|
597
|
+
}
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
program
|
|
601
|
+
.command('full-check')
|
|
602
|
+
.description('Runs every other command in its own safe/read-only mode — a full diagnostic sweep, never applies a fix')
|
|
603
|
+
.argument('[dir]', 'directory every command scans/reads from', '.')
|
|
604
|
+
.option('--skip <command>', `skip a command by name (repeatable) — valid names: ${FULL_CHECK_COMMAND_NAMES.join(', ')}`, (val, prev) => [...prev, val], [])
|
|
605
|
+
.option('--no-prompt', "don't ask about stale-ts-ignore even in an interactive terminal — runs it unless --skip already excludes it")
|
|
606
|
+
.option('--json', 'machine-readable output', false)
|
|
607
|
+
.option('--quiet', 'suppress output when there is nothing to report', false)
|
|
608
|
+
.option('--plain', 'disable color/banner/celebration copy, even in a real terminal', false)
|
|
609
|
+
.action(async (dir, options) => {
|
|
610
|
+
const skip = new Set(options.skip);
|
|
611
|
+
const shouldPrompt = options.prompt &&
|
|
612
|
+
!options.json &&
|
|
613
|
+
!skip.has(SLOW_FULL_CHECK_COMMAND) &&
|
|
614
|
+
Boolean(process.stdin.isTTY) &&
|
|
615
|
+
Boolean(process.stdout.isTTY);
|
|
616
|
+
if (shouldPrompt) {
|
|
617
|
+
const runIt = await promptStaleTsIgnoreChoice();
|
|
618
|
+
if (!runIt)
|
|
619
|
+
skip.add(SLOW_FULL_CHECK_COMMAND);
|
|
620
|
+
process.stderr.write('\n');
|
|
621
|
+
}
|
|
622
|
+
const showProgress = !options.json && !options.quiet;
|
|
623
|
+
const report = runFullCheck({
|
|
624
|
+
dir: resolve(dir),
|
|
625
|
+
skip: [...skip],
|
|
626
|
+
...(showProgress
|
|
627
|
+
? {
|
|
628
|
+
onCommandStart: (command) => process.stderr.write(`Running ${command}...\n`),
|
|
629
|
+
onProgress: (message) => process.stderr.write(`${message}\n`),
|
|
630
|
+
}
|
|
631
|
+
: {}),
|
|
632
|
+
});
|
|
633
|
+
if (options.json) {
|
|
634
|
+
console.log(JSON.stringify(report, null, 2));
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
const text = renderFullCheckReport(report, { quiet: options.quiet, plain: options.plain });
|
|
638
|
+
if (text)
|
|
639
|
+
console.log(text);
|
|
640
|
+
}
|
|
641
|
+
process.exitCode = report.exitCode;
|
|
642
|
+
});
|
|
329
643
|
program.parse(process.argv);
|
|
330
644
|
//# sourceMappingURL=cli.js.map
|