@codefast/cli 0.11.0 → 0.12.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 (222) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +57 -46
  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 +1 -1
  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 +3 -3
  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/command.js +45 -28
  68. package/dist/audit/comments/cli-result.d.ts +1 -1
  69. package/dist/audit/comments/cli-result.js +1 -1
  70. package/dist/audit/comments/output.d.ts +1 -1
  71. package/dist/audit/comments/output.js +1 -1
  72. package/dist/audit/comments/prepare.d.ts +4 -4
  73. package/dist/audit/comments/prepare.js +1 -1
  74. package/dist/audit/comments/run.d.ts +4 -4
  75. package/dist/audit/comments/run.js +9 -9
  76. package/dist/audit/display-names/cli-result.d.ts +1 -1
  77. package/dist/audit/display-names/cli-result.js +1 -1
  78. package/dist/audit/display-names/domain/display-names.d.ts +1 -1
  79. package/dist/audit/display-names/output.d.ts +1 -1
  80. package/dist/audit/display-names/output.js +1 -1
  81. package/dist/audit/display-names/prepare.d.ts +4 -4
  82. package/dist/audit/display-names/prepare.js +1 -1
  83. package/dist/audit/display-names/run.d.ts +4 -4
  84. package/dist/audit/display-names/run.js +5 -5
  85. package/dist/audit/domain/types.d.ts +43 -0
  86. package/dist/audit/imports/cli-result.d.ts +1 -1
  87. package/dist/audit/imports/cli-result.js +1 -1
  88. package/dist/audit/imports/domain/import-policy.d.ts +1 -1
  89. package/dist/audit/imports/output.d.ts +1 -1
  90. package/dist/audit/imports/output.js +1 -1
  91. package/dist/audit/imports/prepare.d.ts +4 -4
  92. package/dist/audit/imports/prepare.js +1 -1
  93. package/dist/audit/imports/run.d.ts +4 -4
  94. package/dist/audit/imports/run.js +6 -6
  95. package/dist/audit/links/cli-result.d.ts +1 -1
  96. package/dist/audit/links/cli-result.js +1 -1
  97. package/dist/audit/links/domain/markdown-links.d.ts +3 -3
  98. package/dist/audit/links/domain/markdown-links.js +22 -8
  99. package/dist/audit/links/output.d.ts +1 -1
  100. package/dist/audit/links/output.js +1 -1
  101. package/dist/audit/links/prepare.d.ts +4 -4
  102. package/dist/audit/links/prepare.js +1 -1
  103. package/dist/audit/links/run.d.ts +4 -4
  104. package/dist/audit/links/run.js +4 -4
  105. package/dist/audit/prepare.d.ts +4 -4
  106. package/dist/audit/prepare.js +4 -4
  107. package/dist/audit/publish/cli-result.d.ts +13 -0
  108. package/dist/audit/publish/cli-result.js +22 -0
  109. package/dist/audit/publish/cli-schema.d.ts +18 -0
  110. package/dist/audit/publish/cli-schema.js +12 -0
  111. package/dist/audit/publish/domain/legacy-subpath.d.ts +8 -0
  112. package/dist/audit/publish/domain/legacy-subpath.js +22 -0
  113. package/dist/audit/publish/output.d.ts +7 -0
  114. package/dist/audit/publish/output.js +24 -0
  115. package/dist/audit/publish/prepare.d.ts +16 -0
  116. package/dist/audit/publish/prepare.js +12 -0
  117. package/dist/audit/publish/run.d.ts +14 -0
  118. package/dist/audit/publish/run.js +62 -0
  119. package/dist/audit/rtl/cli-result.d.ts +1 -1
  120. package/dist/audit/rtl/cli-result.js +1 -1
  121. package/dist/audit/rtl/domain/audit-file.d.ts +1 -1
  122. package/dist/audit/rtl/domain/audit-file.js +2 -2
  123. package/dist/audit/rtl/domain/tokenize.d.ts +1 -1
  124. package/dist/audit/rtl/output.d.ts +1 -1
  125. package/dist/audit/rtl/output.js +1 -1
  126. package/dist/audit/rtl/prepare.d.ts +4 -4
  127. package/dist/audit/rtl/prepare.js +5 -5
  128. package/dist/audit/rtl/run.d.ts +4 -4
  129. package/dist/audit/rtl/run.js +4 -4
  130. package/dist/bin.js +1 -1
  131. package/dist/cli.js +5 -5
  132. package/dist/core/cli/command-pipeline.d.ts +4 -5
  133. package/dist/core/cli/command-pipeline.js +7 -7
  134. package/dist/core/cli/format-error.d.ts +1 -1
  135. package/dist/core/cli/resolve-root.d.ts +3 -3
  136. package/dist/core/cli/resolve-root.js +3 -3
  137. package/dist/core/cli/result-handle.d.ts +2 -2
  138. package/dist/core/cli/result-handle.js +4 -4
  139. package/dist/core/config/define-config.d.ts +1 -1
  140. package/dist/core/config/loader.d.ts +2 -2
  141. package/dist/core/config/loader.js +2 -2
  142. package/dist/core/config/warnings.js +1 -1
  143. package/dist/core/config.d.ts +4 -4
  144. package/dist/core/config.js +4 -4
  145. package/dist/core/filesystem/node.d.ts +1 -1
  146. package/dist/core/schema-parse.d.ts +2 -2
  147. package/dist/core/schema-parse.js +2 -2
  148. package/dist/core/workspace/markdown-walk.d.ts +1 -1
  149. package/dist/core/workspace/markdown-walk.js +1 -1
  150. package/dist/core/workspace/package-version.d.ts +1 -1
  151. package/dist/core/workspace/package-version.js +2 -2
  152. package/dist/core/workspace/resolver.d.ts +1 -1
  153. package/dist/core/workspace/resolver.js +5 -5
  154. package/dist/core/workspace/source-walk.d.ts +1 -1
  155. package/dist/core/workspace/source-walk.js +1 -1
  156. package/dist/core/workspace/typescript-walk.d.ts +1 -1
  157. package/dist/core/workspace/typescript-walk.js +1 -1
  158. package/dist/core/workspace/walk-files.d.ts +1 -1
  159. package/dist/core/workspace/walk-files.js +1 -1
  160. package/dist/index.d.ts +3 -3
  161. package/dist/index.js +2 -2
  162. package/dist/mirror/cli-result.d.ts +1 -1
  163. package/dist/mirror/cli-result.js +1 -1
  164. package/dist/mirror/cli-schema.d.ts +1 -1
  165. package/dist/mirror/command.js +7 -7
  166. package/dist/mirror/dist-filesystem-node.d.ts +2 -2
  167. package/dist/mirror/dist-filesystem-node.js +2 -2
  168. package/dist/mirror/domain/dirent-guard.d.ts +1 -1
  169. package/dist/mirror/domain/exports.d.ts +2 -2
  170. package/dist/mirror/domain/exports.js +1 -1
  171. package/dist/mirror/domain/types.d.ts +2 -2
  172. package/dist/mirror/output.d.ts +1 -1
  173. package/dist/mirror/output.js +2 -2
  174. package/dist/mirror/package-path.d.ts +3 -3
  175. package/dist/mirror/package-path.js +3 -3
  176. package/dist/mirror/prepare.d.ts +5 -5
  177. package/dist/mirror/prepare.js +4 -4
  178. package/dist/mirror/run.d.ts +6 -6
  179. package/dist/mirror/run.js +7 -7
  180. package/dist/mirror/supplement-exports.d.ts +1 -1
  181. package/dist/mirror/supplement-exports.js +2 -2
  182. package/dist/mirror/sync-workspace-package.d.ts +3 -3
  183. package/dist/mirror/sync-workspace-package.js +11 -14
  184. package/dist/mirror/write-exports.d.ts +2 -2
  185. package/dist/mirror/write-exports.js +3 -3
  186. package/dist/pack-slim/cli-result.d.ts +1 -1
  187. package/dist/pack-slim/cli-result.js +1 -1
  188. package/dist/pack-slim/command.js +9 -9
  189. package/dist/pack-slim/domain/transform.d.ts +22 -1
  190. package/dist/pack-slim/domain/transform.js +65 -8
  191. package/dist/pack-slim/output.d.ts +1 -1
  192. package/dist/pack-slim/output.js +1 -1
  193. package/dist/pack-slim/prepare.d.ts +3 -3
  194. package/dist/pack-slim/prepare.js +2 -2
  195. package/dist/pack-slim/run.d.ts +5 -5
  196. package/dist/pack-slim/run.js +5 -5
  197. package/dist/pack-slim/working-tree.d.ts +2 -2
  198. package/dist/pack-slim/working-tree.js +2 -2
  199. package/dist/tag/cli-result.d.ts +1 -1
  200. package/dist/tag/cli-result.js +1 -1
  201. package/dist/tag/cli-schema.d.ts +1 -1
  202. package/dist/tag/command.js +7 -7
  203. package/dist/tag/domain/skip-filter.d.ts +1 -1
  204. package/dist/tag/domain/skip-filter.js +1 -1
  205. package/dist/tag/domain/types.d.ts +1 -1
  206. package/dist/tag/domain/version-summary.d.ts +1 -1
  207. package/dist/tag/output.d.ts +1 -1
  208. package/dist/tag/output.js +1 -1
  209. package/dist/tag/prepare.d.ts +4 -4
  210. package/dist/tag/prepare.js +4 -4
  211. package/dist/tag/run.d.ts +4 -4
  212. package/dist/tag/run.js +7 -7
  213. package/dist/tag/target/candidates.d.ts +2 -2
  214. package/dist/tag/target/candidates.js +2 -2
  215. package/dist/tag/target/resolve-path.d.ts +1 -1
  216. package/dist/tag/target/runner.d.ts +2 -2
  217. package/dist/tag/target/runner.js +3 -3
  218. package/dist/tag/writer/since-writer.d.ts +2 -2
  219. package/dist/tag/writer/since-writer.js +1 -1
  220. package/dist/tag/writer/version-resolver.d.ts +1 -1
  221. package/dist/tag/writer/version-resolver.js +1 -1
  222. package/package.json +7 -7
@@ -1,4 +1,4 @@
1
- import { logger } from "#/core/logger";
1
+ import { logger } from "#core/logger";
2
2
  /**
3
3
  * Human-readable display-name 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 display-names`.
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 display-names`.
4
4
  *
@@ -1,7 +1,7 @@
1
- import type { DisplayNameAuditResult } 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 { DisplayNameAuditResult } 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 `token()`, `tag()` and module display names that break the convention.
7
7
  *
@@ -1,9 +1,9 @@
1
1
  import path from "node:path";
2
- import { auditDisplayNames } from "#/audit/display-names/domain/display-names";
3
- import { AppError, messageFrom } from "#/core/errors";
4
- import { err, ok } from "#/core/result";
5
- import { walkMarkdownFiles } from "#/core/workspace/markdown-walk";
6
- import { walkTsxFiles } from "#/core/workspace/typescript-walk";
2
+ import { auditDisplayNames } from "#audit/display-names/domain/display-names";
3
+ import { AppError, messageFrom } from "#core/errors";
4
+ import { err, ok } from "#core/result";
5
+ import { walkMarkdownFiles } from "#core/workspace/markdown-walk";
6
+ import { walkTsxFiles } from "#core/workspace/typescript-walk";
7
7
  /**
8
8
  * Trees the convention does not reach: a test or benchmark token is scoped by its file and never
9
9
  * meets another author's, and a changelog quotes names as they were.
@@ -168,4 +168,47 @@ export type CommentAuditResult = {
168
168
  readonly fixedCount: number;
169
169
  readonly dividerCount: number;
170
170
  readonly scannedFileCount: number;
171
+ };
172
+ /**
173
+ * A `#/`-prefixed internal import specifier — valid to the in-repo runners but rejected by Node's ESM
174
+ * resolver on the supported floor, so it breaks the published package.
175
+ *
176
+ * @since 0.12.0
177
+ */
178
+ export type LegacySubpathImport = {
179
+ readonly line: number;
180
+ /** The specifier as written, quotes included. */
181
+ readonly raw: string;
182
+ };
183
+ /**
184
+ * The `#/` imports found in one source file.
185
+ *
186
+ * @since 0.12.0
187
+ */
188
+ export type LegacySubpathFile = {
189
+ readonly relativePath: string;
190
+ readonly imports: Array<LegacySubpathImport>;
191
+ };
192
+ /**
193
+ * A published package whose slimmed manifest exports or imports a target it does not ship.
194
+ *
195
+ * @since 0.12.0
196
+ */
197
+ export type UnshippedTargetViolation = {
198
+ readonly packageName: string;
199
+ readonly field: string;
200
+ readonly subpath: string;
201
+ readonly target: string;
202
+ };
203
+ /**
204
+ * Outcome of one `audit publish` run.
205
+ *
206
+ * @since 0.12.0
207
+ */
208
+ export type PublishAuditResult = {
209
+ readonly legacyImportFiles: Array<LegacySubpathFile>;
210
+ readonly unshipped: Array<UnshippedTargetViolation>;
211
+ readonly legacyImportCount: number;
212
+ readonly scannedFileCount: number;
213
+ readonly packageCount: number;
171
214
  };
@@ -1,4 +1,4 @@
1
- import type { ImportsAuditResult } from "#/audit/domain/types";
1
+ import type { ImportsAuditResult } from "#audit/domain/types";
2
2
  /**
3
3
  * Exit `1` when any non-allowlisted import-policy violation 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 import-policy violation remains.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { ImportPolicyViolation } from "#/audit/domain/types";
1
+ import type { ImportPolicyViolation } from "#audit/domain/types";
2
2
  /**
3
3
  * One library's import policy: which forms of importing `module` are banned, optionally limited to
4
4
  * files whose repo-relative path matches `scope`, plus an optional UMD-global name to flag when the
@@ -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 or unshipped publish target 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,22 @@
1
+ import { CLI_EXIT_GENERAL_ERROR, CLI_EXIT_SUCCESS } from "#core/exit-codes";
2
+ /**
3
+ * Exit `1` when any `#/` import or unshipped publish target remains.
4
+ *
5
+ * @since 0.12.0
6
+ */
7
+ export function exitCodeForPublishAuditResult(result) {
8
+ return result.legacyImportCount > 0 || result.unshipped.length > 0 ? CLI_EXIT_GENERAL_ERROR : CLI_EXIT_SUCCESS;
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: result.legacyImportCount === 0 && result.unshipped.length === 0,
19
+ cwd: rootDir,
20
+ result,
21
+ });
22
+ }
@@ -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,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,24 @@
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
+ const problems = result.legacyImportCount + result.unshipped.length;
18
+ if (problems > 0) {
19
+ logger.out(`\n✖ ${result.legacyImportCount} legacy "#/" import(s), ${result.unshipped.length} unshipped target(s)`);
20
+ }
21
+ else {
22
+ logger.out(`✓ No "#/" imports across ${result.scannedFileCount} file(s); every publish target ships across ${result.packageCount} package(s)`);
23
+ }
24
+ }
@@ -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,14 @@
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, and an `exports`/`imports` target the slimmed publish manifest does not ship.
8
+ *
9
+ * @since 0.12.0
10
+ */
11
+ export declare function runPublishAudit(fs: Filesystem, args: {
12
+ readonly rootDir: string;
13
+ readonly targetPath: string;
14
+ }): Promise<Result<PublishAuditResult, AppError>>;
@@ -0,0 +1,62 @@
1
+ import path from "node:path";
2
+ import { scanLegacySubpathImports } from "#audit/publish/domain/legacy-subpath";
3
+ import { AppError, messageFrom } from "#core/errors";
4
+ import { err, ok } from "#core/result";
5
+ import { listWorkspacePackageDirectories } from "#core/workspace/resolver";
6
+ import { sourceCommentLanguage, walkSourceFiles } from "#core/workspace/source-walk";
7
+ import { packageJsonFileName } from "#core/workspace/well-known-files";
8
+ import { unshippedPublishTargets } from "#pack-slim/domain/transform";
9
+ /**
10
+ * Reports what would break a consumer's install: a `#/`-prefixed import Node's ESM resolver rejects on
11
+ * the floor, and an `exports`/`imports` target the slimmed publish manifest does not ship.
12
+ *
13
+ * @since 0.12.0
14
+ */
15
+ export async function runPublishAudit(fs, args) {
16
+ try {
17
+ const { rootDir, targetPath } = args;
18
+ const legacyImportFiles = [];
19
+ let legacyImportCount = 0;
20
+ // Tests are not published and may hold `#/` fixtures on purpose, so the specifier scan skips them.
21
+ const sourceFiles = walkSourceFiles(targetPath, fs).filter((filePath) => sourceCommentLanguage(filePath) === "js" && !filePath.split(path.sep).includes("tests"));
22
+ for (const absolutePath of sourceFiles) {
23
+ const imports = scanLegacySubpathImports(fs.readFileSync(absolutePath, "utf8"));
24
+ if (imports.length === 0) {
25
+ continue;
26
+ }
27
+ legacyImportCount += imports.length;
28
+ legacyImportFiles.push({ relativePath: toPosixPath(path.relative(rootDir, absolutePath)), imports });
29
+ }
30
+ const layout = await listWorkspacePackageDirectories(rootDir, fs, true);
31
+ const unshipped = [];
32
+ let packageCount = 0;
33
+ for (const packageDir of layout.packageDirectoryPathsAbsolute) {
34
+ const manifestPath = path.join(packageDir, packageJsonFileName);
35
+ if (!fs.existsSync(manifestPath)) {
36
+ continue;
37
+ }
38
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
39
+ if (manifest.private === true) {
40
+ continue;
41
+ }
42
+ packageCount++;
43
+ const packageName = typeof manifest.name === "string" ? manifest.name : toPosixPath(path.relative(rootDir, packageDir));
44
+ for (const target of unshippedPublishTargets(manifest)) {
45
+ unshipped.push({ packageName, field: target.field, subpath: target.subpath, target: target.target });
46
+ }
47
+ }
48
+ return ok({
49
+ legacyImportFiles,
50
+ unshipped,
51
+ legacyImportCount,
52
+ scannedFileCount: sourceFiles.length,
53
+ packageCount,
54
+ });
55
+ }
56
+ catch (caughtError) {
57
+ return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
58
+ }
59
+ }
60
+ function toPosixPath(filePath) {
61
+ return filePath.split(path.sep).join("/");
62
+ }
@@ -1,4 +1,4 @@
1
- import type { RtlAuditResult } from "#/audit/domain/types";
1
+ import type { RtlAuditResult } from "#audit/domain/types";
2
2
  /**
3
3
  * Exit `1` when any non-allowlisted violation 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 violation remains.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { RtlViolation } from "#/audit/domain/types";
1
+ import type { RtlViolation } from "#audit/domain/types";
2
2
  /**
3
3
  * Detects physical-direction Tailwind classes that should be logical or rtl:-paired.
4
4
  *