@macrulez/devtoolz 0.1.0 → 0.2.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 (129) hide show
  1. package/README.md +164 -3
  2. package/dist/cli.js +233 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/circular-imports/core.d.ts +15 -0
  5. package/dist/commands/circular-imports/core.js +95 -0
  6. package/dist/commands/circular-imports/core.js.map +1 -0
  7. package/dist/commands/circular-imports/report.d.ts +3 -0
  8. package/dist/commands/circular-imports/report.js +41 -0
  9. package/dist/commands/circular-imports/report.js.map +1 -0
  10. package/dist/commands/circular-imports/run.d.ts +19 -0
  11. package/dist/commands/circular-imports/run.js +29 -0
  12. package/dist/commands/circular-imports/run.js.map +1 -0
  13. package/dist/commands/dead-exports/core.d.ts +1 -1
  14. package/dist/commands/dead-exports/core.js +2 -2
  15. package/dist/commands/dead-exports/core.js.map +1 -1
  16. package/dist/commands/dead-exports/entry.d.ts +0 -1
  17. package/dist/commands/dead-exports/entry.js +1 -14
  18. package/dist/commands/dead-exports/entry.js.map +1 -1
  19. package/dist/commands/dead-exports/run.js +3 -2
  20. package/dist/commands/dead-exports/run.js.map +1 -1
  21. package/dist/commands/empty-catch/core.d.ts +8 -0
  22. package/dist/commands/empty-catch/core.js +61 -0
  23. package/dist/commands/empty-catch/core.js.map +1 -0
  24. package/dist/commands/empty-catch/report.d.ts +3 -0
  25. package/dist/commands/empty-catch/report.js +27 -0
  26. package/dist/commands/empty-catch/report.js.map +1 -0
  27. package/dist/commands/empty-catch/run.d.ts +17 -0
  28. package/dist/commands/empty-catch/run.js +34 -0
  29. package/dist/commands/empty-catch/run.js.map +1 -0
  30. package/dist/commands/empty-catch/vue.d.ts +2 -0
  31. package/dist/commands/empty-catch/vue.js +16 -0
  32. package/dist/commands/empty-catch/vue.js.map +1 -0
  33. package/dist/commands/orphan-tests/core.d.ts +9 -0
  34. package/dist/commands/orphan-tests/core.js +42 -0
  35. package/dist/commands/orphan-tests/core.js.map +1 -0
  36. package/dist/commands/orphan-tests/report.d.ts +3 -0
  37. package/dist/commands/orphan-tests/report.js +30 -0
  38. package/dist/commands/orphan-tests/report.js.map +1 -0
  39. package/dist/commands/orphan-tests/run.d.ts +25 -0
  40. package/dist/commands/orphan-tests/run.js +55 -0
  41. package/dist/commands/orphan-tests/run.js.map +1 -0
  42. package/dist/commands/readme-check/run.js +8 -3
  43. package/dist/commands/readme-check/run.js.map +1 -1
  44. package/dist/commands/scripts-check/core.d.ts +18 -0
  45. package/dist/commands/scripts-check/core.js +121 -0
  46. package/dist/commands/scripts-check/core.js.map +1 -0
  47. package/dist/commands/scripts-check/report.d.ts +3 -0
  48. package/dist/commands/scripts-check/report.js +34 -0
  49. package/dist/commands/scripts-check/report.js.map +1 -0
  50. package/dist/commands/scripts-check/run.d.ts +15 -0
  51. package/dist/commands/scripts-check/run.js +81 -0
  52. package/dist/commands/scripts-check/run.js.map +1 -0
  53. package/dist/commands/stale-ts-ignore/check.d.ts +23 -0
  54. package/dist/commands/stale-ts-ignore/check.js +104 -0
  55. package/dist/commands/stale-ts-ignore/check.js.map +1 -0
  56. package/dist/commands/stale-ts-ignore/core.d.ts +11 -0
  57. package/dist/commands/stale-ts-ignore/core.js +42 -0
  58. package/dist/commands/stale-ts-ignore/core.js.map +1 -0
  59. package/dist/commands/stale-ts-ignore/report.d.ts +3 -0
  60. package/dist/commands/stale-ts-ignore/report.js +30 -0
  61. package/dist/commands/stale-ts-ignore/report.js.map +1 -0
  62. package/dist/commands/stale-ts-ignore/run.d.ts +23 -0
  63. package/dist/commands/stale-ts-ignore/run.js +101 -0
  64. package/dist/commands/stale-ts-ignore/run.js.map +1 -0
  65. package/dist/commands/stale-ts-ignore/vue.d.ts +9 -0
  66. package/dist/commands/stale-ts-ignore/vue.js +16 -0
  67. package/dist/commands/stale-ts-ignore/vue.js.map +1 -0
  68. package/dist/commands/strip-comments/core.js +2 -56
  69. package/dist/commands/strip-comments/core.js.map +1 -1
  70. package/dist/commands/todo-report/core.d.ts +16 -0
  71. package/dist/commands/todo-report/core.js +112 -0
  72. package/dist/commands/todo-report/core.js.map +1 -0
  73. package/dist/commands/todo-report/report.d.ts +3 -0
  74. package/dist/commands/todo-report/report.js +31 -0
  75. package/dist/commands/todo-report/report.js.map +1 -0
  76. package/dist/commands/todo-report/run.d.ts +22 -0
  77. package/dist/commands/todo-report/run.js +41 -0
  78. package/dist/commands/todo-report/run.js.map +1 -0
  79. package/dist/commands/todo-report/vue.d.ts +2 -0
  80. package/dist/commands/todo-report/vue.js +71 -0
  81. package/dist/commands/todo-report/vue.js.map +1 -0
  82. package/dist/commands/unused-deps/config-files.d.ts +9 -0
  83. package/dist/commands/unused-deps/config-files.js +64 -0
  84. package/dist/commands/unused-deps/config-files.js.map +1 -0
  85. package/dist/commands/unused-deps/core.d.ts +25 -0
  86. package/dist/commands/unused-deps/core.js +121 -0
  87. package/dist/commands/unused-deps/core.js.map +1 -0
  88. package/dist/commands/unused-deps/disk-resolve.d.ts +11 -0
  89. package/dist/commands/unused-deps/disk-resolve.js +40 -0
  90. package/dist/commands/unused-deps/disk-resolve.js.map +1 -0
  91. package/dist/commands/unused-deps/package-name.d.ts +8 -0
  92. package/dist/commands/unused-deps/package-name.js +20 -0
  93. package/dist/commands/unused-deps/package-name.js.map +1 -0
  94. package/dist/commands/unused-deps/report.d.ts +3 -0
  95. package/dist/commands/unused-deps/report.js +32 -0
  96. package/dist/commands/unused-deps/report.js.map +1 -0
  97. package/dist/commands/unused-deps/run.d.ts +18 -0
  98. package/dist/commands/unused-deps/run.js +68 -0
  99. package/dist/commands/unused-deps/run.js.map +1 -0
  100. package/dist/index.d.ts +51 -5
  101. package/dist/index.js +33 -3
  102. package/dist/index.js.map +1 -1
  103. package/dist/utils/escape-regexp.d.ts +1 -0
  104. package/dist/utils/escape-regexp.js +4 -0
  105. package/dist/utils/escape-regexp.js.map +1 -0
  106. package/dist/utils/find-comments.d.ts +10 -0
  107. package/dist/utils/find-comments.js +62 -0
  108. package/dist/utils/find-comments.js.map +1 -0
  109. package/dist/utils/find-package-dir.d.ts +2 -0
  110. package/dist/utils/find-package-dir.js +15 -0
  111. package/dist/utils/find-package-dir.js.map +1 -0
  112. package/dist/utils/line-of.d.ts +2 -0
  113. package/dist/utils/line-of.js +12 -0
  114. package/dist/utils/line-of.js.map +1 -0
  115. package/dist/utils/load-tsconfig.d.ts +20 -0
  116. package/dist/utils/load-tsconfig.js +33 -0
  117. package/dist/utils/load-tsconfig.js.map +1 -0
  118. package/dist/utils/parse-module.d.ts +32 -0
  119. package/dist/utils/parse-module.js +178 -0
  120. package/dist/utils/parse-module.js.map +1 -0
  121. package/dist/utils/resolve-specifier.d.ts +15 -0
  122. package/dist/utils/resolve-specifier.js +63 -0
  123. package/dist/utils/resolve-specifier.js.map +1 -0
  124. package/dist/utils/walk.js +20 -1
  125. package/dist/utils/walk.js.map +1 -1
  126. package/dist/utils/workspace.d.ts +14 -0
  127. package/dist/utils/workspace.js +101 -0
  128. package/dist/utils/workspace.js.map +1 -0
  129. package/package.json +1 -1
@@ -0,0 +1,71 @@
1
+ // `.vue` handling, same split as strip-comments/vue.ts: the `<script>`
2
+ // block goes through the real TS-parser path (core.ts), and the
3
+ // `<template>` block's `<!-- -->` comments are matched by regex — no full
4
+ // SFC template parser here, so a `<!-- -->`-looking sequence inside a
5
+ // bound attribute string would be misread as a real comment. Same known,
6
+ // documented limitation as strip-comments.
7
+ import { analyzeTodoReport, buildTagMatcher, collectNoteText, DEFAULT_TAGS, } from './core.js';
8
+ const SCRIPT_RE = /(<script[^>]*>)([\s\S]*?)(<\/script>)/;
9
+ const TEMPLATE_RE = /(<template[^>]*>)([\s\S]*?)(<\/template>)/;
10
+ const TEMPLATE_COMMENT_RE = /<!--[\s\S]*?-->/g;
11
+ function lineAt(text, index) {
12
+ return (text.slice(0, index).match(/\n/g) ?? []).length;
13
+ }
14
+ function cleanTemplateLine(line) {
15
+ return line
16
+ .replace(/^\s*<!--/, '')
17
+ .replace(/-->\s*$/, '')
18
+ .trim();
19
+ }
20
+ function findTemplateTodos(body, tags) {
21
+ const findings = [];
22
+ const { pattern, canonical } = buildTagMatcher(tags);
23
+ for (const match of body.matchAll(TEMPLATE_COMMENT_RE)) {
24
+ const start = match.index;
25
+ const rawLines = match[0].split('\n');
26
+ let lineStart = start;
27
+ let i = 0;
28
+ while (i < rawLines.length) {
29
+ const rawLine = rawLines[i];
30
+ const m = pattern.exec(rawLine);
31
+ if (m) {
32
+ const { text: noteText, consumedThrough } = collectNoteText(rawLines, i, cleanTemplateLine, pattern);
33
+ findings.push({
34
+ line: lineAt(body, lineStart + m.index) + 1,
35
+ column: lineStart + m.index - body.lastIndexOf('\n', lineStart + m.index),
36
+ tag: canonical.get(m[0].toLowerCase()) ?? m[0],
37
+ text: noteText,
38
+ });
39
+ for (let k = i; k < consumedThrough; k++)
40
+ lineStart += rawLines[k].length + 1;
41
+ i = consumedThrough;
42
+ continue;
43
+ }
44
+ lineStart += rawLine.length + 1;
45
+ i++;
46
+ }
47
+ }
48
+ return findings;
49
+ }
50
+ export function analyzeTodoReportVue(text, file, tags = DEFAULT_TAGS) {
51
+ const findings = [];
52
+ const scriptMatch = text.match(SCRIPT_RE);
53
+ if (scriptMatch) {
54
+ const [, , body] = scriptMatch;
55
+ const offset = lineAt(text, scriptMatch.index);
56
+ for (const f of analyzeTodoReport(body, file.replace(/\.vue$/, '.ts'), tags)) {
57
+ findings.push({ ...f, line: f.line + offset });
58
+ }
59
+ }
60
+ const templateMatch = text.match(TEMPLATE_RE);
61
+ if (templateMatch) {
62
+ const [, , body] = templateMatch;
63
+ const offset = lineAt(text, templateMatch.index);
64
+ for (const f of findTemplateTodos(body, tags)) {
65
+ findings.push({ ...f, line: f.line + offset });
66
+ }
67
+ }
68
+ findings.sort((a, b) => a.line - b.line || a.column - b.column);
69
+ return findings;
70
+ }
71
+ //# sourceMappingURL=vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vue.js","sourceRoot":"","sources":["../../../src/commands/todo-report/vue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export declare function readConfigFilesText(packageDir: string): string;
2
+ /**
3
+ * Whether `name` appears as its own token anywhere in `text` — either
4
+ * quoted (`extends: 'stylelint-config-standard'`) or a bare identifier
5
+ * (`plugins: { autoprefixer: {} }`). Bounded on both sides so a
6
+ * different package that merely contains `name` as a substring
7
+ * (`autoprefixer-wrapper`, `@scope/autoprefixer`) doesn't count.
8
+ */
9
+ export declare function textReferencesPackage(text: string, name: string): boolean;
@@ -0,0 +1,64 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { escapeRegExp } from '../../utils/escape-regexp.js';
4
+ // Config files commonly reference a dependency by name — a vitest.config.ts's
5
+ // `environment: 'happy-dom'` (a quoted string), a postcss.config.js's
6
+ // `plugins: { autoprefixer: {} }` (a bare object key), never a JS
7
+ // `import`. No import-statement parse can see either shape; this is a
8
+ // deliberately narrow, explicit-filename text scan instead of guessing
9
+ // at every possible config format. Only the package root is checked
10
+ // (not recursive) — the overwhelming common location for all of these
11
+ // in a real project.
12
+ const CONFIG_FILENAMES = [
13
+ 'vitest.config.ts',
14
+ 'vitest.config.js',
15
+ 'vitest.config.mts',
16
+ 'vitest.config.mjs',
17
+ 'vite.config.ts',
18
+ 'vite.config.js',
19
+ 'vite.config.mts',
20
+ '.eslintrc.json',
21
+ '.eslintrc.js',
22
+ '.eslintrc.cjs',
23
+ 'eslint.config.js',
24
+ 'eslint.config.mjs',
25
+ 'eslint.config.ts',
26
+ '.stylelintrc',
27
+ '.stylelintrc.json',
28
+ '.stylelintrc.js',
29
+ 'stylelint.config.js',
30
+ 'stylelint.config.mjs',
31
+ 'postcss.config.js',
32
+ 'postcss.config.cjs',
33
+ 'postcss.config.mjs',
34
+ 'nuxt.config.ts',
35
+ 'tsconfig.json',
36
+ 'tsconfig.build.json',
37
+ ];
38
+ export function readConfigFilesText(packageDir) {
39
+ const chunks = [];
40
+ for (const filename of CONFIG_FILENAMES) {
41
+ const path = join(packageDir, filename);
42
+ if (!existsSync(path))
43
+ continue;
44
+ try {
45
+ chunks.push(readFileSync(path, 'utf8'));
46
+ }
47
+ catch {
48
+ // unreadable — skip rather than fail the whole command over one config file
49
+ }
50
+ }
51
+ return chunks.join('\n');
52
+ }
53
+ /**
54
+ * Whether `name` appears as its own token anywhere in `text` — either
55
+ * quoted (`extends: 'stylelint-config-standard'`) or a bare identifier
56
+ * (`plugins: { autoprefixer: {} }`). Bounded on both sides so a
57
+ * different package that merely contains `name` as a substring
58
+ * (`autoprefixer-wrapper`, `@scope/autoprefixer`) doesn't count.
59
+ */
60
+ export function textReferencesPackage(text, name) {
61
+ const pattern = new RegExp(`(?<![\\w@./-])${escapeRegExp(name)}(?![\\w-])`);
62
+ return pattern.test(text);
63
+ }
64
+ //# sourceMappingURL=config-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-files.js","sourceRoot":"","sources":["../../../src/commands/unused-deps/config-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAE3D,8EAA8E;AAC9E,sEAAsE;AACtE,kEAAkE;AAClE,sEAAsE;AACtE,uEAAuE;AACvE,oEAAoE;AACpE,sEAAsE;AACtE,qBAAqB;AACrB,MAAM,gBAAgB,GAAG;IACvB,kBAAkB;IAClB,kBAAkB;IAClB,mBAAmB;IACnB,mBAAmB;IACnB,gBAAgB;IAChB,gBAAgB;IAChB,iBAAiB;IACjB,gBAAgB;IAChB,cAAc;IACd,eAAe;IACf,kBAAkB;IAClB,mBAAmB;IACnB,kBAAkB;IAClB,cAAc;IACd,mBAAmB;IACnB,iBAAiB;IACjB,qBAAqB;IACrB,sBAAsB;IACtB,mBAAmB;IACnB,oBAAoB;IACpB,oBAAoB;IACpB,gBAAgB;IAChB,eAAe;IACf,qBAAqB;CACtB,CAAA;AAED,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,MAAM,QAAQ,IAAI,gBAAgB,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;QACvC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,SAAQ;QAC/B,IAAI,CAAC;YACH,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,4EAA4E;QAC9E,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,IAAY;IAC9D,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,iBAAiB,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IAC3E,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC3B,CAAC"}
@@ -0,0 +1,25 @@
1
+ declare const DEPENDENCY_SECTIONS: readonly ["dependencies", "devDependencies", "optionalDependencies"];
2
+ type DependencySection = (typeof DEPENDENCY_SECTIONS)[number];
3
+ export interface UnusedDepsFinding {
4
+ kind: 'unused' | 'phantom';
5
+ name: string;
6
+ /** which package.json section an 'unused' finding was declared in */
7
+ section?: DependencySection;
8
+ /** where a 'phantom' finding actually resolved from on disk */
9
+ resolvedFrom?: string;
10
+ }
11
+ export interface UnusedDepsAnalysis {
12
+ findings: UnusedDepsFinding[];
13
+ filesAnalyzed: number;
14
+ }
15
+ export interface AnalyzeOptions {
16
+ packageDir: string;
17
+ pkg: Record<string, unknown>;
18
+ /** also check packages that are structurally undetectable as "used" by any means this command can see. default false */
19
+ strict: boolean;
20
+ ignorePackages: Set<string>;
21
+ /** names of sibling packages in the same workspace — used-but-undeclared is expected for these, never "phantom" */
22
+ workspacePackageNames: Set<string>;
23
+ }
24
+ export declare function analyzeUnusedDeps(files: string[], options: AnalyzeOptions): UnusedDepsAnalysis;
25
+ export {};
@@ -0,0 +1,121 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { analyzeFile } from '../../utils/parse-module.js';
3
+ import { packageNameFromSpecifier } from './package-name.js';
4
+ import { declaredBinNames, resolvePackageDir } from './disk-resolve.js';
5
+ import { readConfigFilesText, textReferencesPackage } from './config-files.js';
6
+ const DEPENDENCY_SECTIONS = ['dependencies', 'devDependencies', 'optionalDependencies'];
7
+ // Packages that are near-universally legitimate to have installed
8
+ // without ever appearing as an `import`, a script token, or even a
9
+ // string in a config file — either genuinely ambient (`@types/*`, picked
10
+ // up by TypeScript automatically) or inferred from a config VALUE by a
11
+ // tool's own naming convention rather than written out in full
12
+ // (`coverage: { provider: 'v8' }` in a vitest config makes vitest
13
+ // require `@vitest/coverage-v8` itself — the full package name is never
14
+ // actually written anywhere a text scan could find it). `--strict`
15
+ // checks these too, for a manual audit.
16
+ function isStructurallyImplicit(name) {
17
+ return (name.startsWith('@types/') ||
18
+ name === 'typescript' ||
19
+ name.startsWith('@vitest/coverage-') ||
20
+ name === '@vitest/ui' ||
21
+ // Ambient type augmentation for a Nuxt module's own config/types
22
+ // (`defineNuxtConfig`, module hooks) — never imported by name,
23
+ // same structural class as @types/*.
24
+ name === '@nuxt/schema' ||
25
+ // Vite's CSS preprocessors — auto-detected by file EXTENSION
26
+ // (a `.scss` import triggers Vite to require `sass` if it's
27
+ // installed) and postcss by config file PRESENCE, neither ever
28
+ // spelled out by name in the project's own files. Confirmed
29
+ // against two real, independent packages before adding this —
30
+ // not a guess.
31
+ name === 'postcss' ||
32
+ name === 'sass' ||
33
+ name === 'less' ||
34
+ name === 'stylus');
35
+ }
36
+ function tokenAppearsIn(values, token) {
37
+ const words = values.flatMap((s) => s.split(/\s+/));
38
+ return words.some((w) => w === token || w.endsWith(`/${token}`) || w.endsWith(`\\${token}`));
39
+ }
40
+ /**
41
+ * A dependency invoked only by its bin name from tooling config embedded
42
+ * directly in package.json (`scripts`, and `lint-staged` — commonly
43
+ * inlined there rather than its own file) counts as used, same as an
44
+ * import. Checked by the package's own name AND (if installed) its
45
+ * declared `bin` keys, since `tsc`/`vitest`/`eslint` etc. don't always
46
+ * match their package name.
47
+ */
48
+ function usedViaPackageJsonTooling(pkg, packageDir, name) {
49
+ const scripts = pkg.scripts && typeof pkg.scripts === 'object'
50
+ ? Object.values(pkg.scripts)
51
+ : [];
52
+ const lintStaged = pkg['lint-staged'] && typeof pkg['lint-staged'] === 'object'
53
+ ? Object.values(pkg['lint-staged']).flatMap((v) => Array.isArray(v) ? v : typeof v === 'string' ? [v] : [])
54
+ : [];
55
+ const values = [...scripts, ...lintStaged];
56
+ if (tokenAppearsIn(values, name))
57
+ return true;
58
+ return declaredBinNames(packageDir, name).some((bin) => tokenAppearsIn(values, bin));
59
+ }
60
+ export function analyzeUnusedDeps(files, options) {
61
+ const usedPackageNames = new Set();
62
+ for (const file of files) {
63
+ const text = readFileSync(file, 'utf8');
64
+ const analysis = analyzeFile(text, file);
65
+ for (const imp of analysis.imports) {
66
+ const name = packageNameFromSpecifier(imp.from);
67
+ if (name)
68
+ usedPackageNames.add(name);
69
+ }
70
+ }
71
+ const declared = new Map();
72
+ const allDeclaredNames = new Set();
73
+ for (const section of DEPENDENCY_SECTIONS) {
74
+ const value = options.pkg[section];
75
+ if (!value || typeof value !== 'object')
76
+ continue;
77
+ for (const name of Object.keys(value)) {
78
+ declared.set(name, section);
79
+ allDeclaredNames.add(name);
80
+ }
81
+ }
82
+ const peerDeps = options.pkg.peerDependencies && typeof options.pkg.peerDependencies === 'object'
83
+ ? Object.keys(options.pkg.peerDependencies)
84
+ : [];
85
+ for (const name of peerDeps)
86
+ allDeclaredNames.add(name);
87
+ // A package importing itself by its own published name (the same
88
+ // self-reference readme-check resolves) is never a phantom dependency
89
+ // — it can't sensibly be its own devDependency, so it's exempted
90
+ // explicitly rather than expected to appear in package.json at all.
91
+ if (typeof options.pkg.name === 'string')
92
+ allDeclaredNames.add(options.pkg.name);
93
+ const configText = readConfigFilesText(options.packageDir);
94
+ const findings = [];
95
+ for (const [name, section] of declared) {
96
+ if (usedPackageNames.has(name))
97
+ continue;
98
+ if (options.ignorePackages.has(name))
99
+ continue;
100
+ if (!options.strict && isStructurallyImplicit(name))
101
+ continue;
102
+ if (usedViaPackageJsonTooling(options.pkg, options.packageDir, name))
103
+ continue;
104
+ if (textReferencesPackage(configText, name))
105
+ continue;
106
+ findings.push({ kind: 'unused', name, section });
107
+ }
108
+ for (const name of usedPackageNames) {
109
+ if (allDeclaredNames.has(name))
110
+ continue;
111
+ if (options.ignorePackages.has(name))
112
+ continue;
113
+ if (options.workspacePackageNames.has(name))
114
+ continue;
115
+ const resolvedFrom = resolvePackageDir(options.packageDir, name);
116
+ if (resolvedFrom)
117
+ findings.push({ kind: 'phantom', name, resolvedFrom });
118
+ }
119
+ return { findings, filesAnalyzed: files.length };
120
+ }
121
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../../src/commands/unused-deps/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Walks up from `fromDir` looking for `node_modules/<packageName>` —
3
+ * npm/yarn/pnpm all create this top-level symlink/directory for a
4
+ * package that's actually installed and resolvable, even when pnpm's
5
+ * real content lives in a `.pnpm` store elsewhere. Returns the resolved
6
+ * directory, or null if the package isn't installed anywhere up the
7
+ * tree (a broken import, not this command's concern — see core.ts).
8
+ */
9
+ export declare function resolvePackageDir(fromDir: string, packageName: string): string | null;
10
+ /** The bin command name(s) a package declares in its own package.json, if it's installed and readable. */
11
+ export declare function declaredBinNames(fromDir: string, packageName: string): string[];
@@ -0,0 +1,40 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ /**
4
+ * Walks up from `fromDir` looking for `node_modules/<packageName>` —
5
+ * npm/yarn/pnpm all create this top-level symlink/directory for a
6
+ * package that's actually installed and resolvable, even when pnpm's
7
+ * real content lives in a `.pnpm` store elsewhere. Returns the resolved
8
+ * directory, or null if the package isn't installed anywhere up the
9
+ * tree (a broken import, not this command's concern — see core.ts).
10
+ */
11
+ export function resolvePackageDir(fromDir, packageName) {
12
+ let dir = resolve(fromDir);
13
+ for (;;) {
14
+ const candidate = join(dir, 'node_modules', packageName);
15
+ if (existsSync(candidate))
16
+ return candidate;
17
+ const parent = dirname(dir);
18
+ if (parent === dir)
19
+ return null;
20
+ dir = parent;
21
+ }
22
+ }
23
+ /** The bin command name(s) a package declares in its own package.json, if it's installed and readable. */
24
+ export function declaredBinNames(fromDir, packageName) {
25
+ const pkgDir = resolvePackageDir(fromDir, packageName);
26
+ if (!pkgDir)
27
+ return [];
28
+ try {
29
+ const pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8'));
30
+ if (typeof pkg.bin === 'string')
31
+ return [packageName];
32
+ if (pkg.bin && typeof pkg.bin === 'object')
33
+ return Object.keys(pkg.bin);
34
+ return [];
35
+ }
36
+ catch {
37
+ return [];
38
+ }
39
+ }
40
+ //# sourceMappingURL=disk-resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disk-resolve.js","sourceRoot":"","sources":["../../../src/commands/unused-deps/disk-resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAElD;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,WAAmB;IACpE,IAAI,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1B,SAAS,CAAC;QACR,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,EAAE,WAAW,CAAC,CAAA;QACxD,IAAI,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAA;QAC3C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAA;QAC/B,GAAG,GAAG,MAAM,CAAA;IACd,CAAC;AACH,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,WAAmB;IACnE,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IACtD,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAA;IACtB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAGxE,CAAA;QACD,IAAI,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ;YAAE,OAAO,CAAC,WAAW,CAAC,CAAA;QACrD,IAAI,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ;YACxC,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAA8B,CAAC,CAAA;QACxD,OAAO,EAAE,CAAA;IACX,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Extracts the real npm package name a bare import specifier resolves
3
+ * to — `lodash/debounce` -> `lodash`, `@scope/name/sub/path` ->
4
+ * `@scope/name`. Returns null for a relative/absolute specifier or a
5
+ * Node builtin (`fs`, `node:fs`) — neither is ever a real dependency of
6
+ * this project.
7
+ */
8
+ export declare function packageNameFromSpecifier(specifier: string): string | null;
@@ -0,0 +1,20 @@
1
+ import { isBuiltin } from 'node:module';
2
+ /**
3
+ * Extracts the real npm package name a bare import specifier resolves
4
+ * to — `lodash/debounce` -> `lodash`, `@scope/name/sub/path` ->
5
+ * `@scope/name`. Returns null for a relative/absolute specifier or a
6
+ * Node builtin (`fs`, `node:fs`) — neither is ever a real dependency of
7
+ * this project.
8
+ */
9
+ export function packageNameFromSpecifier(specifier) {
10
+ if (specifier.startsWith('.') || specifier.startsWith('/'))
11
+ return null;
12
+ if (isBuiltin(specifier))
13
+ return null;
14
+ const parts = specifier.split('/');
15
+ if (specifier.startsWith('@')) {
16
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : null;
17
+ }
18
+ return parts[0] ?? null;
19
+ }
20
+ //# sourceMappingURL=package-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-name.js","sourceRoot":"","sources":["../../../src/commands/unused-deps/package-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,SAAiB;IACxD,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACvE,IAAI,SAAS,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAA;IAErC,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAClC,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAC7D,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;AACzB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type VibesOptions } from '../../format/vibes.js';
2
+ import type { UnusedDepsReport } from './run.js';
3
+ export declare function renderUnusedDepsReport(report: UnusedDepsReport, options?: VibesOptions): string;
@@ -0,0 +1,32 @@
1
+ import { banner, cleanCelebration } from '../../format/vibes.js';
2
+ import { formatFindingsRows } from '../../format/findings-list.js';
3
+ export function renderUnusedDepsReport(report, options = {}) {
4
+ if (report.error) {
5
+ const lines = [];
6
+ if (!options.quiet && !options.plain)
7
+ lines.push(banner(), '');
8
+ lines.push(`Could not run unused-deps: ${report.error}`);
9
+ return lines.join('\n');
10
+ }
11
+ const isClean = report.findings.length === 0;
12
+ if (options.quiet && isClean)
13
+ return '';
14
+ const lines = [];
15
+ if (!options.quiet && !options.plain)
16
+ lines.push(banner(), '');
17
+ lines.push(`Scanned ${report.filesScanned} file${report.filesScanned === 1 ? '' : 's'}.`, '');
18
+ if (isClean) {
19
+ lines.push(cleanCelebration(options));
20
+ return lines.join('\n');
21
+ }
22
+ lines.push(`${report.findings.length} problem${report.findings.length === 1 ? '' : 's'} found:`);
23
+ lines.push(...formatFindingsRows(report.findings.map((f) => ({
24
+ location: f.kind,
25
+ name: f.name,
26
+ tag: f.kind === 'unused'
27
+ ? `(${f.section}, not imported anywhere)`
28
+ : `resolves from ${f.resolvedFrom} — not declared in package.json`,
29
+ })), options));
30
+ return lines.join('\n');
31
+ }
32
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/commands/unused-deps/report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAqB,MAAM,uBAAuB,CAAA;AACnF,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAA;AAGlE,MAAM,UAAU,sBAAsB,CACpC,MAAwB,EACxB,UAAwB,EAAE;IAE1B,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,KAAK,GAAa,EAAE,CAAA;QAC1B,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC,CAAA;QAC9D,KAAK,CAAC,IAAI,CAAC,8BAA8B,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;QACxD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAA;IAC5C,IAAI,OAAO,CAAC,KAAK,IAAI,OAAO;QAAE,OAAO,EAAE,CAAA;IAEvC,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC,CAAA;IAE9D,KAAK,CAAC,IAAI,CAAC,WAAW,MAAM,CAAC,YAAY,QAAQ,MAAM,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,CAAC,CAAA;IAE7F,IAAI,OAAO,EAAE,CAAC;QACZ,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAA;QACrC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,WAAW,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAA;IAChG,KAAK,CAAC,IAAI,CACR,GAAG,kBAAkB,CACnB,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1B,QAAQ,EAAE,CAAC,CAAC,IAAI;QAChB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,GAAG,EACD,CAAC,CAAC,IAAI,KAAK,QAAQ;YACjB,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,0BAA0B;YACzC,CAAC,CAAC,iBAAiB,CAAC,CAAC,YAAY,iCAAiC;KACvE,CAAC,CAAC,EACH,OAAO,CACR,CACF,CAAA;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type UnusedDepsFinding } from './core.js';
2
+ export interface UnusedDepsRunOptions {
3
+ /** package directory to check — both the scan root and where package.json is looked up from, deliberately the same value (see below) */
4
+ dir: string;
5
+ extensions?: string[];
6
+ ignoreGlobs?: string[];
7
+ respectGitignore?: boolean;
8
+ strict?: boolean;
9
+ ignorePackages?: string[];
10
+ }
11
+ export interface UnusedDepsReport {
12
+ filesScanned: number;
13
+ findings: UnusedDepsFinding[];
14
+ /** package.json couldn't be found/read at all — a different kind of problem than any single finding */
15
+ error: string | null;
16
+ exitCode: number;
17
+ }
18
+ export declare function runUnusedDeps(options: UnusedDepsRunOptions): UnusedDepsReport;
@@ -0,0 +1,68 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { walk } from '../../utils/walk.js';
4
+ import { findNearestPackageDir } from '../../utils/find-package-dir.js';
5
+ import { detectWorkspace } from '../../utils/workspace.js';
6
+ import { analyzeUnusedDeps } from './core.js';
7
+ const DEFAULT_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
8
+ // A single [dir] argument, not the [paths...] + --cwd shape most other
9
+ // commands use — deliberately. unused-deps compares a package.json's
10
+ // declared dependencies against real source usage, so scanning one
11
+ // directory tree while consulting a DIFFERENT one's package.json (e.g.
12
+ // `--cwd` still defaulted to wherever the CLI happened to be invoked
13
+ // from, while `paths` pointed somewhere else entirely) would silently
14
+ // check the wrong dependency list — caught for real while dogfooding
15
+ // against sibling packages from inside devtoolz's own checkout, not by
16
+ // a test: findings named devtoolz's OWN dependencies (`commander`,
17
+ // `diff`, `ignore`) against a completely different package's source.
18
+ // One shared root removes the possibility entirely.
19
+ export function runUnusedDeps(options) {
20
+ const scanRoot = resolve(options.dir);
21
+ const packageDir = findNearestPackageDir(scanRoot);
22
+ if (!packageDir) {
23
+ return {
24
+ filesScanned: 0,
25
+ findings: [],
26
+ error: `no package.json found at or above ${scanRoot}`,
27
+ exitCode: 1,
28
+ };
29
+ }
30
+ let pkg;
31
+ try {
32
+ pkg = JSON.parse(readFileSync(`${packageDir}/package.json`, 'utf8'));
33
+ }
34
+ catch (err) {
35
+ return {
36
+ filesScanned: 0,
37
+ findings: [],
38
+ error: err instanceof Error ? err.message : String(err),
39
+ exitCode: 1,
40
+ };
41
+ }
42
+ const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
43
+ const files = walk(['.'], {
44
+ cwd: scanRoot,
45
+ extensions,
46
+ ignoreGlobs: options.ignoreGlobs ?? [],
47
+ ...(options.respectGitignore !== undefined
48
+ ? { respectGitignore: options.respectGitignore }
49
+ : {}),
50
+ });
51
+ const workspace = detectWorkspace(scanRoot);
52
+ const workspacePackageNames = new Set(workspace?.packages.map((p) => p.name) ?? []);
53
+ const analysis = analyzeUnusedDeps(files, {
54
+ packageDir,
55
+ pkg,
56
+ strict: options.strict ?? false,
57
+ ignorePackages: new Set(options.ignorePackages ?? []),
58
+ workspacePackageNames,
59
+ });
60
+ const findings = [...analysis.findings].sort((a, b) => a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name));
61
+ return {
62
+ filesScanned: analysis.filesAnalyzed,
63
+ findings,
64
+ error: null,
65
+ exitCode: findings.length > 0 ? 1 : 0,
66
+ };
67
+ }
68
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/commands/unused-deps/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAA0B,MAAM,WAAW,CAAA;AAErE,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAoBzE,uEAAuE;AACvE,qEAAqE;AACrE,mEAAmE;AACnE,uEAAuE;AACvE,qEAAqE;AACrE,sEAAsE;AACtE,qEAAqE;AACrE,uEAAuE;AACvE,mEAAmE;AACnE,qEAAqE;AACrE,oDAAoD;AACpD,MAAM,UAAU,aAAa,CAAC,OAA6B;IACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACrC,MAAM,UAAU,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAA;IAClD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO;YACL,YAAY,EAAE,CAAC;YACf,QAAQ,EAAE,EAAE;YACZ,KAAK,EAAE,qCAAqC,QAAQ,EAAE;YACtD,QAAQ,EAAE,CAAC;SACZ,CAAA;IACH,CAAC;IAED,IAAI,GAA4B,CAAA;IAChC,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,UAAU,eAAe,EAAE,MAAM,CAAC,CAA4B,CAAA;IACjG,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,YAAY,EAAE,CAAC;YACf,QAAQ,EAAE,EAAE;YACZ,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;YACvD,QAAQ,EAAE,CAAC;SACZ,CAAA;IACH,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,kBAAkB,CAAA;IAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE;QACxB,GAAG,EAAE,QAAQ;QACb,UAAU;QACV,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,EAAE;QACtC,GAAG,CAAC,OAAO,CAAC,gBAAgB,KAAK,SAAS;YACxC,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,EAAE;YAChD,CAAC,CAAC,EAAE,CAAC;KACR,CAAC,CAAA;IAEF,MAAM,SAAS,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAA;IAC3C,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;IAEnF,MAAM,QAAQ,GAAG,iBAAiB,CAAC,KAAK,EAAE;QACxC,UAAU;QACV,GAAG;QACH,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;QAC/B,cAAc,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC;QACrD,qBAAqB;KACtB,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAC1C,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CACvE,CAAA;IAED,OAAO;QACL,YAAY,EAAE,QAAQ,CAAC,aAAa;QACpC,QAAQ;QACR,KAAK,EAAE,IAAI;QACX,QAAQ,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACtC,CAAA;AACH,CAAC"}
package/dist/index.d.ts CHANGED
@@ -15,9 +15,26 @@ export type { DeadExportFinding, DeadExportsAnalysis, AnalyzeOptions, } from './
15
15
  export { runDeadExports } from './commands/dead-exports/run.js';
16
16
  export type { DeadExportsRunOptions, DeadExportsReport } from './commands/dead-exports/run.js';
17
17
  export { renderDeadExportsReport } from './commands/dead-exports/report.js';
18
- export { detectWorkspace } from './commands/dead-exports/workspace.js';
19
- export type { WorkspaceInfo, WorkspacePackage } from './commands/dead-exports/workspace.js';
20
- export { resolvePublicEntries, findNearestPackageDir } from './commands/dead-exports/entry.js';
18
+ export { detectWorkspace } from './utils/workspace.js';
19
+ export type { WorkspaceInfo, WorkspacePackage } from './utils/workspace.js';
20
+ export { analyzeCircularImports } from './commands/circular-imports/core.js';
21
+ export type { CircularImportFinding, CircularImportsAnalysis, CircularImportsAnalyzeOptions, } from './commands/circular-imports/core.js';
22
+ export { runCircularImports } from './commands/circular-imports/run.js';
23
+ export type { CircularImportsRunOptions, CircularImportsFileFinding, CircularImportsReport, } from './commands/circular-imports/run.js';
24
+ export { renderCircularImportsReport } from './commands/circular-imports/report.js';
25
+ export { analyzeUnusedDeps } from './commands/unused-deps/core.js';
26
+ export type { UnusedDepsFinding, UnusedDepsAnalysis } from './commands/unused-deps/core.js';
27
+ export type { AnalyzeOptions as UnusedDepsAnalyzeOptions } from './commands/unused-deps/core.js';
28
+ export { runUnusedDeps } from './commands/unused-deps/run.js';
29
+ export type { UnusedDepsRunOptions, UnusedDepsReport } from './commands/unused-deps/run.js';
30
+ export { renderUnusedDepsReport } from './commands/unused-deps/report.js';
31
+ export { packageNameFromSpecifier } from './commands/unused-deps/package-name.js';
32
+ export { resolvePackageDir, declaredBinNames } from './commands/unused-deps/disk-resolve.js';
33
+ export { analyzeFile } from './utils/parse-module.js';
34
+ export type { FileAnalysis, ExportedName, ReexportEntry, ImportedName, } from './utils/parse-module.js';
35
+ export { resolveRelativeSpecifier } from './utils/resolve-specifier.js';
36
+ export { resolvePublicEntries } from './commands/dead-exports/entry.js';
37
+ export { findNearestPackageDir } from './utils/find-package-dir.js';
21
38
  export { checkSpecifierCase } from './commands/case-check/resolve-case.js';
22
39
  export type { CaseCheckOutcome } from './commands/case-check/resolve-case.js';
23
40
  export { checkFile } from './commands/case-check/core.js';
@@ -36,12 +53,41 @@ export { extractCodeBlocks } from './commands/readme-check/extract.js';
36
53
  export type { CodeBlock } from './commands/readme-check/extract.js';
37
54
  export { typecheckBlock } from './commands/readme-check/typecheck.js';
38
55
  export type { BlockDiagnostic } from './commands/readme-check/typecheck.js';
39
- export { loadCompilerOptions } from './commands/readme-check/tsconfig.js';
40
- export type { LoadedCompilerOptions, LoadCompilerOptionsResult, } from './commands/readme-check/tsconfig.js';
56
+ export { loadTsconfig } from './utils/load-tsconfig.js';
57
+ export type { LoadedTsconfig, LoadTsconfigResult } from './utils/load-tsconfig.js';
41
58
  export { checkMarkdownFile } from './commands/readme-check/core.js';
42
59
  export type { ReadmeCheckFinding, ReadmeCheckFileResult } from './commands/readme-check/core.js';
43
60
  export { runReadmeCheck } from './commands/readme-check/run.js';
44
61
  export type { ReadmeCheckRunOptions, ReadmeCheckReport } from './commands/readme-check/run.js';
45
62
  export { renderReadmeCheckReport } from './commands/readme-check/report.js';
63
+ export { analyzeEmptyCatch } from './commands/empty-catch/core.js';
64
+ export type { EmptyCatchFinding } from './commands/empty-catch/core.js';
65
+ export { analyzeEmptyCatchVue } from './commands/empty-catch/vue.js';
66
+ export { runEmptyCatch } from './commands/empty-catch/run.js';
67
+ export type { EmptyCatchRunOptions, EmptyCatchFileFinding, EmptyCatchReport, } from './commands/empty-catch/run.js';
68
+ export { renderEmptyCatchReport } from './commands/empty-catch/report.js';
69
+ export { analyzeTodoReport, buildTagMatcher, DEFAULT_TAGS } from './commands/todo-report/core.js';
70
+ export type { TodoFinding } from './commands/todo-report/core.js';
71
+ export { analyzeTodoReportVue } from './commands/todo-report/vue.js';
72
+ export { runTodoReport } from './commands/todo-report/run.js';
73
+ export type { TodoReportRunOptions, TodoFileFinding, TodoReportReport, } from './commands/todo-report/run.js';
74
+ export { renderTodoReportReport } from './commands/todo-report/report.js';
75
+ export { analyzeScriptsCheck, extractMentionedScripts, isScriptMentioned, isReservedLifecycleScript, } from './commands/scripts-check/core.js';
76
+ export type { ScriptsCheckFinding, ScriptSource } from './commands/scripts-check/core.js';
77
+ export { runScriptsCheck } from './commands/scripts-check/run.js';
78
+ export type { ScriptsCheckRunOptions, ScriptsCheckReport } from './commands/scripts-check/run.js';
79
+ export { renderScriptsCheckReport } from './commands/scripts-check/report.js';
80
+ export { parseTestFile, hasMatchingSource, candidateSourceDirs, } from './commands/orphan-tests/core.js';
81
+ export type { OrphanTestFinding } from './commands/orphan-tests/core.js';
82
+ export { runOrphanTests } from './commands/orphan-tests/run.js';
83
+ export type { OrphanTestsRunOptions, OrphanTestsReport } from './commands/orphan-tests/run.js';
84
+ export { renderOrphanTestsReport } from './commands/orphan-tests/report.js';
85
+ export { findTsIgnoreDirectives, blankDirectives } from './commands/stale-ts-ignore/core.js';
86
+ export type { TsIgnoreDirective } from './commands/stale-ts-ignore/core.js';
87
+ export { extractVueScript, findVueTsIgnoreDirectives, type VueScript, } from './commands/stale-ts-ignore/vue.js';
88
+ export { runProjectTypecheck, checkIsolatedFile } from './commands/stale-ts-ignore/check.js';
89
+ export { runStaleTsIgnore } from './commands/stale-ts-ignore/run.js';
90
+ export type { StaleTsIgnoreRunOptions, StaleTsIgnoreFinding, StaleTsIgnoreReport, } from './commands/stale-ts-ignore/run.js';
91
+ export { renderStaleTsIgnoreReport } from './commands/stale-ts-ignore/report.js';
46
92
  export { walk } from './utils/walk.js';
47
93
  export type { WalkOptions } from './utils/walk.js';