@macrulez/devtoolz 0.1.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/LICENSE +21 -0
  2. package/README.md +262 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +330 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/case-check/core.d.ts +17 -0
  7. package/dist/commands/case-check/core.js +46 -0
  8. package/dist/commands/case-check/core.js.map +1 -0
  9. package/dist/commands/case-check/parse.d.ts +10 -0
  10. package/dist/commands/case-check/parse.js +52 -0
  11. package/dist/commands/case-check/parse.js.map +1 -0
  12. package/dist/commands/case-check/report.d.ts +3 -0
  13. package/dist/commands/case-check/report.js +38 -0
  14. package/dist/commands/case-check/report.js.map +1 -0
  15. package/dist/commands/case-check/resolve-case.d.ts +14 -0
  16. package/dist/commands/case-check/resolve-case.js +163 -0
  17. package/dist/commands/case-check/resolve-case.js.map +1 -0
  18. package/dist/commands/case-check/run.d.ts +31 -0
  19. package/dist/commands/case-check/run.js +72 -0
  20. package/dist/commands/case-check/run.js.map +1 -0
  21. package/dist/commands/case-check/tsconfig-paths.d.ts +19 -0
  22. package/dist/commands/case-check/tsconfig-paths.js +119 -0
  23. package/dist/commands/case-check/tsconfig-paths.js.map +1 -0
  24. package/dist/commands/case-check/vue.d.ts +8 -0
  25. package/dist/commands/case-check/vue.js +26 -0
  26. package/dist/commands/case-check/vue.js.map +1 -0
  27. package/dist/commands/console-strip/core.d.ts +23 -0
  28. package/dist/commands/console-strip/core.js +104 -0
  29. package/dist/commands/console-strip/core.js.map +1 -0
  30. package/dist/commands/console-strip/report.d.ts +3 -0
  31. package/dist/commands/console-strip/report.js +47 -0
  32. package/dist/commands/console-strip/report.js.map +1 -0
  33. package/dist/commands/console-strip/run.d.ts +30 -0
  34. package/dist/commands/console-strip/run.js +55 -0
  35. package/dist/commands/console-strip/run.js.map +1 -0
  36. package/dist/commands/console-strip/vue.d.ts +8 -0
  37. package/dist/commands/console-strip/vue.js +28 -0
  38. package/dist/commands/console-strip/vue.js.map +1 -0
  39. package/dist/commands/dead-exports/core.d.ts +19 -0
  40. package/dist/commands/dead-exports/core.js +136 -0
  41. package/dist/commands/dead-exports/core.js.map +1 -0
  42. package/dist/commands/dead-exports/entry.d.ts +2 -0
  43. package/dist/commands/dead-exports/entry.js +93 -0
  44. package/dist/commands/dead-exports/entry.js.map +1 -0
  45. package/dist/commands/dead-exports/parse.d.ts +30 -0
  46. package/dist/commands/dead-exports/parse.js +174 -0
  47. package/dist/commands/dead-exports/parse.js.map +1 -0
  48. package/dist/commands/dead-exports/report.d.ts +3 -0
  49. package/dist/commands/dead-exports/report.js +27 -0
  50. package/dist/commands/dead-exports/report.js.map +1 -0
  51. package/dist/commands/dead-exports/resolve.d.ts +10 -0
  52. package/dist/commands/dead-exports/resolve.js +58 -0
  53. package/dist/commands/dead-exports/resolve.js.map +1 -0
  54. package/dist/commands/dead-exports/run.d.ts +21 -0
  55. package/dist/commands/dead-exports/run.js +51 -0
  56. package/dist/commands/dead-exports/run.js.map +1 -0
  57. package/dist/commands/dead-exports/workspace.d.ts +14 -0
  58. package/dist/commands/dead-exports/workspace.js +99 -0
  59. package/dist/commands/dead-exports/workspace.js.map +1 -0
  60. package/dist/commands/exports-doctor/core.d.ts +13 -0
  61. package/dist/commands/exports-doctor/core.js +85 -0
  62. package/dist/commands/exports-doctor/core.js.map +1 -0
  63. package/dist/commands/exports-doctor/parse.d.ts +20 -0
  64. package/dist/commands/exports-doctor/parse.js +80 -0
  65. package/dist/commands/exports-doctor/parse.js.map +1 -0
  66. package/dist/commands/exports-doctor/report.d.ts +3 -0
  67. package/dist/commands/exports-doctor/report.js +36 -0
  68. package/dist/commands/exports-doctor/report.js.map +1 -0
  69. package/dist/commands/exports-doctor/resolve-path.d.ts +10 -0
  70. package/dist/commands/exports-doctor/resolve-path.js +46 -0
  71. package/dist/commands/exports-doctor/resolve-path.js.map +1 -0
  72. package/dist/commands/exports-doctor/run.d.ts +13 -0
  73. package/dist/commands/exports-doctor/run.js +25 -0
  74. package/dist/commands/exports-doctor/run.js.map +1 -0
  75. package/dist/commands/readme-check/core.d.ts +22 -0
  76. package/dist/commands/readme-check/core.js +92 -0
  77. package/dist/commands/readme-check/core.js.map +1 -0
  78. package/dist/commands/readme-check/extract.d.ts +9 -0
  79. package/dist/commands/readme-check/extract.js +48 -0
  80. package/dist/commands/readme-check/extract.js.map +1 -0
  81. package/dist/commands/readme-check/lang.d.ts +8 -0
  82. package/dist/commands/readme-check/lang.js +21 -0
  83. package/dist/commands/readme-check/lang.js.map +1 -0
  84. package/dist/commands/readme-check/report.d.ts +3 -0
  85. package/dist/commands/readme-check/report.js +31 -0
  86. package/dist/commands/readme-check/report.js.map +1 -0
  87. package/dist/commands/readme-check/run.d.ts +19 -0
  88. package/dist/commands/readme-check/run.js +34 -0
  89. package/dist/commands/readme-check/run.js.map +1 -0
  90. package/dist/commands/readme-check/tsconfig.d.ts +9 -0
  91. package/dist/commands/readme-check/tsconfig.js +26 -0
  92. package/dist/commands/readme-check/tsconfig.js.map +1 -0
  93. package/dist/commands/readme-check/typecheck.d.ts +31 -0
  94. package/dist/commands/readme-check/typecheck.js +82 -0
  95. package/dist/commands/readme-check/typecheck.js.map +1 -0
  96. package/dist/commands/strip-comments/apply-deletions.d.ts +17 -0
  97. package/dist/commands/strip-comments/apply-deletions.js +74 -0
  98. package/dist/commands/strip-comments/apply-deletions.js.map +1 -0
  99. package/dist/commands/strip-comments/core.d.ts +15 -0
  100. package/dist/commands/strip-comments/core.js +111 -0
  101. package/dist/commands/strip-comments/core.js.map +1 -0
  102. package/dist/commands/strip-comments/report.d.ts +6 -0
  103. package/dist/commands/strip-comments/report.js +30 -0
  104. package/dist/commands/strip-comments/report.js.map +1 -0
  105. package/dist/commands/strip-comments/run.d.ts +28 -0
  106. package/dist/commands/strip-comments/run.js +50 -0
  107. package/dist/commands/strip-comments/run.js.map +1 -0
  108. package/dist/commands/strip-comments/vue.d.ts +6 -0
  109. package/dist/commands/strip-comments/vue.js +65 -0
  110. package/dist/commands/strip-comments/vue.js.map +1 -0
  111. package/dist/format/colors.d.ts +8 -0
  112. package/dist/format/colors.js +14 -0
  113. package/dist/format/colors.js.map +1 -0
  114. package/dist/format/diff-render.d.ts +2 -0
  115. package/dist/format/diff-render.js +35 -0
  116. package/dist/format/diff-render.js.map +1 -0
  117. package/dist/format/file-list.d.ts +14 -0
  118. package/dist/format/file-list.js +26 -0
  119. package/dist/format/file-list.js.map +1 -0
  120. package/dist/format/findings-list.d.ts +14 -0
  121. package/dist/format/findings-list.js +23 -0
  122. package/dist/format/findings-list.js.map +1 -0
  123. package/dist/format/vibes.d.ts +10 -0
  124. package/dist/format/vibes.js +67 -0
  125. package/dist/format/vibes.js.map +1 -0
  126. package/dist/index.d.ts +47 -0
  127. package/dist/index.js +29 -0
  128. package/dist/index.js.map +1 -0
  129. package/dist/utils/diff.d.ts +1 -0
  130. package/dist/utils/diff.js +13 -0
  131. package/dist/utils/diff.js.map +1 -0
  132. package/dist/utils/walk.d.ts +19 -0
  133. package/dist/utils/walk.js +96 -0
  134. package/dist/utils/walk.js.map +1 -0
  135. package/package.json +57 -0
@@ -0,0 +1,20 @@
1
+ export interface DeclaredPath {
2
+ /** e.g. 'main', 'bin.devtoolz', 'exports["./cdn"].types' */
3
+ location: string;
4
+ /** as written in package.json, e.g. './dist/index.js' */
5
+ path: string;
6
+ /**
7
+ * True when this entry came from inside a real conditions object (e.g.
8
+ * `{ import: ..., require: ... }`), where a missing `types` sibling is a
9
+ * genuine gap. False for a bare string/array subpath value, where there
10
+ * are no siblings at all and TypeScript falls back to a colocated
11
+ * `.d.ts` file instead — flagging those as "types missing" would be a
12
+ * false positive.
13
+ */
14
+ isConditionEntry: boolean;
15
+ }
16
+ export declare function collectDeclaredPaths(pkg: Record<string, unknown>): DeclaredPath[];
17
+ /** `location`s that name a `bin` entry — these additionally need a shebang. */
18
+ export declare function isBinLocation(location: string): boolean;
19
+ /** `location`s that are the `types`/`typings` half of a pair — see core.ts's types-vs-runtime check. */
20
+ export declare function isTypesLocation(location: string): boolean;
@@ -0,0 +1,80 @@
1
+ // Enumerates every file path `package.json` declares — main/module/types/
2
+ // typings/bin/exports — each tagged with WHERE it came from (e.g.
3
+ // `exports["./cdn"].types`), so a finding can point at the exact field to
4
+ // fix, not just "package.json is wrong somewhere".
5
+ function isConditionsObject(value) {
6
+ // An `exports` value is either a subpaths map (every key starts with
7
+ // '.') or a conditions map for the implicit '.' entry (keys are
8
+ // condition names like import/require/types/default/node/browser,
9
+ // none starting with '.') — same heuristic Node/bundlers use.
10
+ const keys = Object.keys(value);
11
+ return keys.length > 0 && keys.every((k) => !k.startsWith('.'));
12
+ }
13
+ function walkExportsValue(value, exportsKey, conditionPath, out) {
14
+ if (typeof value === 'string') {
15
+ const suffix = conditionPath.length > 0 ? `.${conditionPath.join('.')}` : '';
16
+ out.push({
17
+ location: `exports["${exportsKey}"]${suffix}`,
18
+ path: value,
19
+ isConditionEntry: conditionPath.length > 0,
20
+ });
21
+ }
22
+ else if (Array.isArray(value)) {
23
+ // A fallback array — any entry could be the one actually picked at
24
+ // runtime depending on the consumer's environment, so all are checked.
25
+ for (const entry of value)
26
+ walkExportsValue(entry, exportsKey, conditionPath, out);
27
+ }
28
+ else if (value && typeof value === 'object') {
29
+ for (const [key, nested] of Object.entries(value)) {
30
+ walkExportsValue(nested, exportsKey, [...conditionPath, key], out);
31
+ }
32
+ }
33
+ }
34
+ function collectExportsPaths(exportsField) {
35
+ const out = [];
36
+ if (typeof exportsField === 'string') {
37
+ out.push({ location: 'exports', path: exportsField, isConditionEntry: false });
38
+ return out;
39
+ }
40
+ if (!exportsField || typeof exportsField !== 'object')
41
+ return out;
42
+ if (isConditionsObject(exportsField)) {
43
+ walkExportsValue(exportsField, '.', [], out);
44
+ }
45
+ else {
46
+ for (const [subpath, value] of Object.entries(exportsField)) {
47
+ walkExportsValue(value, subpath, [], out);
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+ function collectBinPaths(binField) {
53
+ if (typeof binField === 'string')
54
+ return [{ location: 'bin', path: binField, isConditionEntry: false }];
55
+ if (binField && typeof binField === 'object') {
56
+ return Object.entries(binField)
57
+ .filter((entry) => typeof entry[1] === 'string')
58
+ .map(([name, path]) => ({ location: `bin.${name}`, path, isConditionEntry: false }));
59
+ }
60
+ return [];
61
+ }
62
+ export function collectDeclaredPaths(pkg) {
63
+ const out = [];
64
+ for (const field of ['main', 'module', 'types', 'typings']) {
65
+ if (typeof pkg[field] === 'string')
66
+ out.push({ location: field, path: pkg[field], isConditionEntry: false });
67
+ }
68
+ out.push(...collectBinPaths(pkg.bin));
69
+ out.push(...collectExportsPaths(pkg.exports));
70
+ return out;
71
+ }
72
+ /** `location`s that name a `bin` entry — these additionally need a shebang. */
73
+ export function isBinLocation(location) {
74
+ return location === 'bin' || location.startsWith('bin.');
75
+ }
76
+ /** `location`s that are the `types`/`typings` half of a pair — see core.ts's types-vs-runtime check. */
77
+ export function isTypesLocation(location) {
78
+ return location === 'types' || location === 'typings' || location.endsWith('.types');
79
+ }
80
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../../../src/commands/exports-doctor/parse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type VibesOptions } from '../../format/vibes.js';
2
+ import type { ExportsDoctorReport } from './run.js';
3
+ export declare function renderExportsDoctorReport(report: ExportsDoctorReport, options?: VibesOptions): string;
@@ -0,0 +1,36 @@
1
+ import { banner, cleanCelebration } from '../../format/vibes.js';
2
+ import { formatFindingsRows } from '../../format/findings-list.js';
3
+ const KIND_LABEL = {
4
+ missing: () => 'file does not exist',
5
+ 'case-mismatch': (_path, realPath) => `wrong case — real path is ${realPath}`,
6
+ 'missing-shebang': () => "missing '#!/usr/bin/env node' — won't run as a bin",
7
+ 'types-missing': () => 'runtime resolves fine, but no types declared for this entry at all',
8
+ };
9
+ export function renderExportsDoctorReport(report, options = {}) {
10
+ if (report.error) {
11
+ const lines = [];
12
+ if (!options.quiet && !options.plain)
13
+ lines.push(banner(), '');
14
+ lines.push(`Could not read package.json: ${report.error}`);
15
+ return lines.join('\n');
16
+ }
17
+ const isClean = report.findings.length === 0;
18
+ if (options.quiet && isClean)
19
+ return '';
20
+ const lines = [];
21
+ if (!options.quiet && !options.plain)
22
+ lines.push(banner(), '');
23
+ lines.push(`Checked ${report.packageName ?? 'this package'}'s declared exports.`, '');
24
+ if (isClean) {
25
+ lines.push(cleanCelebration(options));
26
+ return lines.join('\n');
27
+ }
28
+ lines.push(`${report.findings.length} problem${report.findings.length === 1 ? '' : 's'} found:`);
29
+ lines.push(...formatFindingsRows(report.findings.map((f) => ({
30
+ location: f.location,
31
+ name: f.path,
32
+ tag: KIND_LABEL[f.kind]?.(f.path, f.realPath) ?? f.kind,
33
+ })), options));
34
+ return lines.join('\n');
35
+ }
36
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/commands/exports-doctor/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,GAAgE;IAC9E,OAAO,EAAE,GAAG,EAAE,CAAC,qBAAqB;IACpC,eAAe,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,6BAA6B,QAAQ,EAAE;IAC7E,iBAAiB,EAAE,GAAG,EAAE,CAAC,oDAAoD;IAC7E,eAAe,EAAE,GAAG,EAAE,CAAC,oEAAoE;CAC5F,CAAA;AAED,MAAM,UAAU,yBAAyB,CACvC,MAA2B,EAC3B,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,gCAAgC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;QAC1D,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,WAAW,IAAI,cAAc,sBAAsB,EAAE,EAAE,CAAC,CAAA;IAErF,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,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI;KACxD,CAAC,CAAC,EACH,OAAO,CACR,CACF,CAAA;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC"}
@@ -0,0 +1,10 @@
1
+ export type PathCheckResult = {
2
+ status: 'ok';
3
+ } | {
4
+ status: 'missing';
5
+ } | {
6
+ status: 'case-mismatch';
7
+ realPath: string;
8
+ };
9
+ export declare function checkDeclaredPath(packageDir: string, declaredPath: string): PathCheckResult;
10
+ export declare function resolveDeclaredPath(packageDir: string, declaredPath: string): string;
@@ -0,0 +1,46 @@
1
+ // Checks one declared path against what's REALLY on disk, segment by
2
+ // segment, via `readdirSync` — same discipline as case-check, but here a
3
+ // missing file is the PRIMARY thing being looked for (case-check treats
4
+ // "doesn't resolve at all" as out of scope; here it's the main case),
5
+ // so the result is three-way instead of a plain yes/no.
6
+ import { readdirSync } from 'node:fs';
7
+ import { join, resolve } from 'node:path';
8
+ export function checkDeclaredPath(packageDir, declaredPath) {
9
+ const segments = declaredPath
10
+ .replace(/^\.\//, '')
11
+ .split('/')
12
+ .filter((s) => s.length > 0);
13
+ if (segments.length === 0)
14
+ return { status: 'missing' };
15
+ let currentDir = packageDir;
16
+ const corrected = [];
17
+ let hasMismatch = false;
18
+ for (const segment of segments) {
19
+ let entries;
20
+ try {
21
+ entries = readdirSync(currentDir);
22
+ }
23
+ catch {
24
+ return { status: 'missing' };
25
+ }
26
+ if (entries.includes(segment)) {
27
+ corrected.push(segment);
28
+ }
29
+ else {
30
+ const lower = segment.toLowerCase();
31
+ const match = entries.find((e) => e.toLowerCase() === lower);
32
+ if (!match)
33
+ return { status: 'missing' };
34
+ hasMismatch = true;
35
+ corrected.push(match);
36
+ }
37
+ currentDir = join(currentDir, corrected[corrected.length - 1]);
38
+ }
39
+ if (hasMismatch)
40
+ return { status: 'case-mismatch', realPath: `./${corrected.join('/')}` };
41
+ return { status: 'ok' };
42
+ }
43
+ export function resolveDeclaredPath(packageDir, declaredPath) {
44
+ return resolve(packageDir, declaredPath);
45
+ }
46
+ //# sourceMappingURL=resolve-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-path.js","sourceRoot":"","sources":["../../../src/commands/exports-doctor/resolve-path.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,yEAAyE;AACzE,wEAAwE;AACxE,sEAAsE;AACtE,wDAAwD;AAExD,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAKzC,MAAM,UAAU,iBAAiB,CAAC,UAAkB,EAAE,YAAoB;IACxE,MAAM,QAAQ,GAAG,YAAY;SAC1B,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;SACpB,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC9B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;IAEvD,IAAI,UAAU,GAAG,UAAU,CAAA;IAC3B,MAAM,SAAS,GAAa,EAAE,CAAA;IAC9B,IAAI,WAAW,GAAG,KAAK,CAAA;IAEvB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAiB,CAAA;QACrB,IAAI,CAAC;YACH,OAAO,GAAG,WAAW,CAAC,UAAU,CAAC,CAAA;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;QAC9B,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACzB,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAA;YACnC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAA;YAC5D,IAAI,CAAC,KAAK;gBAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;YACxC,WAAW,GAAG,IAAI,CAAA;YAClB,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACvB,CAAC;QACD,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAW,CAAC,CAAA;IAC1E,CAAC;IAED,IAAI,WAAW;QAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,KAAK,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAA;IACzF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;AACzB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,UAAkB,EAAE,YAAoB;IAC1E,OAAO,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;AAC1C,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { type ExportsDoctorFinding } from './core.js';
2
+ export interface ExportsDoctorRunOptions {
3
+ /** package directory to check */
4
+ dir: string;
5
+ }
6
+ export interface ExportsDoctorReport {
7
+ packageName: string | null;
8
+ findings: ExportsDoctorFinding[];
9
+ /** package.json couldn't be read/parsed at all — a different kind of problem than any single finding */
10
+ error: string | null;
11
+ exitCode: number;
12
+ }
13
+ export declare function runExportsDoctor(options: ExportsDoctorRunOptions): ExportsDoctorReport;
@@ -0,0 +1,25 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { checkPackage } from './core.js';
4
+ export function runExportsDoctor(options) {
5
+ let pkg;
6
+ try {
7
+ pkg = JSON.parse(readFileSync(join(options.dir, 'package.json'), 'utf8'));
8
+ }
9
+ catch (err) {
10
+ return {
11
+ packageName: null,
12
+ findings: [],
13
+ error: err instanceof Error ? err.message : String(err),
14
+ exitCode: 1,
15
+ };
16
+ }
17
+ const result = checkPackage(pkg, options.dir);
18
+ return {
19
+ packageName: result.packageName,
20
+ findings: result.findings,
21
+ error: null,
22
+ exitCode: result.findings.length > 0 ? 1 : 0,
23
+ };
24
+ }
25
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/commands/exports-doctor/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,YAAY,EAA6B,MAAM,WAAW,CAAA;AAenE,MAAM,UAAU,gBAAgB,CAAC,OAAgC;IAC/D,IAAI,GAA4B,CAAA;IAChC,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAGvE,CAAA;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,WAAW,EAAE,IAAI;YACjB,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,MAAM,GAAG,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAA;IAE7C,OAAO;QACL,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,KAAK,EAAE,IAAI;QACX,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KAC7C,CAAA;AACH,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type ts from 'typescript';
2
+ export interface ReadmeCheckFinding {
3
+ file: string;
4
+ /** 1-based, real line in the markdown file */
5
+ line: number;
6
+ /** 1-based column */
7
+ column: number;
8
+ message: string;
9
+ lang: string;
10
+ }
11
+ export interface ReadmeCheckFileResult {
12
+ file: string;
13
+ blocksChecked: number;
14
+ blocksSkipped: number;
15
+ findings: ReadmeCheckFinding[];
16
+ }
17
+ /**
18
+ * Each block is typechecked in isolation — never accumulated with other
19
+ * blocks in the same file. See `isNoise` above for what that isolation
20
+ * costs and how it's mitigated.
21
+ */
22
+ export declare function checkMarkdownFile(file: string, markdown: string, packageDir: string, compilerOptions: ts.CompilerOptions, langs: string[]): ReadmeCheckFileResult;
@@ -0,0 +1,92 @@
1
+ import { extractCodeBlocks } from './extract.js';
2
+ import { typecheckBlock } from './typecheck.js';
3
+ // "Cannot find name" (2304) and its "did you mean" variant (2552) — a doc
4
+ // snippet is routinely a FRAGMENT that assumes a variable set up in
5
+ // surrounding prose or an earlier, unrelated block ("inside your
6
+ // component:", "given a `router` instance, ..."). Checking each block in
7
+ // isolation (see below) can't tell that apart from a genuinely wrong
8
+ // example, so it doesn't try — these codes are dropped rather than
9
+ // reported as unreliable noise. Concatenating blocks together to give
10
+ // them shared context was tried and reverted: real dogfooding against
11
+ // every README in this workspace showed it trades this false-positive
12
+ // class for a WORSE one (two independent, alternative examples that
13
+ // happen to reuse a name like `toast`/`useForm` read as "Cannot redeclare
14
+ // block-scoped variable" — an alarming false bug report, not an obvious
15
+ // tool limitation the way "Cannot find name" reads).
16
+ //
17
+ // "X is declared but its value is never read" (6133) — a doc snippet
18
+ // routinely defines a handler function that's wired up somewhere NOT
19
+ // shown in the fenced block (a template's `@click`, an event listener
20
+ // registered elsewhere in prose) — real dogfooding hit this exact shape
21
+ // in vue-toast-kit's README (`function deleteFile(id) { ... }`, never
22
+ // called within its own isolated block). Also fires as a pure cascade of
23
+ // an already-suppressed 2304/2307 upstream (an unresolvable import
24
+ // leaves nothing to meaningfully call the declaration "unused" against).
25
+ const SUPPRESSED_CODES = new Set([2304, 2552, 6133]);
26
+ // "Cannot find module './X'" (2307) for a RELATIVE specifier is almost
27
+ // always a hypothetical file in the reader's own project ("your
28
+ // App.vue"), not something that could ever exist in this package — not
29
+ // checkable, so not reported. A BARE specifier (the package's own name,
30
+ // or one of its subpath exports) is kept: that's the one historically
31
+ // valuable case this command exists for (vue-image-kit's /cdn export
32
+ // resolving to nothing at runtime while nobody noticed).
33
+ const MODULE_NOT_FOUND_CODE = 2307;
34
+ const RELATIVE_MODULE_RE = /Cannot find module '(\.\.?\/[^']*)'/;
35
+ // A DIFFERENT code (2792, not 2307) for the case where the package's own
36
+ // dev tsconfig doesn't set `moduleResolution: bundler/node16/nodenext` at
37
+ // all — self-referencing its own name can never resolve there regardless
38
+ // of what the README says, and TypeScript's message says so explicitly
39
+ // ("Did you mean to set the 'moduleResolution' option..."). That's how
40
+ // this is told apart from a genuine bad specifier under a properly
41
+ // bundler-configured tsconfig (plain 2307, no such suffix) — the real
42
+ // vue-state-machine bug this command caught: its README's code examples
43
+ // still imported the unscoped `vue-state-machine` in five places after a
44
+ // scope-prefix fix elsewhere had already landed.
45
+ const UNSUPPORTED_RESOLUTION_CODE = 2792;
46
+ function isNoise(message, code) {
47
+ if (SUPPRESSED_CODES.has(code))
48
+ return true;
49
+ if (code === MODULE_NOT_FOUND_CODE && RELATIVE_MODULE_RE.test(message))
50
+ return true;
51
+ if (code === UNSUPPORTED_RESOLUTION_CODE)
52
+ return true;
53
+ return false;
54
+ }
55
+ /**
56
+ * Each block is typechecked in isolation — never accumulated with other
57
+ * blocks in the same file. See `isNoise` above for what that isolation
58
+ * costs and how it's mitigated.
59
+ */
60
+ export function checkMarkdownFile(file, markdown, packageDir, compilerOptions, langs) {
61
+ const blocks = extractCodeBlocks(markdown);
62
+ const findings = [];
63
+ let blocksChecked = 0;
64
+ let blocksSkipped = 0;
65
+ for (const block of blocks) {
66
+ if (block.noCheck || !langs.includes(block.lang)) {
67
+ blocksSkipped++;
68
+ continue;
69
+ }
70
+ const diagnostics = typecheckBlock(block.code, block.lang, packageDir, compilerOptions);
71
+ if (diagnostics === null) {
72
+ blocksSkipped++;
73
+ continue;
74
+ }
75
+ blocksChecked++;
76
+ for (const d of diagnostics) {
77
+ if (d.category !== 'error')
78
+ continue;
79
+ if (isNoise(d.message, d.code))
80
+ continue;
81
+ findings.push({
82
+ file,
83
+ line: block.startLine + d.line,
84
+ column: d.character + 1,
85
+ message: d.message,
86
+ lang: block.lang,
87
+ });
88
+ }
89
+ }
90
+ return { file, blocksChecked, blocksSkipped, findings };
91
+ }
92
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../../src/commands/readme-check/core.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAmB/C,0EAA0E;AAC1E,oEAAoE;AACpE,iEAAiE;AACjE,yEAAyE;AACzE,qEAAqE;AACrE,mEAAmE;AACnE,sEAAsE;AACtE,sEAAsE;AACtE,sEAAsE;AACtE,oEAAoE;AACpE,0EAA0E;AAC1E,wEAAwE;AACxE,qDAAqD;AACrD,EAAE;AACF,qEAAqE;AACrE,qEAAqE;AACrE,sEAAsE;AACtE,wEAAwE;AACxE,sEAAsE;AACtE,yEAAyE;AACzE,mEAAmE;AACnE,yEAAyE;AACzE,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AAEpD,uEAAuE;AACvE,gEAAgE;AAChE,uEAAuE;AACvE,wEAAwE;AACxE,sEAAsE;AACtE,qEAAqE;AACrE,yDAAyD;AACzD,MAAM,qBAAqB,GAAG,IAAI,CAAA;AAClC,MAAM,kBAAkB,GAAG,qCAAqC,CAAA;AAEhE,yEAAyE;AACzE,0EAA0E;AAC1E,yEAAyE;AACzE,uEAAuE;AACvE,uEAAuE;AACvE,mEAAmE;AACnE,sEAAsE;AACtE,wEAAwE;AACxE,yEAAyE;AACzE,iDAAiD;AACjD,MAAM,2BAA2B,GAAG,IAAI,CAAA;AAExC,SAAS,OAAO,CAAC,OAAe,EAAE,IAAY;IAC5C,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAC3C,IAAI,IAAI,KAAK,qBAAqB,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAA;IACnF,IAAI,IAAI,KAAK,2BAA2B;QAAE,OAAO,IAAI,CAAA;IACrD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAY,EACZ,QAAgB,EAChB,UAAkB,EAClB,eAAmC,EACnC,KAAe;IAEf,MAAM,MAAM,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAA;IAC1C,MAAM,QAAQ,GAAyB,EAAE,CAAA;IACzC,IAAI,aAAa,GAAG,CAAC,CAAA;IACrB,IAAI,aAAa,GAAG,CAAC,CAAA;IAErB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACjD,aAAa,EAAE,CAAA;YACf,SAAQ;QACV,CAAC;QAED,MAAM,WAAW,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,CAAA;QACvF,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;YACzB,aAAa,EAAE,CAAA;YACf,SAAQ;QACV,CAAC;QAED,aAAa,EAAE,CAAA;QACf,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;YAC5B,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO;gBAAE,SAAQ;YACpC,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC;gBAAE,SAAQ;YACxC,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI;gBACJ,IAAI,EAAE,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI;gBAC9B,MAAM,EAAE,CAAC,CAAC,SAAS,GAAG,CAAC;gBACvB,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,IAAI,EAAE,KAAK,CAAC,IAAI;aACjB,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAA;AACzD,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface CodeBlock {
2
+ lang: string;
3
+ /** 1-based line number of the first line of code, in the ORIGINAL markdown file */
4
+ startLine: number;
5
+ code: string;
6
+ /** an explicit `no-check` word in the fence's info string opts a block out */
7
+ noCheck: boolean;
8
+ }
9
+ export declare function extractCodeBlocks(markdown: string): CodeBlock[];
@@ -0,0 +1,48 @@
1
+ // Extracts fenced code blocks from a markdown file, tracking the block's
2
+ // language tag and the real 1-based line number where its content starts
3
+ // (right after the opening fence), so a typecheck error inside the block
4
+ // can be reported at the exact markdown line, not an offset into a
5
+ // throwaway virtual file.
6
+ const FENCE_START = /^(\s*)([`~]{3,})(.*)$/;
7
+ export function extractCodeBlocks(markdown) {
8
+ const lines = markdown.split(/\r\n|\r|\n/);
9
+ const blocks = [];
10
+ let i = 0;
11
+ while (i < lines.length) {
12
+ const line = lines[i];
13
+ const openMatch = FENCE_START.exec(line);
14
+ if (!openMatch) {
15
+ i++;
16
+ continue;
17
+ }
18
+ const fenceChars = openMatch[2];
19
+ const fenceChar = fenceChars[0];
20
+ const fenceLen = fenceChars.length;
21
+ const info = openMatch[3].trim();
22
+ const infoWords = info.split(/\s+/).filter(Boolean);
23
+ const lang = (infoWords[0] ?? '').toLowerCase();
24
+ const noCheck = infoWords.slice(1).includes('no-check');
25
+ // A closing fence must use the same character and be at least as long
26
+ // — a shorter run of the same character inside the block is literal
27
+ // content, not a close (standard GFM fence-length rule).
28
+ const closeRe = new RegExp(`^\\s*${fenceChar === '`' ? '`' : '~'}{${fenceLen},}\\s*$`);
29
+ let j = i + 1;
30
+ while (j < lines.length && !closeRe.test(lines[j]))
31
+ j++;
32
+ if (j >= lines.length) {
33
+ // Unterminated fence — malformed markdown, nothing sensible to
34
+ // extract; stop treating the rest of the file as inside this block.
35
+ i++;
36
+ continue;
37
+ }
38
+ blocks.push({
39
+ lang,
40
+ startLine: i + 2,
41
+ code: lines.slice(i + 1, j).join('\n'),
42
+ noCheck,
43
+ });
44
+ i = j + 1;
45
+ }
46
+ return blocks;
47
+ }
48
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../../src/commands/readme-check/extract.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,yEAAyE;AACzE,yEAAyE;AACzE,mEAAmE;AACnE,0BAA0B;AAW1B,MAAM,WAAW,GAAG,uBAAuB,CAAA;AAE3C,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IAC1C,MAAM,MAAM,GAAgB,EAAE,CAAA;IAC9B,IAAI,CAAC,GAAG,CAAC,CAAA;IAET,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAW,CAAA;QAC/B,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACxC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,CAAC,EAAE,CAAA;YACH,SAAQ;QACV,CAAC;QAED,MAAM,UAAU,GAAG,SAAS,CAAC,CAAC,CAAW,CAAA;QACzC,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAW,CAAA;QACzC,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAA;QAClC,MAAM,IAAI,GAAI,SAAS,CAAC,CAAC,CAAY,CAAC,IAAI,EAAE,CAAA;QAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACnD,MAAM,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;QAC/C,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;QAEvD,sEAAsE;QACtE,oEAAoE;QACpE,yDAAyD;QACzD,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,QAAQ,SAAS,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,QAAQ,SAAS,CAAC,CAAA;QACtF,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACb,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAW,CAAC;YAAE,CAAC,EAAE,CAAA;QAEjE,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACtB,+DAA+D;YAC/D,oEAAoE;YACpE,CAAC,EAAE,CAAA;YACH,SAAQ;QACV,CAAC;QAED,MAAM,CAAC,IAAI,CAAC;YACV,IAAI;YACJ,SAAS,EAAE,CAAC,GAAG,CAAC;YAChB,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YACtC,OAAO;SACR,CAAC,CAAA;QACF,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACX,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -0,0 +1,8 @@
1
+ import ts from 'typescript';
2
+ export interface LangInfo {
3
+ scriptKind: ts.ScriptKind;
4
+ extension: string;
5
+ needsAllowJs: boolean;
6
+ }
7
+ export declare function resolveLangInfo(lang: string): LangInfo | null;
8
+ export declare const DEFAULT_LANGS: string[];
@@ -0,0 +1,21 @@
1
+ import ts from 'typescript';
2
+ const LANG_MAP = {
3
+ ts: { scriptKind: ts.ScriptKind.TS, extension: '.ts', needsAllowJs: false },
4
+ typescript: { scriptKind: ts.ScriptKind.TS, extension: '.ts', needsAllowJs: false },
5
+ tsx: { scriptKind: ts.ScriptKind.TSX, extension: '.tsx', needsAllowJs: false },
6
+ js: { scriptKind: ts.ScriptKind.JS, extension: '.js', needsAllowJs: true },
7
+ javascript: { scriptKind: ts.ScriptKind.JS, extension: '.js', needsAllowJs: true },
8
+ jsx: { scriptKind: ts.ScriptKind.JSX, extension: '.jsx', needsAllowJs: true },
9
+ };
10
+ export function resolveLangInfo(lang) {
11
+ return LANG_MAP[lang] ?? null;
12
+ }
13
+ // tsx is deliberately NOT in the default set — real-world dogfooding
14
+ // against every package in this workspace showed EVERY tsx block failing
15
+ // universally with "Cannot use JSX unless the '--jsx' flag is provided"
16
+ // (Vue packages don't configure JSX/React types, since the block is
17
+ // illustrative "if you're in a React app" pseudocode, not code this
18
+ // package could ever compile). Opt in explicitly with `--lang ts,tsx` for
19
+ // a package that actually configures JSX in its own tsconfig.
20
+ export const DEFAULT_LANGS = ['ts'];
21
+ //# sourceMappingURL=lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lang.js","sourceRoot":"","sources":["../../../src/commands/readme-check/lang.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAQ3B,MAAM,QAAQ,GAA6B;IACzC,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE;IAC3E,UAAU,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE;IACnF,GAAG,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE;IAC9E,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE;IAC1E,UAAU,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE;IAClF,GAAG,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE;CAC9E,CAAA;AAED,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAA;AAC/B,CAAC;AAED,qEAAqE;AACrE,yEAAyE;AACzE,wEAAwE;AACxE,oEAAoE;AACpE,oEAAoE;AACpE,0EAA0E;AAC1E,8DAA8D;AAC9D,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAI,CAAC,CAAA"}
@@ -0,0 +1,3 @@
1
+ import { type VibesOptions } from '../../format/vibes.js';
2
+ import type { ReadmeCheckReport } from './run.js';
3
+ export declare function renderReadmeCheckReport(report: ReadmeCheckReport, options?: VibesOptions): string;
@@ -0,0 +1,31 @@
1
+ import { banner, cleanCelebration } from '../../format/vibes.js';
2
+ import { formatFindingsRows } from '../../format/findings-list.js';
3
+ export function renderReadmeCheckReport(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 readme-check: ${report.error}`);
9
+ return lines.join('\n');
10
+ }
11
+ const blocksChecked = report.fileResults.reduce((sum, f) => sum + f.blocksChecked, 0);
12
+ const isClean = report.findings.length === 0;
13
+ if (options.quiet && isClean)
14
+ return '';
15
+ const lines = [];
16
+ if (!options.quiet && !options.plain)
17
+ lines.push(banner(), '');
18
+ lines.push(`Typechecked ${blocksChecked} code block${blocksChecked === 1 ? '' : 's'} across ${report.fileResults.length} file${report.fileResults.length === 1 ? '' : 's'}.`, '');
19
+ if (isClean) {
20
+ lines.push(cleanCelebration(options));
21
+ return lines.join('\n');
22
+ }
23
+ lines.push(`${report.findings.length} problem${report.findings.length === 1 ? '' : 's'} found:`);
24
+ lines.push(...formatFindingsRows(report.findings.map((f) => ({
25
+ location: `${f.file}:${f.line}:${f.column}`,
26
+ name: f.lang,
27
+ tag: f.message,
28
+ })), options));
29
+ return lines.join('\n');
30
+ }
31
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/commands/readme-check/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,uBAAuB,CACrC,MAAyB,EACzB,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,+BAA+B,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;QACzD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAA;IACrF,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,CACR,eAAe,aAAa,cAAc,aAAa,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,WAAW,MAAM,CAAC,WAAW,CAAC,MAAM,QAAQ,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,EACjK,EAAE,CACH,CAAA;IAED,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,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,EAAE;QAC3C,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,GAAG,EAAE,CAAC,CAAC,OAAO;KACf,CAAC,CAAC,EACH,OAAO,CACR,CACF,CAAA;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { type ReadmeCheckFinding, type ReadmeCheckFileResult } from './core.js';
2
+ export interface ReadmeCheckRunOptions {
3
+ /** package directory to check against */
4
+ dir: string;
5
+ /** markdown files to check, relative to `dir` — default: ['README.md'] */
6
+ files?: string[];
7
+ /** fenced-block languages to typecheck — default: ['ts', 'tsx'] */
8
+ langs?: string[];
9
+ /** tsconfig.json path, relative to `dir` — auto-detected by default */
10
+ tsconfig?: string;
11
+ }
12
+ export interface ReadmeCheckReport {
13
+ fileResults: ReadmeCheckFileResult[];
14
+ findings: ReadmeCheckFinding[];
15
+ /** couldn't load a tsconfig or read a target file at all — a different kind of problem than any single finding */
16
+ error: string | null;
17
+ exitCode: number;
18
+ }
19
+ export declare function runReadmeCheck(options: ReadmeCheckRunOptions): ReadmeCheckReport;
@@ -0,0 +1,34 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { checkMarkdownFile } from './core.js';
4
+ import { loadCompilerOptions } from './tsconfig.js';
5
+ import { DEFAULT_LANGS } from './lang.js';
6
+ export function runReadmeCheck(options) {
7
+ const packageDir = resolve(options.dir);
8
+ const targetFiles = options.files && options.files.length > 0 ? options.files : ['README.md'];
9
+ const langs = options.langs && options.langs.length > 0 ? options.langs : DEFAULT_LANGS;
10
+ const loaded = loadCompilerOptions(packageDir, options.tsconfig);
11
+ if ('error' in loaded) {
12
+ return { fileResults: [], findings: [], error: loaded.error, exitCode: 1 };
13
+ }
14
+ const fileResults = [];
15
+ for (const relFile of targetFiles) {
16
+ const absPath = resolve(packageDir, relFile);
17
+ let markdown;
18
+ try {
19
+ markdown = readFileSync(absPath, 'utf8');
20
+ }
21
+ catch (err) {
22
+ return {
23
+ fileResults: [],
24
+ findings: [],
25
+ error: `could not read ${relFile}: ${err instanceof Error ? err.message : String(err)}`,
26
+ exitCode: 1,
27
+ };
28
+ }
29
+ fileResults.push(checkMarkdownFile(relFile, markdown, packageDir, loaded.options, langs));
30
+ }
31
+ const findings = fileResults.flatMap((f) => f.findings);
32
+ return { fileResults, findings, error: null, exitCode: findings.length > 0 ? 1 : 0 };
33
+ }
34
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/commands/readme-check/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,iBAAiB,EAAuD,MAAM,WAAW,CAAA;AAClG,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAqBzC,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACvC,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;IAC7F,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAA;IAEvF,MAAM,MAAM,GAAG,mBAAmB,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IAChE,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;QACtB,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAA;IAC5E,CAAC;IAED,MAAM,WAAW,GAA4B,EAAE,CAAA;IAC/C,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;QAC5C,IAAI,QAAgB,CAAA;QACpB,IAAI,CAAC;YACH,QAAQ,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,WAAW,EAAE,EAAE;gBACf,QAAQ,EAAE,EAAE;gBACZ,KAAK,EAAE,kBAAkB,OAAO,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;gBACvF,QAAQ,EAAE,CAAC;aACZ,CAAA;QACH,CAAC;QACD,WAAW,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;IACvD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AACtF,CAAC"}
@@ -0,0 +1,9 @@
1
+ import ts from 'typescript';
2
+ export interface LoadedCompilerOptions {
3
+ options: ts.CompilerOptions;
4
+ configPath: string;
5
+ }
6
+ export type LoadCompilerOptionsResult = LoadedCompilerOptions | {
7
+ error: string;
8
+ };
9
+ export declare function loadCompilerOptions(packageDir: string, tsconfigOverride?: string): LoadCompilerOptionsResult;
@@ -0,0 +1,26 @@
1
+ import ts from 'typescript';
2
+ import { dirname, resolve } from 'node:path';
3
+ export function loadCompilerOptions(packageDir, tsconfigOverride) {
4
+ const configPath = tsconfigOverride
5
+ ? resolve(packageDir, tsconfigOverride)
6
+ : ts.findConfigFile(packageDir, ts.sys.fileExists, 'tsconfig.json');
7
+ if (!configPath || !ts.sys.fileExists(configPath)) {
8
+ return {
9
+ error: `no tsconfig.json found under ${packageDir} — nothing to typecheck README code blocks against`,
10
+ };
11
+ }
12
+ const read = ts.readConfigFile(configPath, ts.sys.readFile);
13
+ if (read.error) {
14
+ return { error: ts.flattenDiagnosticMessageText(read.error.messageText, '\n') };
15
+ }
16
+ const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, dirname(configPath));
17
+ if (parsed.errors.length > 0) {
18
+ return {
19
+ error: parsed.errors
20
+ .map((e) => ts.flattenDiagnosticMessageText(e.messageText, '\n'))
21
+ .join('\n'),
22
+ };
23
+ }
24
+ return { options: parsed.options, configPath };
25
+ }
26
+ //# sourceMappingURL=tsconfig.js.map