@codefast/cli 0.11.0 → 0.13.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 (261) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +152 -56
  3. package/dist/arrange/command.js +24 -25
  4. package/dist/arrange/domain/ast/collectors-cn.d.ts +2 -2
  5. package/dist/arrange/domain/ast/collectors-cn.js +2 -2
  6. package/dist/arrange/domain/ast/collectors-jsx.d.ts +2 -2
  7. package/dist/arrange/domain/ast/collectors-jsx.js +1 -1
  8. package/dist/arrange/domain/ast/collectors-tv.d.ts +2 -2
  9. package/dist/arrange/domain/ast/collectors-tv.js +5 -5
  10. package/dist/arrange/domain/ast/helpers.d.ts +2 -2
  11. package/dist/arrange/domain/ast/helpers.js +3 -4
  12. package/dist/arrange/domain/ast/simplify-targets.d.ts +1 -1
  13. package/dist/arrange/domain/ast/simplify-targets.js +5 -5
  14. package/dist/arrange/domain/ast/targets.d.ts +2 -2
  15. package/dist/arrange/domain/ast/targets.js +9 -9
  16. package/dist/arrange/domain/ast/translator.d.ts +1 -1
  17. package/dist/arrange/domain/ast/translator.js +43 -29
  18. package/dist/arrange/domain/constants.d.ts +1 -1
  19. package/dist/arrange/domain/grouping-service.d.ts +2 -2
  20. package/dist/arrange/domain/grouping-service.js +3 -3
  21. package/dist/arrange/domain/grouping.js +3 -3
  22. package/dist/arrange/domain/imports.d.ts +1 -1
  23. package/dist/arrange/domain/imports.js +2 -2
  24. package/dist/arrange/domain/source-text-formatters.js +1 -1
  25. package/dist/arrange/domain/tailwind-token.js +1 -1
  26. package/dist/arrange/domain/token-classifier.d.ts +1 -1
  27. package/dist/arrange/domain/token-classifier.js +2 -2
  28. package/dist/arrange/domain/types.d.ts +3 -3
  29. package/dist/arrange/group/cli-result.d.ts +1 -1
  30. package/dist/arrange/group/output.d.ts +1 -1
  31. package/dist/arrange/group/output.js +1 -1
  32. package/dist/arrange/group/suggest.d.ts +2 -2
  33. package/dist/arrange/group/suggest.js +2 -2
  34. package/dist/arrange/inspect/cli-result.d.ts +1 -1
  35. package/dist/arrange/inspect/domain/analyze-service.d.ts +2 -2
  36. package/dist/arrange/inspect/domain/analyze-service.js +7 -7
  37. package/dist/arrange/inspect/output.d.ts +1 -1
  38. package/dist/arrange/inspect/output.js +2 -2
  39. package/dist/arrange/inspect/run.d.ts +4 -4
  40. package/dist/arrange/inspect/run.js +6 -6
  41. package/dist/arrange/prepare.d.ts +4 -4
  42. package/dist/arrange/prepare.js +5 -5
  43. package/dist/arrange/regroup/cli-result.d.ts +1 -1
  44. package/dist/arrange/regroup/cli-result.js +1 -1
  45. package/dist/arrange/regroup/output.d.ts +2 -2
  46. package/dist/arrange/regroup/output.js +2 -2
  47. package/dist/arrange/regroup/process-file.d.ts +2 -2
  48. package/dist/arrange/regroup/process-file.js +3 -3
  49. package/dist/arrange/regroup/run.d.ts +5 -5
  50. package/dist/arrange/regroup/run.js +4 -4
  51. package/dist/arrange/resolve-target.d.ts +1 -1
  52. package/dist/arrange/resolve-target.js +2 -2
  53. package/dist/arrange/scan-target.d.ts +1 -1
  54. package/dist/arrange/scan-target.js +3 -3
  55. package/dist/arrange/simplify/cli-result.d.ts +1 -1
  56. package/dist/arrange/simplify/fold-targets.d.ts +3 -3
  57. package/dist/arrange/simplify/fold-targets.js +3 -3
  58. package/dist/arrange/simplify/output.d.ts +1 -1
  59. package/dist/arrange/simplify/output.js +1 -1
  60. package/dist/arrange/simplify/process-file.d.ts +4 -4
  61. package/dist/arrange/simplify/process-file.js +5 -5
  62. package/dist/arrange/simplify/run.d.ts +4 -4
  63. package/dist/arrange/simplify/run.js +4 -4
  64. package/dist/arrange/simplify/variant-classname-probe.js +1 -1
  65. package/dist/arrange/source-parse.d.ts +1 -1
  66. package/dist/arrange/source-parse.js +3 -3
  67. package/dist/audit/assertions/cli-result.d.ts +13 -0
  68. package/dist/audit/assertions/cli-result.js +22 -0
  69. package/dist/audit/assertions/cli-schema.d.ts +18 -0
  70. package/dist/audit/assertions/cli-schema.js +12 -0
  71. package/dist/audit/assertions/domain/double-assertion.d.ts +20 -0
  72. package/dist/audit/assertions/domain/double-assertion.js +122 -0
  73. package/dist/audit/assertions/output.d.ts +7 -0
  74. package/dist/audit/assertions/output.js +21 -0
  75. package/dist/audit/assertions/prepare.d.ts +16 -0
  76. package/dist/audit/assertions/prepare.js +12 -0
  77. package/dist/audit/assertions/run.d.ts +14 -0
  78. package/dist/audit/assertions/run.js +40 -0
  79. package/dist/audit/command.js +89 -28
  80. package/dist/audit/comments/cli-result.d.ts +1 -1
  81. package/dist/audit/comments/cli-result.js +1 -1
  82. package/dist/audit/comments/output.d.ts +1 -1
  83. package/dist/audit/comments/output.js +1 -1
  84. package/dist/audit/comments/prepare.d.ts +4 -4
  85. package/dist/audit/comments/prepare.js +1 -1
  86. package/dist/audit/comments/run.d.ts +4 -4
  87. package/dist/audit/comments/run.js +9 -9
  88. package/dist/audit/constants/cli-result.d.ts +13 -0
  89. package/dist/audit/constants/cli-result.js +22 -0
  90. package/dist/audit/constants/cli-schema.d.ts +18 -0
  91. package/dist/audit/constants/cli-schema.js +12 -0
  92. package/dist/audit/constants/domain/constants.d.ts +8 -0
  93. package/dist/audit/constants/domain/constants.js +66 -0
  94. package/dist/audit/constants/output.d.ts +7 -0
  95. package/dist/audit/constants/output.js +21 -0
  96. package/dist/audit/constants/prepare.d.ts +16 -0
  97. package/dist/audit/constants/prepare.js +37 -0
  98. package/dist/audit/constants/run.d.ts +14 -0
  99. package/dist/audit/constants/run.js +64 -0
  100. package/dist/audit/display-names/cli-result.d.ts +1 -1
  101. package/dist/audit/display-names/cli-result.js +1 -1
  102. package/dist/audit/display-names/domain/display-names.d.ts +1 -1
  103. package/dist/audit/display-names/domain/display-names.js +1 -9
  104. package/dist/audit/display-names/output.d.ts +1 -1
  105. package/dist/audit/display-names/output.js +1 -1
  106. package/dist/audit/display-names/prepare.d.ts +4 -4
  107. package/dist/audit/display-names/prepare.js +1 -1
  108. package/dist/audit/display-names/run.d.ts +4 -4
  109. package/dist/audit/display-names/run.js +5 -5
  110. package/dist/audit/domain/types.d.ts +128 -0
  111. package/dist/audit/imports/cli-result.d.ts +1 -1
  112. package/dist/audit/imports/cli-result.js +1 -1
  113. package/dist/audit/imports/domain/import-policy.d.ts +1 -1
  114. package/dist/audit/imports/domain/import-policy.js +4 -18
  115. package/dist/audit/imports/output.d.ts +1 -1
  116. package/dist/audit/imports/output.js +1 -1
  117. package/dist/audit/imports/prepare.d.ts +4 -4
  118. package/dist/audit/imports/prepare.js +1 -1
  119. package/dist/audit/imports/run.d.ts +4 -4
  120. package/dist/audit/imports/run.js +6 -6
  121. package/dist/audit/links/cli-result.d.ts +1 -1
  122. package/dist/audit/links/cli-result.js +1 -1
  123. package/dist/audit/links/domain/markdown-links.d.ts +3 -3
  124. package/dist/audit/links/domain/markdown-links.js +22 -8
  125. package/dist/audit/links/output.d.ts +1 -1
  126. package/dist/audit/links/output.js +1 -1
  127. package/dist/audit/links/prepare.d.ts +4 -4
  128. package/dist/audit/links/prepare.js +1 -1
  129. package/dist/audit/links/run.d.ts +4 -4
  130. package/dist/audit/links/run.js +4 -4
  131. package/dist/audit/prepare.d.ts +4 -4
  132. package/dist/audit/prepare.js +4 -4
  133. package/dist/audit/publish/cli-result.d.ts +13 -0
  134. package/dist/audit/publish/cli-result.js +25 -0
  135. package/dist/audit/publish/cli-schema.d.ts +18 -0
  136. package/dist/audit/publish/cli-schema.js +12 -0
  137. package/dist/audit/publish/domain/legacy-subpath.d.ts +8 -0
  138. package/dist/audit/publish/domain/legacy-subpath.js +22 -0
  139. package/dist/audit/publish/domain/stylesheet-sources.d.ts +22 -0
  140. package/dist/audit/publish/domain/stylesheet-sources.js +64 -0
  141. package/dist/audit/publish/output.d.ts +7 -0
  142. package/dist/audit/publish/output.js +33 -0
  143. package/dist/audit/publish/prepare.d.ts +16 -0
  144. package/dist/audit/publish/prepare.js +12 -0
  145. package/dist/audit/publish/run.d.ts +15 -0
  146. package/dist/audit/publish/run.js +91 -0
  147. package/dist/audit/publish/shipped-files.d.ts +21 -0
  148. package/dist/audit/publish/shipped-files.js +36 -0
  149. package/dist/audit/rtl/cli-result.d.ts +1 -1
  150. package/dist/audit/rtl/cli-result.js +1 -1
  151. package/dist/audit/rtl/domain/audit-file.d.ts +1 -1
  152. package/dist/audit/rtl/domain/audit-file.js +2 -2
  153. package/dist/audit/rtl/domain/tokenize.d.ts +1 -1
  154. package/dist/audit/rtl/output.d.ts +1 -1
  155. package/dist/audit/rtl/output.js +1 -1
  156. package/dist/audit/rtl/prepare.d.ts +4 -4
  157. package/dist/audit/rtl/prepare.js +5 -5
  158. package/dist/audit/rtl/run.d.ts +4 -4
  159. package/dist/audit/rtl/run.js +4 -4
  160. package/dist/bin.js +1 -1
  161. package/dist/cli.js +5 -5
  162. package/dist/core/cli/command-pipeline.d.ts +4 -5
  163. package/dist/core/cli/command-pipeline.js +7 -7
  164. package/dist/core/cli/format-error.d.ts +1 -1
  165. package/dist/core/cli/resolve-root.d.ts +3 -3
  166. package/dist/core/cli/resolve-root.js +3 -3
  167. package/dist/core/cli/result-handle.d.ts +2 -2
  168. package/dist/core/cli/result-handle.js +4 -4
  169. package/dist/core/config/define-config.d.ts +1 -1
  170. package/dist/core/config/loader.d.ts +2 -2
  171. package/dist/core/config/loader.js +2 -2
  172. package/dist/core/config/schema.d.ts +5 -0
  173. package/dist/core/config/schema.js +2 -0
  174. package/dist/core/config/warnings.js +1 -1
  175. package/dist/core/config.d.ts +4 -4
  176. package/dist/core/config.js +4 -4
  177. package/dist/core/filesystem/filesystem.d.ts +3 -4
  178. package/dist/core/filesystem/node.d.ts +1 -1
  179. package/dist/core/filesystem/node.js +1 -7
  180. package/dist/core/oxc-node.d.ts +32 -0
  181. package/dist/core/oxc-node.js +25 -0
  182. package/dist/core/schema-parse.d.ts +2 -2
  183. package/dist/core/schema-parse.js +2 -2
  184. package/dist/core/source-position.d.ts +15 -0
  185. package/dist/core/source-position.js +26 -0
  186. package/dist/core/workspace/markdown-walk.d.ts +1 -1
  187. package/dist/core/workspace/markdown-walk.js +1 -1
  188. package/dist/core/workspace/package-version.d.ts +1 -1
  189. package/dist/core/workspace/package-version.js +2 -2
  190. package/dist/core/workspace/resolver.d.ts +1 -1
  191. package/dist/core/workspace/resolver.js +5 -5
  192. package/dist/core/workspace/source-walk.d.ts +1 -1
  193. package/dist/core/workspace/source-walk.js +1 -1
  194. package/dist/core/workspace/typescript-walk.d.ts +1 -1
  195. package/dist/core/workspace/typescript-walk.js +1 -1
  196. package/dist/core/workspace/walk-files.d.ts +1 -1
  197. package/dist/core/workspace/walk-files.js +1 -1
  198. package/dist/index.d.ts +3 -3
  199. package/dist/index.js +2 -2
  200. package/dist/mirror/cli-result.d.ts +1 -1
  201. package/dist/mirror/cli-result.js +1 -1
  202. package/dist/mirror/cli-schema.d.ts +1 -1
  203. package/dist/mirror/command.js +7 -7
  204. package/dist/mirror/dist-filesystem-node.d.ts +2 -2
  205. package/dist/mirror/dist-filesystem-node.js +5 -15
  206. package/dist/mirror/domain/exports.d.ts +2 -2
  207. package/dist/mirror/domain/exports.js +1 -1
  208. package/dist/mirror/domain/types.d.ts +2 -2
  209. package/dist/mirror/output.d.ts +1 -1
  210. package/dist/mirror/output.js +2 -2
  211. package/dist/mirror/package-path.d.ts +3 -3
  212. package/dist/mirror/package-path.js +3 -3
  213. package/dist/mirror/prepare.d.ts +5 -5
  214. package/dist/mirror/prepare.js +4 -4
  215. package/dist/mirror/run.d.ts +6 -6
  216. package/dist/mirror/run.js +7 -7
  217. package/dist/mirror/supplement-exports.d.ts +1 -1
  218. package/dist/mirror/supplement-exports.js +2 -2
  219. package/dist/mirror/sync-workspace-package.d.ts +3 -3
  220. package/dist/mirror/sync-workspace-package.js +11 -14
  221. package/dist/mirror/write-exports.d.ts +2 -2
  222. package/dist/mirror/write-exports.js +3 -3
  223. package/dist/pack-slim/cli-result.d.ts +1 -1
  224. package/dist/pack-slim/cli-result.js +1 -1
  225. package/dist/pack-slim/command.js +9 -9
  226. package/dist/pack-slim/domain/transform.d.ts +22 -1
  227. package/dist/pack-slim/domain/transform.js +65 -8
  228. package/dist/pack-slim/output.d.ts +1 -1
  229. package/dist/pack-slim/output.js +1 -1
  230. package/dist/pack-slim/prepare.d.ts +3 -3
  231. package/dist/pack-slim/prepare.js +2 -2
  232. package/dist/pack-slim/run.d.ts +5 -5
  233. package/dist/pack-slim/run.js +6 -6
  234. package/dist/pack-slim/working-tree.d.ts +2 -2
  235. package/dist/pack-slim/working-tree.js +2 -2
  236. package/dist/tag/cli-result.d.ts +1 -1
  237. package/dist/tag/cli-result.js +1 -1
  238. package/dist/tag/cli-schema.d.ts +1 -1
  239. package/dist/tag/command.js +7 -7
  240. package/dist/tag/domain/skip-filter.d.ts +1 -1
  241. package/dist/tag/domain/skip-filter.js +1 -1
  242. package/dist/tag/domain/types.d.ts +1 -1
  243. package/dist/tag/domain/version-summary.d.ts +5 -2
  244. package/dist/tag/output.d.ts +1 -1
  245. package/dist/tag/output.js +1 -1
  246. package/dist/tag/prepare.d.ts +4 -4
  247. package/dist/tag/prepare.js +4 -4
  248. package/dist/tag/run.d.ts +4 -4
  249. package/dist/tag/run.js +7 -7
  250. package/dist/tag/target/candidates.d.ts +2 -2
  251. package/dist/tag/target/candidates.js +2 -2
  252. package/dist/tag/target/resolve-path.d.ts +1 -1
  253. package/dist/tag/target/runner.d.ts +2 -2
  254. package/dist/tag/target/runner.js +3 -3
  255. package/dist/tag/writer/since-writer.d.ts +2 -2
  256. package/dist/tag/writer/since-writer.js +3 -5
  257. package/dist/tag/writer/version-resolver.d.ts +1 -1
  258. package/dist/tag/writer/version-resolver.js +1 -1
  259. package/package.json +7 -7
  260. package/dist/mirror/domain/dirent-guard.d.ts +0 -10
  261. package/dist/mirror/domain/dirent-guard.js +0 -15
@@ -1,4 +1,6 @@
1
1
  import { parseSync } from "oxc-parser";
2
+ import { isOxcNode, programStatements } from "#core/oxc-node";
3
+ import { firstLineOf, lineOfOffset } from "#core/source-position";
2
4
  /**
3
5
  * The import policies enforced across the monorepo: React members by name (never a namespace,
4
6
  * default, or implicit `React.*` UMD global), and Zod as a namespace repo-wide — the house form,
@@ -20,9 +22,6 @@ export const defaultImportPolicyRules = [
20
22
  "locale set into any bundle that reaches it)",
21
23
  },
22
24
  ];
23
- function isOxcNode(value) {
24
- return typeof value === "object" && value !== null && typeof value.type === "string";
25
- }
26
25
  function isIdentifierNamed(node, name) {
27
26
  return isOxcNode(node) && node.type === "Identifier" && node.name === name;
28
27
  }
@@ -41,7 +40,7 @@ function importedName(specifier) {
41
40
  */
42
41
  export function auditImportPolicySource(filePath, sourceText, rules) {
43
42
  const { program } = parseSync(filePath, sourceText);
44
- const statements = program.body;
43
+ const statements = programStatements(program);
45
44
  const violations = [];
46
45
  const boundUmdNames = new Set();
47
46
  for (const rule of rules) {
@@ -89,7 +88,7 @@ export function auditImportPolicySource(filePath, sourceText, rules) {
89
88
  }
90
89
  }
91
90
  for (const rule of rules) {
92
- if (rule.umdGlobal !== undefined && !boundUmdNames.has(rule.umdGlobal)) {
91
+ if (rule.umdGlobal !== undefined && !boundUmdNames.has(rule.umdGlobal) && isOxcNode(program)) {
93
92
  collectUmdGlobalReferences(program, sourceText, rule, violations);
94
93
  }
95
94
  }
@@ -124,17 +123,4 @@ function collectUmdGlobalReferences(node, sourceText, rule, violations) {
124
123
  collectUmdGlobalReferences(value, sourceText, rule, violations);
125
124
  }
126
125
  }
127
- }
128
- function lineOfOffset(sourceText, offset) {
129
- let line = 1;
130
- for (let index = 0; index < offset; index++) {
131
- if (sourceText.charCodeAt(index) === 10) {
132
- line++;
133
- }
134
- }
135
- return line;
136
- }
137
- function firstLineOf(text) {
138
- const newlineIndex = text.indexOf("\n");
139
- return newlineIndex === -1 ? text : text.slice(0, newlineIndex);
140
126
  }
@@ -1,4 +1,4 @@
1
- import type { ImportsAuditResult } from "#/audit/domain/types";
1
+ import type { ImportsAuditResult } from "#audit/domain/types";
2
2
  /**
3
3
  * Human-readable import-policy report.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { logger } from "#/core/logger";
1
+ import { logger } from "#core/logger";
2
2
  /**
3
3
  * Human-readable import-policy report.
4
4
  *
@@ -1,7 +1,7 @@
1
- import type { AuditCommandPrelude } from "#/audit/prepare";
2
- import type { AppError } from "#/core/errors";
3
- import type { Filesystem } from "#/core/filesystem/filesystem";
4
- import type { Result } from "#/core/result";
1
+ import type { AuditCommandPrelude } from "#audit/prepare";
2
+ import type { AppError } from "#core/errors";
3
+ import type { Filesystem } from "#core/filesystem/filesystem";
4
+ import type { Result } from "#core/result";
5
5
  /**
6
6
  * Loads config and resolves the scan target for `audit imports`.
7
7
  *
@@ -1,4 +1,4 @@
1
- import { prepareRepoRootAudit } from "#/audit/prepare";
1
+ import { prepareRepoRootAudit } from "#audit/prepare";
2
2
  /**
3
3
  * Loads config and resolves the scan target for `audit imports`.
4
4
  *
@@ -1,7 +1,7 @@
1
- import type { ImportsAuditResult } from "#/audit/domain/types";
2
- import { AppError } from "#/core/errors";
3
- import type { Filesystem } from "#/core/filesystem/filesystem";
4
- import type { Result } from "#/core/result";
1
+ import type { ImportsAuditResult } from "#audit/domain/types";
2
+ import { AppError } from "#core/errors";
3
+ import type { Filesystem } from "#core/filesystem/filesystem";
4
+ import type { Result } from "#core/result";
5
5
  /**
6
6
  * Scans a target path for import-policy violations, applying each rule only to files in its scope.
7
7
  *
@@ -1,10 +1,10 @@
1
1
  import path from "node:path";
2
- import { defaultImportPolicyRules } from "#/audit/imports/domain/import-policy";
3
- import { auditImportPolicySource } from "#/audit/imports/domain/import-policy";
4
- import { AppError, messageFrom } from "#/core/errors";
5
- import { createAnyGlobMatcher } from "#/core/glob";
6
- import { err, ok } from "#/core/result";
7
- import { walkTsxFiles } from "#/core/workspace/typescript-walk";
2
+ import { defaultImportPolicyRules } from "#audit/imports/domain/import-policy";
3
+ import { auditImportPolicySource } from "#audit/imports/domain/import-policy";
4
+ import { AppError, messageFrom } from "#core/errors";
5
+ import { createAnyGlobMatcher } from "#core/glob";
6
+ import { err, ok } from "#core/result";
7
+ import { walkTsxFiles } from "#core/workspace/typescript-walk";
8
8
  /**
9
9
  * Scans a target path for import-policy violations, applying each rule only to files in its scope.
10
10
  *
@@ -1,4 +1,4 @@
1
- import type { LinkAuditResult } from "#/audit/domain/types";
1
+ import type { LinkAuditResult } from "#audit/domain/types";
2
2
  /**
3
3
  * Exit `1` when any non-allowlisted broken link remains.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { CLI_EXIT_GENERAL_ERROR, CLI_EXIT_SUCCESS } from "#/core/exit-codes";
1
+ import { CLI_EXIT_GENERAL_ERROR, CLI_EXIT_SUCCESS } from "#core/exit-codes";
2
2
  /**
3
3
  * Exit `1` when any non-allowlisted broken link remains.
4
4
  *
@@ -25,9 +25,9 @@ export type MarkdownLinkScan = {
25
25
  /**
26
26
  * The anchor ids a rendered document exposes: explicit `<a id>` targets plus every heading's slug.
27
27
  *
28
- * @remarks Slugging matches GitHub's — lowercase, drop everything that is not a letter, number, space
29
- * or hyphen, then hyphenate spaces. Duplicate headings get a `-1` suffix there; this returns the base
30
- * only, so a link to the second copy reads as dangling rather than being silently accepted.
28
+ * @remarks Slugs follow GitHub's own algorithm so a link that resolves on GitHub resolves here.
29
+ * Repeated slugs take a `-1`, `-2`, … suffix in heading order, exactly as GitHub disambiguates them,
30
+ * so a link to the second copy of a heading resolves instead of reading as dangling.
31
31
  *
32
32
  * @since 0.5.0
33
33
  */
@@ -6,9 +6,9 @@ const EXTERNAL = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
6
6
  /**
7
7
  * The anchor ids a rendered document exposes: explicit `<a id>` targets plus every heading's slug.
8
8
  *
9
- * @remarks Slugging matches GitHub's — lowercase, drop everything that is not a letter, number, space
10
- * or hyphen, then hyphenate spaces. Duplicate headings get a `-1` suffix there; this returns the base
11
- * only, so a link to the second copy reads as dangling rather than being silently accepted.
9
+ * @remarks Slugs follow GitHub's own algorithm so a link that resolves on GitHub resolves here.
10
+ * Repeated slugs take a `-1`, `-2`, … suffix in heading order, exactly as GitHub disambiguates them,
11
+ * so a link to the second copy of a heading resolves instead of reading as dangling.
12
12
  *
13
13
  * @since 0.5.0
14
14
  */
@@ -17,15 +17,29 @@ export function collectMarkdownAnchors(content) {
17
17
  for (const match of content.matchAll(/<a\s+id="([^"]+)"\s*><\/a>/g)) {
18
18
  anchors.add(match[1]);
19
19
  }
20
+ const occurrences = new Map();
20
21
  for (const match of content.matchAll(/^#{1,6}\s+(.+?)\s*$/gm)) {
21
- anchors.add(match[1]
22
- .toLowerCase()
23
- .replaceAll(/[^\p{L}\p{N} -]/gu, "")
24
- .trim()
25
- .replaceAll(/\s+/g, "-"));
22
+ const base = slugifyHeading(match[1]);
23
+ let result = base;
24
+ while (occurrences.has(result)) {
25
+ occurrences.set(base, (occurrences.get(base) ?? 0) + 1);
26
+ result = `${base}-${occurrences.get(base)}`;
27
+ }
28
+ occurrences.set(result, 0);
29
+ anchors.add(result);
26
30
  }
27
31
  return anchors;
28
32
  }
33
+ /**
34
+ * A heading's GitHub slug: lowercased, stripped to letters, numbers, marks, underscore and hyphen,
35
+ * with each remaining space turned into a hyphen — runs are kept, not collapsed, matching GitHub.
36
+ */
37
+ function slugifyHeading(heading) {
38
+ return heading
39
+ .toLowerCase()
40
+ .replaceAll(/[^\p{L}\p{N}\p{M} _-]/gu, "")
41
+ .replaceAll(" ", "-");
42
+ }
29
43
  /**
30
44
  * Every repo-local link a document makes, with the anchors it offers.
31
45
  *
@@ -1,4 +1,4 @@
1
- import type { LinkAuditResult } from "#/audit/domain/types";
1
+ import type { LinkAuditResult } from "#audit/domain/types";
2
2
  /**
3
3
  * Human-readable link audit report.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { logger } from "#/core/logger";
1
+ import { logger } from "#core/logger";
2
2
  /**
3
3
  * Human-readable link audit report.
4
4
  *
@@ -1,7 +1,7 @@
1
- import type { AuditCommandPrelude } from "#/audit/prepare";
2
- import type { AppError } from "#/core/errors";
3
- import type { Filesystem } from "#/core/filesystem/filesystem";
4
- import type { Result } from "#/core/result";
1
+ import type { AuditCommandPrelude } from "#audit/prepare";
2
+ import type { AppError } from "#core/errors";
3
+ import type { Filesystem } from "#core/filesystem/filesystem";
4
+ import type { Result } from "#core/result";
5
5
  /**
6
6
  * Loads config and resolves the scan target for `audit links`.
7
7
  *
@@ -1,4 +1,4 @@
1
- import { prepareRepoRootAudit } from "#/audit/prepare";
1
+ import { prepareRepoRootAudit } from "#audit/prepare";
2
2
  /**
3
3
  * Loads config and resolves the scan target for `audit links`.
4
4
  *
@@ -1,7 +1,7 @@
1
- import type { LinkAuditResult } from "#/audit/domain/types";
2
- import { AppError } from "#/core/errors";
3
- import type { Filesystem } from "#/core/filesystem/filesystem";
4
- import type { Result } from "#/core/result";
1
+ import type { LinkAuditResult } from "#audit/domain/types";
2
+ import { AppError } from "#core/errors";
3
+ import type { Filesystem } from "#core/filesystem/filesystem";
4
+ import type { Result } from "#core/result";
5
5
  /**
6
6
  * Reports markdown links that point at nothing — a missing path, or an anchor the target does not offer.
7
7
  *
@@ -1,8 +1,8 @@
1
1
  import path from "node:path";
2
- import { collectMarkdownAnchors, scanMarkdownLinks } from "#/audit/links/domain/markdown-links";
3
- import { AppError, messageFrom } from "#/core/errors";
4
- import { err, ok } from "#/core/result";
5
- import { walkMarkdownFiles } from "#/core/workspace/markdown-walk";
2
+ import { collectMarkdownAnchors, scanMarkdownLinks } from "#audit/links/domain/markdown-links";
3
+ import { AppError, messageFrom } from "#core/errors";
4
+ import { err, ok } from "#core/result";
5
+ import { walkMarkdownFiles } from "#core/workspace/markdown-walk";
6
6
  /**
7
7
  * Reports markdown links that point at nothing — a missing path, or an anchor the target does not offer.
8
8
  *
@@ -1,7 +1,7 @@
1
- import type { CodefastConfig } from "#/core/config/schema";
2
- import { AppError } from "#/core/errors";
3
- import type { Filesystem } from "#/core/filesystem/filesystem";
4
- import type { Result } from "#/core/result";
1
+ import type { CodefastConfig } from "#core/config/schema";
2
+ import { AppError } from "#core/errors";
3
+ import type { Filesystem } from "#core/filesystem/filesystem";
4
+ import type { Result } from "#core/result";
5
5
  /**
6
6
  * Shared prelude for an audit: repo root and the canonicalized scan target with its allowlist.
7
7
  *
@@ -1,8 +1,8 @@
1
1
  import path from "node:path";
2
- import { loadCodefastConfig } from "#/core/config";
3
- import { AppError, messageFrom } from "#/core/errors";
4
- import { err, ok } from "#/core/result";
5
- import { resolveProjectRoot } from "#/core/workspace/resolver";
2
+ import { loadCodefastConfig } from "#core/config";
3
+ import { AppError, messageFrom } from "#core/errors";
4
+ import { err, ok } from "#core/result";
5
+ import { resolveProjectRoot } from "#core/workspace/resolver";
6
6
  /**
7
7
  * Resolves a path that may be absolute or relative to `rootDir`.
8
8
  *
@@ -0,0 +1,13 @@
1
+ import type { PublishAuditResult } from "#audit/domain/types";
2
+ /**
3
+ * Exit `1` when any `#/` import, unshipped publish target, or unreachable stylesheet source remains.
4
+ *
5
+ * @since 0.12.0
6
+ */
7
+ export declare function exitCodeForPublishAuditResult(result: PublishAuditResult): number;
8
+ /**
9
+ * Machine-readable publish audit summary for `--json`.
10
+ *
11
+ * @since 0.12.0
12
+ */
13
+ export declare function formatPublishAuditJsonOutput(result: PublishAuditResult, rootDir: string): string;
@@ -0,0 +1,25 @@
1
+ import { CLI_EXIT_GENERAL_ERROR, CLI_EXIT_SUCCESS } from "#core/exit-codes";
2
+ /**
3
+ * Exit `1` when any `#/` import, unshipped publish target, or unreachable stylesheet source remains.
4
+ *
5
+ * @since 0.12.0
6
+ */
7
+ export function exitCodeForPublishAuditResult(result) {
8
+ return isCleanPublishAudit(result) ? CLI_EXIT_SUCCESS : CLI_EXIT_GENERAL_ERROR;
9
+ }
10
+ /**
11
+ * Machine-readable publish audit summary for `--json`.
12
+ *
13
+ * @since 0.12.0
14
+ */
15
+ export function formatPublishAuditJsonOutput(result, rootDir) {
16
+ return JSON.stringify({
17
+ schemaVersion: 1,
18
+ ok: isCleanPublishAudit(result),
19
+ cwd: rootDir,
20
+ result,
21
+ });
22
+ }
23
+ function isCleanPublishAudit(result) {
24
+ return result.legacyImportCount === 0 && result.unshipped.length === 0 && result.unreachableStylesheets.length === 0;
25
+ }
@@ -0,0 +1,18 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * Resolved request for a single publish audit run.
4
+ *
5
+ * @since 0.12.0
6
+ */
7
+ export type PublishAuditRunRequest = {
8
+ readonly rootDir: string;
9
+ readonly targetPath: string;
10
+ readonly allowlist?: ReadonlyArray<string> | undefined;
11
+ readonly json: boolean;
12
+ };
13
+ /**
14
+ * Zod schema for {@link PublishAuditRunRequest}.
15
+ *
16
+ * @since 0.12.0
17
+ */
18
+ export declare const publishAuditRunRequestSchema: z.ZodType<PublishAuditRunRequest>;
@@ -0,0 +1,12 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * Zod schema for {@link PublishAuditRunRequest}.
4
+ *
5
+ * @since 0.12.0
6
+ */
7
+ export const publishAuditRunRequestSchema = z.object({
8
+ rootDir: z.string().min(1),
9
+ targetPath: z.string().min(1),
10
+ allowlist: z.array(z.string()).optional(),
11
+ json: z.boolean(),
12
+ });
@@ -0,0 +1,8 @@
1
+ /** Detects `#/`-prefixed internal import specifiers, which Node's ESM resolver rejects on the floor. */
2
+ import type { LegacySubpathImport } from "#audit/domain/types";
3
+ /**
4
+ * Every `#/`-prefixed import specifier in a source file, with its line number.
5
+ *
6
+ * @since 0.12.0
7
+ */
8
+ export declare function scanLegacySubpathImports(content: string): Array<LegacySubpathImport>;
@@ -0,0 +1,22 @@
1
+ /** Detects `#/`-prefixed internal import specifiers, which Node's ESM resolver rejects on the floor. */
2
+ // A `#/` specifier in import position — after `from`, `import(`, or `require(`. Anchoring on the keyword
3
+ // rules out an unrelated string that merely contains `#/`, such as this rule's own diagnostic text.
4
+ const LEGACY_SUBPATH_SPECIFIER = /(?:\bfrom|\bimport|\brequire)\b\s*\(?\s*(["']#\/[^"']*["'])/g;
5
+ /**
6
+ * Every `#/`-prefixed import specifier in a source file, with its line number.
7
+ *
8
+ * @since 0.12.0
9
+ */
10
+ export function scanLegacySubpathImports(content) {
11
+ const found = [];
12
+ const lines = content.split("\n");
13
+ for (const [index, line] of lines.entries()) {
14
+ for (const match of line.matchAll(LEGACY_SUBPATH_SPECIFIER)) {
15
+ const raw = match[1];
16
+ if (raw !== undefined) {
17
+ found.push({ line: index + 1, raw });
18
+ }
19
+ }
20
+ }
21
+ return found;
22
+ }
@@ -0,0 +1,22 @@
1
+ /** Reads the paths a stylesheet registers with Tailwind's `@source` and checks them against a shipped file list. */
2
+ import type { StylesheetSource } from "#audit/domain/types";
3
+ /**
4
+ * Every path a stylesheet registers with `@source`, with the line it sits on.
5
+ *
6
+ * @since 0.13.0
7
+ */
8
+ export declare function scanStylesheetSources(content: string): Array<StylesheetSource>;
9
+ /**
10
+ * Whether a stylesheet's `@source` paths all miss the files its package ships.
11
+ *
12
+ * @remarks Paths are package-relative and POSIX. A path that leaves the package names another package's files, which
13
+ * this tarball cannot vouch for, so it is not judged. One reachable path is enough: a workspace-only lane beside the
14
+ * published one is expected.
15
+ *
16
+ * @param stylesheetPath - The stylesheet, relative to its package root.
17
+ * @param sources - What {@link scanStylesheetSources} found in it.
18
+ * @param shippedFiles - Every file the package's tarball ships, relative to the package root.
19
+ *
20
+ * @since 0.13.0
21
+ */
22
+ export declare function missesShippedFiles(stylesheetPath: string, sources: ReadonlyArray<StylesheetSource>, shippedFiles: ReadonlyArray<string>): boolean;
@@ -0,0 +1,64 @@
1
+ /** Reads the paths a stylesheet registers with Tailwind's `@source` and checks them against a shipped file list. */
2
+ import path from "node:path";
3
+ import { createAnyGlobMatcher } from "#core/glob";
4
+ import { lineOfOffset } from "#core/source-position";
5
+ // A plain `@source "<path>";` — `@source not …` excludes paths and `@source inline(…)` names classes, so neither
6
+ // registers files.
7
+ const SOURCE_DIRECTIVE = /@source\s+(["'])([^"']+)\1\s*;/g;
8
+ // A string or a comment. Strings match first, so the `/**/` of a quoted glob is never read as a comment.
9
+ const STRING_OR_COMMENT = /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')|\/\*[\s\S]*?\*\//g;
10
+ const GLOB_CHARACTERS = /[*?[\]{}]/;
11
+ /**
12
+ * Every path a stylesheet registers with `@source`, with the line it sits on.
13
+ *
14
+ * @since 0.13.0
15
+ */
16
+ export function scanStylesheetSources(content) {
17
+ // Blanking comments keeps every offset in place, so a commented-out directive drops out and lines still count.
18
+ const code = content.replaceAll(STRING_OR_COMMENT, (match, quoted) => quoted ?? match.replaceAll(/[^\n]/g, " "));
19
+ const found = [];
20
+ for (const match of code.matchAll(SOURCE_DIRECTIVE)) {
21
+ const pattern = match[2];
22
+ if (pattern !== undefined) {
23
+ found.push({ line: lineOfOffset(code, match.index), pattern });
24
+ }
25
+ }
26
+ return found;
27
+ }
28
+ /**
29
+ * Whether a stylesheet's `@source` paths all miss the files its package ships.
30
+ *
31
+ * @remarks Paths are package-relative and POSIX. A path that leaves the package names another package's files, which
32
+ * this tarball cannot vouch for, so it is not judged. One reachable path is enough: a workspace-only lane beside the
33
+ * published one is expected.
34
+ *
35
+ * @param stylesheetPath - The stylesheet, relative to its package root.
36
+ * @param sources - What {@link scanStylesheetSources} found in it.
37
+ * @param shippedFiles - Every file the package's tarball ships, relative to the package root.
38
+ *
39
+ * @since 0.13.0
40
+ */
41
+ export function missesShippedFiles(stylesheetPath, sources, shippedFiles) {
42
+ const patterns = sources.flatMap((source) => packagePatternsFor(stylesheetPath, source.pattern));
43
+ if (patterns.length === 0) {
44
+ return false;
45
+ }
46
+ const isReached = createAnyGlobMatcher(patterns, { dot: true });
47
+ return !shippedFiles.some(isReached);
48
+ }
49
+ // Tailwind resolves a `@source` path against the stylesheet's directory; a path without a glob is a file or a
50
+ // directory, and a directory registers everything beneath it.
51
+ function packagePatternsFor(stylesheetPath, pattern) {
52
+ if (path.posix.isAbsolute(pattern)) {
53
+ return [];
54
+ }
55
+ const resolved = path.posix.normalize(path.posix.join(path.posix.dirname(stylesheetPath), pattern));
56
+ if (resolved === ".." || resolved.startsWith("../")) {
57
+ return [];
58
+ }
59
+ if (GLOB_CHARACTERS.test(resolved)) {
60
+ return [resolved];
61
+ }
62
+ const withoutTrailingSlash = resolved.replace(/\/+$/, "");
63
+ return withoutTrailingSlash === "." ? ["**"] : [withoutTrailingSlash, `${withoutTrailingSlash}/**`];
64
+ }
@@ -0,0 +1,7 @@
1
+ import type { PublishAuditResult } from "#audit/domain/types";
2
+ /**
3
+ * Human-readable publish audit report.
4
+ *
5
+ * @since 0.12.0
6
+ */
7
+ export declare function presentPublishAuditResult(result: PublishAuditResult): void;
@@ -0,0 +1,33 @@
1
+ import { logger } from "#core/logger";
2
+ /**
3
+ * Human-readable publish audit report.
4
+ *
5
+ * @since 0.12.0
6
+ */
7
+ export function presentPublishAuditResult(result) {
8
+ for (const file of result.legacyImportFiles) {
9
+ logger.out(`\n${file.relativePath}`);
10
+ for (const { line, raw } of file.imports) {
11
+ logger.out(` ${line}: ${raw} → use a bare "#" prefix, not "#/"`);
12
+ }
13
+ }
14
+ for (const { packageName, field, subpath, target } of result.unshipped) {
15
+ logger.out(`\n${packageName}: ${field}["${subpath}"] → ${target} is not shipped by "files"`);
16
+ }
17
+ for (const { packageName, stylesheet, sources, missingFilesEntries } of result.unreachableStylesheets) {
18
+ logger.out(`\n${packageName}: ${stylesheet} registers no file the tarball ships`);
19
+ for (const { line, pattern } of sources) {
20
+ logger.out(` ${line}: @source "${pattern}"`);
21
+ }
22
+ if (missingFilesEntries.length > 0) {
23
+ logger.out(` not on disk: ${missingFilesEntries.join(", ")} — build the package first`);
24
+ }
25
+ }
26
+ const problems = result.legacyImportCount + result.unshipped.length + result.unreachableStylesheets.length;
27
+ if (problems > 0) {
28
+ logger.out(`\n✖ ${result.legacyImportCount} legacy "#/" import(s), ${result.unshipped.length} unshipped target(s), ${result.unreachableStylesheets.length} stylesheet(s) registering nothing shipped`);
29
+ }
30
+ else {
31
+ logger.out(`✓ No "#/" imports across ${result.scannedFileCount} file(s); every publish target and stylesheet source ships across ${result.packageCount} package(s)`);
32
+ }
33
+ }
@@ -0,0 +1,16 @@
1
+ import type { AuditCommandPrelude } from "#audit/prepare";
2
+ import type { AppError } from "#core/errors";
3
+ import type { Filesystem } from "#core/filesystem/filesystem";
4
+ import type { Result } from "#core/result";
5
+ /**
6
+ * Loads config and resolves the scan target for `audit publish`.
7
+ *
8
+ * @remarks Defaults to the repo root: the audit walks every published package's source and manifest, so
9
+ * a single-package target would miss the cross-package surface. It takes no allowlist.
10
+ *
11
+ * @since 0.12.0
12
+ */
13
+ export declare function preparePublishAudit(fs: Filesystem, args: {
14
+ readonly currentWorkingDirectory: string;
15
+ readonly rawTarget: string | undefined;
16
+ }): Promise<Result<AuditCommandPrelude, AppError>>;
@@ -0,0 +1,12 @@
1
+ import { prepareRepoRootAudit } from "#audit/prepare";
2
+ /**
3
+ * Loads config and resolves the scan target for `audit publish`.
4
+ *
5
+ * @remarks Defaults to the repo root: the audit walks every published package's source and manifest, so
6
+ * a single-package target would miss the cross-package surface. It takes no allowlist.
7
+ *
8
+ * @since 0.12.0
9
+ */
10
+ export async function preparePublishAudit(fs, args) {
11
+ return prepareRepoRootAudit(fs, args, () => []);
12
+ }
@@ -0,0 +1,15 @@
1
+ import type { PublishAuditResult } from "#audit/domain/types";
2
+ import { AppError } from "#core/errors";
3
+ import type { Filesystem } from "#core/filesystem/filesystem";
4
+ import type { Result } from "#core/result";
5
+ /**
6
+ * Reports what would break a consumer's install: a `#/`-prefixed import Node's ESM resolver rejects on
7
+ * the floor, an `exports`/`imports` target the slimmed publish manifest does not ship, and a shipped
8
+ * stylesheet whose `@source` paths reach none of the files that do ship.
9
+ *
10
+ * @since 0.12.0
11
+ */
12
+ export declare function runPublishAudit(fs: Filesystem, args: {
13
+ readonly rootDir: string;
14
+ readonly targetPath: string;
15
+ }): Promise<Result<PublishAuditResult, AppError>>;