@codefast/cli 0.8.0 → 0.9.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 (230) hide show
  1. package/CHANGELOG.md +568 -0
  2. package/LICENSE +1 -1
  3. package/README.md +150 -50
  4. package/dist/arrange/analyze.js +1 -2
  5. package/dist/arrange/cli-schema.js +1 -2
  6. package/dist/arrange/command.js +1 -2
  7. package/dist/arrange/domain/analyze-service.js +1 -2
  8. package/dist/arrange/domain/ast/ast-node.js +1 -2
  9. package/dist/arrange/domain/ast/collectors-cn.js +1 -2
  10. package/dist/arrange/domain/ast/collectors-jsx.js +1 -2
  11. package/dist/arrange/domain/ast/collectors-tv.js +1 -2
  12. package/dist/arrange/domain/ast/helpers.js +1 -2
  13. package/dist/arrange/domain/ast/simplify-targets.js +1 -2
  14. package/dist/arrange/domain/ast/targets.js +1 -2
  15. package/dist/arrange/domain/constants.js +1 -2
  16. package/dist/arrange/domain/grouping-service.js +1 -2
  17. package/dist/arrange/domain/grouping.js +1 -2
  18. package/dist/arrange/domain/imports.js +1 -2
  19. package/dist/arrange/domain/source-text-formatters.js +1 -2
  20. package/dist/arrange/domain/tailwind-token.js +1 -2
  21. package/dist/arrange/domain/token-classifier.js +1 -2
  22. package/dist/arrange/domain/types.js +1 -2
  23. package/dist/arrange/output.js +1 -2
  24. package/dist/arrange/process-file.js +1 -2
  25. package/dist/arrange/resolve-target.js +1 -2
  26. package/dist/arrange/scan-target.js +1 -2
  27. package/dist/arrange/simplify-process-file.js +1 -2
  28. package/dist/arrange/simplify-sync.js +1 -2
  29. package/dist/arrange/source-parse.js +1 -2
  30. package/dist/arrange/suggest.js +1 -2
  31. package/dist/arrange/sync.js +1 -2
  32. package/dist/arrange/typescript-ast-translator.js +1 -2
  33. package/dist/arrange/workspace.js +1 -2
  34. package/dist/audit/cli-schema.js +12 -2
  35. package/dist/audit/command.js +44 -5
  36. package/dist/audit/domain/audit-file.js +2 -3
  37. package/dist/audit/domain/comment-content.js +1 -2
  38. package/dist/audit/domain/comment-dividers.js +49 -22
  39. package/dist/audit/domain/display-names.js +71 -0
  40. package/dist/audit/domain/link-references.js +1 -2
  41. package/dist/audit/domain/mappings.js +1 -2
  42. package/dist/audit/domain/markdown-links.js +1 -2
  43. package/dist/audit/domain/react-imports.js +1 -2
  44. package/dist/audit/domain/since-versions.js +1 -2
  45. package/dist/audit/domain/tokenize.js +2 -3
  46. package/dist/audit/domain/tsdoc-syntax.js +1 -2
  47. package/dist/audit/domain/types.js +1 -2
  48. package/dist/audit/output.js +41 -1
  49. package/dist/audit/prepare.js +31 -1
  50. package/dist/audit/run-comments.js +23 -20
  51. package/dist/audit/run-display-names.js +60 -0
  52. package/dist/audit/run-links.js +1 -2
  53. package/dist/audit/run-react.js +1 -2
  54. package/dist/audit/run.js +1 -2
  55. package/dist/bin.js +1 -2
  56. package/dist/cli.js +3 -2
  57. package/dist/core/cli/format-error.js +1 -2
  58. package/dist/core/cli/global-options.js +1 -2
  59. package/dist/core/cli/positional.js +1 -2
  60. package/dist/core/cli/result-handle.js +1 -22
  61. package/dist/core/config/loader.js +1 -2
  62. package/dist/core/config/schema.js +19 -10
  63. package/dist/core/config/warnings.js +1 -2
  64. package/dist/core/config.js +1 -2
  65. package/dist/core/errors.js +1 -2
  66. package/dist/core/exit-codes.js +1 -2
  67. package/dist/core/filesystem/node.js +1 -2
  68. package/dist/core/filesystem/port.js +1 -2
  69. package/dist/core/glob.js +1 -2
  70. package/dist/core/logger.js +1 -2
  71. package/dist/core/result.js +1 -2
  72. package/dist/core/schema-parse.js +1 -2
  73. package/dist/core/source-text-edit.js +1 -2
  74. package/dist/core/verbose-diagnostics.js +1 -2
  75. package/dist/core/workspace/markdown-walk.js +1 -2
  76. package/dist/core/workspace/package-version.js +1 -2
  77. package/dist/core/workspace/resolver.js +1 -2
  78. package/dist/core/workspace/skip-directories.js +1 -2
  79. package/dist/core/workspace/source-walk.js +14 -4
  80. package/dist/core/workspace/typescript-walk.js +1 -2
  81. package/dist/mirror/cli-result.js +1 -2
  82. package/dist/mirror/cli-schema.js +1 -2
  83. package/dist/mirror/command.js +1 -2
  84. package/dist/mirror/dist-filesystem-impl.js +1 -2
  85. package/dist/mirror/domain/constants.js +1 -2
  86. package/dist/mirror/domain/dirent-guard.js +1 -2
  87. package/dist/mirror/domain/dist-filesystem.js +1 -2
  88. package/dist/mirror/domain/errors.js +1 -2
  89. package/dist/mirror/domain/exports.js +1 -2
  90. package/dist/mirror/domain/package-display-name.js +1 -2
  91. package/dist/mirror/domain/path-normalizer.js +1 -2
  92. package/dist/mirror/domain/types.js +1 -2
  93. package/dist/mirror/output.js +1 -2
  94. package/dist/mirror/package-path.js +1 -2
  95. package/dist/mirror/prepare.js +1 -2
  96. package/dist/mirror/supplement-exports.js +1 -2
  97. package/dist/mirror/sync-reporter.js +1 -2
  98. package/dist/mirror/sync-types.js +1 -2
  99. package/dist/mirror/sync-workspace-package.js +1 -2
  100. package/dist/mirror/sync.js +1 -2
  101. package/dist/mirror/write-exports.js +1 -2
  102. package/dist/pack-slim/cli-result.js +23 -0
  103. package/dist/pack-slim/cli-schema.js +11 -0
  104. package/dist/pack-slim/command.js +80 -0
  105. package/dist/pack-slim/domain/transform.js +221 -0
  106. package/dist/pack-slim/domain/types.js +2 -0
  107. package/dist/pack-slim/output.js +62 -0
  108. package/dist/pack-slim/sync.js +157 -0
  109. package/dist/pack-slim/working-tree.js +45 -0
  110. package/dist/tag/cli-result.js +1 -2
  111. package/dist/tag/cli-schema.js +1 -2
  112. package/dist/tag/command.js +1 -2
  113. package/dist/tag/domain/types.js +1 -2
  114. package/dist/tag/output.js +1 -2
  115. package/dist/tag/prepare.js +1 -2
  116. package/dist/tag/resolve-target-path.js +1 -2
  117. package/dist/tag/since-writer.js +1 -2
  118. package/dist/tag/sync.js +1 -2
  119. package/dist/tag/target-candidates.js +1 -2
  120. package/dist/tag/target-runner.js +1 -2
  121. package/dist/tag/version-resolver.js +1 -2
  122. package/package.json +8 -43
  123. package/dist/arrange/analyze.js.map +0 -1
  124. package/dist/arrange/cli-schema.js.map +0 -1
  125. package/dist/arrange/command.js.map +0 -1
  126. package/dist/arrange/domain/analyze-service.js.map +0 -1
  127. package/dist/arrange/domain/ast/ast-node.js.map +0 -1
  128. package/dist/arrange/domain/ast/collectors-cn.js.map +0 -1
  129. package/dist/arrange/domain/ast/collectors-jsx.js.map +0 -1
  130. package/dist/arrange/domain/ast/collectors-tv.js.map +0 -1
  131. package/dist/arrange/domain/ast/helpers.js.map +0 -1
  132. package/dist/arrange/domain/ast/simplify-targets.js.map +0 -1
  133. package/dist/arrange/domain/ast/targets.js.map +0 -1
  134. package/dist/arrange/domain/constants.js.map +0 -1
  135. package/dist/arrange/domain/grouping-service.js.map +0 -1
  136. package/dist/arrange/domain/grouping.js.map +0 -1
  137. package/dist/arrange/domain/imports.js.map +0 -1
  138. package/dist/arrange/domain/source-text-formatters.js.map +0 -1
  139. package/dist/arrange/domain/tailwind-token.js.map +0 -1
  140. package/dist/arrange/domain/token-classifier.js.map +0 -1
  141. package/dist/arrange/domain/types.js.map +0 -1
  142. package/dist/arrange/output.js.map +0 -1
  143. package/dist/arrange/process-file.js.map +0 -1
  144. package/dist/arrange/resolve-target.js.map +0 -1
  145. package/dist/arrange/scan-target.js.map +0 -1
  146. package/dist/arrange/simplify-process-file.js.map +0 -1
  147. package/dist/arrange/simplify-sync.js.map +0 -1
  148. package/dist/arrange/source-parse.js.map +0 -1
  149. package/dist/arrange/suggest.js.map +0 -1
  150. package/dist/arrange/sync.js.map +0 -1
  151. package/dist/arrange/typescript-ast-translator.js.map +0 -1
  152. package/dist/arrange/workspace.js.map +0 -1
  153. package/dist/audit/cli-schema.js.map +0 -1
  154. package/dist/audit/command.js.map +0 -1
  155. package/dist/audit/domain/audit-file.js.map +0 -1
  156. package/dist/audit/domain/comment-content.js.map +0 -1
  157. package/dist/audit/domain/comment-dividers.js.map +0 -1
  158. package/dist/audit/domain/link-references.js.map +0 -1
  159. package/dist/audit/domain/mappings.js.map +0 -1
  160. package/dist/audit/domain/markdown-links.js.map +0 -1
  161. package/dist/audit/domain/react-imports.js.map +0 -1
  162. package/dist/audit/domain/since-versions.js.map +0 -1
  163. package/dist/audit/domain/tokenize.js.map +0 -1
  164. package/dist/audit/domain/tsdoc-syntax.js.map +0 -1
  165. package/dist/audit/domain/types.js.map +0 -1
  166. package/dist/audit/output.js.map +0 -1
  167. package/dist/audit/prepare.js.map +0 -1
  168. package/dist/audit/run-comments.js.map +0 -1
  169. package/dist/audit/run-links.js.map +0 -1
  170. package/dist/audit/run-react.js.map +0 -1
  171. package/dist/audit/run.js.map +0 -1
  172. package/dist/bin.js.map +0 -1
  173. package/dist/cli.js.map +0 -1
  174. package/dist/core/cli/format-error.js.map +0 -1
  175. package/dist/core/cli/global-options.js.map +0 -1
  176. package/dist/core/cli/positional.js.map +0 -1
  177. package/dist/core/cli/result-handle.js.map +0 -1
  178. package/dist/core/config/loader.js.map +0 -1
  179. package/dist/core/config/schema.js.map +0 -1
  180. package/dist/core/config/warnings.js.map +0 -1
  181. package/dist/core/config.js.map +0 -1
  182. package/dist/core/errors.js.map +0 -1
  183. package/dist/core/exit-codes.js.map +0 -1
  184. package/dist/core/filesystem/node.js.map +0 -1
  185. package/dist/core/filesystem/port.js.map +0 -1
  186. package/dist/core/glob.js.map +0 -1
  187. package/dist/core/logger.js.map +0 -1
  188. package/dist/core/result.js.map +0 -1
  189. package/dist/core/schema-parse.js.map +0 -1
  190. package/dist/core/source-text-edit.js.map +0 -1
  191. package/dist/core/verbose-diagnostics.js.map +0 -1
  192. package/dist/core/workspace/markdown-walk.js.map +0 -1
  193. package/dist/core/workspace/package-version.js.map +0 -1
  194. package/dist/core/workspace/resolver.js.map +0 -1
  195. package/dist/core/workspace/skip-directories.js.map +0 -1
  196. package/dist/core/workspace/source-walk.js.map +0 -1
  197. package/dist/core/workspace/typescript-walk.js.map +0 -1
  198. package/dist/mirror/cli-result.js.map +0 -1
  199. package/dist/mirror/cli-schema.js.map +0 -1
  200. package/dist/mirror/command.js.map +0 -1
  201. package/dist/mirror/dist-filesystem-impl.js.map +0 -1
  202. package/dist/mirror/domain/constants.js.map +0 -1
  203. package/dist/mirror/domain/dirent-guard.js.map +0 -1
  204. package/dist/mirror/domain/dist-filesystem.js.map +0 -1
  205. package/dist/mirror/domain/errors.js.map +0 -1
  206. package/dist/mirror/domain/exports.js.map +0 -1
  207. package/dist/mirror/domain/package-display-name.js.map +0 -1
  208. package/dist/mirror/domain/path-normalizer.js.map +0 -1
  209. package/dist/mirror/domain/types.js.map +0 -1
  210. package/dist/mirror/output.js.map +0 -1
  211. package/dist/mirror/package-path.js.map +0 -1
  212. package/dist/mirror/prepare.js.map +0 -1
  213. package/dist/mirror/supplement-exports.js.map +0 -1
  214. package/dist/mirror/sync-reporter.js.map +0 -1
  215. package/dist/mirror/sync-types.js.map +0 -1
  216. package/dist/mirror/sync-workspace-package.js.map +0 -1
  217. package/dist/mirror/sync.js.map +0 -1
  218. package/dist/mirror/write-exports.js.map +0 -1
  219. package/dist/tag/cli-result.js.map +0 -1
  220. package/dist/tag/cli-schema.js.map +0 -1
  221. package/dist/tag/command.js.map +0 -1
  222. package/dist/tag/domain/types.js.map +0 -1
  223. package/dist/tag/output.js.map +0 -1
  224. package/dist/tag/prepare.js.map +0 -1
  225. package/dist/tag/resolve-target-path.js.map +0 -1
  226. package/dist/tag/since-writer.js.map +0 -1
  227. package/dist/tag/sync.js.map +0 -1
  228. package/dist/tag/target-candidates.js.map +0 -1
  229. package/dist/tag/target-runner.js.map +0 -1
  230. package/dist/tag/version-resolver.js.map +0 -1
@@ -1,10 +1,11 @@
1
1
  import process from "node:process";
2
2
  import { Command } from "commander";
3
- import { commentAuditRunRequestSchema, linkAuditRunRequestSchema, reactAuditRunRequestSchema, rtlAuditRunRequestSchema, } from "#/audit/cli-schema";
4
- import { exitCodeForCommentAuditResult, exitCodeForLinkAuditResult, exitCodeForReactAuditResult, exitCodeForRtlAuditResult, formatCommentAuditJsonOutput, formatLinkAuditJsonOutput, formatReactAuditJsonOutput, formatRtlAuditJsonOutput, presentCommentAuditResult, presentLinkAuditResult, presentReactAuditResult, presentRtlAuditResult, } from "#/audit/output";
5
- import { prepareCommentAudit, prepareLinkAudit, prepareReactAudit, prepareRtlAudit } from "#/audit/prepare";
3
+ import { commentAuditRunRequestSchema, linkAuditRunRequestSchema, reactAuditRunRequestSchema, rtlAuditRunRequestSchema, displayNameAuditRunRequestSchema, } from "#/audit/cli-schema";
4
+ import { exitCodeForCommentAuditResult, exitCodeForLinkAuditResult, exitCodeForReactAuditResult, exitCodeForRtlAuditResult, exitCodeForDisplayNameAuditResult, formatCommentAuditJsonOutput, formatLinkAuditJsonOutput, formatReactAuditJsonOutput, formatRtlAuditJsonOutput, formatDisplayNameAuditJsonOutput, presentCommentAuditResult, presentLinkAuditResult, presentReactAuditResult, presentRtlAuditResult, presentDisplayNameAuditResult, } from "#/audit/output";
5
+ import { prepareCommentAudit, prepareLinkAudit, prepareReactAudit, prepareRtlAudit, prepareDisplayNameAudit, } from "#/audit/prepare";
6
6
  import { runRtlAudit } from "#/audit/run";
7
7
  import { runCommentAudit } from "#/audit/run-comments";
8
+ import { runDisplayNameAudit } from "#/audit/run-display-names";
8
9
  import { runLinkAudit } from "#/audit/run-links";
9
10
  import { runReactAudit } from "#/audit/run-react";
10
11
  import { readOptionalPositionalArg } from "#/core/cli/positional";
@@ -137,6 +138,45 @@ export function createAuditCommand() {
137
138
  }
138
139
  process.exitCode = exitCodeForReactAuditResult(outcome.value);
139
140
  });
141
+ cmd
142
+ .command("display-names")
143
+ .description("Report token(), tag() and module display names that break the <namespace>:<Name> convention")
144
+ .argument("[target]", "Directory or file to scan (default: the repo root)")
145
+ .option("--json", "Print one JSON summary on stdout", false)
146
+ .action(async (target, opts) => {
147
+ const prelude = await prepareDisplayNameAudit(nodeFilesystem, {
148
+ currentWorkingDirectory: process.cwd(),
149
+ rawTarget: readOptionalPositionalArg(target),
150
+ });
151
+ if (!consumeCliAppError(prelude)) {
152
+ return;
153
+ }
154
+ const { rootDir, targetPath, allowlist } = prelude.value;
155
+ const parsed = parseWithSchema(displayNameAuditRunRequestSchema, {
156
+ rootDir,
157
+ targetPath,
158
+ allowlist,
159
+ json: !!opts.json,
160
+ });
161
+ if (!consumeCliAppError(parsed)) {
162
+ return;
163
+ }
164
+ const outcome = runDisplayNameAudit(nodeFilesystem, {
165
+ rootDir: parsed.value.rootDir,
166
+ targetPath: parsed.value.targetPath,
167
+ allowlist: parsed.value.allowlist ?? [],
168
+ });
169
+ if (!consumeCliAppError(outcome)) {
170
+ return;
171
+ }
172
+ if (parsed.value.json) {
173
+ logger.out(formatDisplayNameAuditJsonOutput(outcome.value, rootDir));
174
+ }
175
+ else {
176
+ presentDisplayNameAuditResult(outcome.value);
177
+ }
178
+ process.exitCode = exitCodeForDisplayNameAuditResult(outcome.value);
179
+ });
140
180
  cmd
141
181
  .command("comments")
142
182
  .description("Report section dividers that are not in the repo's one allowed form")
@@ -180,5 +220,4 @@ export function createAuditCommand() {
180
220
  process.exitCode = exitCodeForCommentAuditResult(outcome.value);
181
221
  });
182
222
  return cmd;
183
- }
184
- //# sourceMappingURL=command.js.map
223
+ }
@@ -7,7 +7,7 @@ import { collectTokens } from "#/audit/domain/tokenize";
7
7
  *
8
8
  * @since 0.5.0-canary.6
9
9
  */
10
- export function hasRtlCompanion(fileTokens, expectedValue) {
10
+ function hasRtlCompanion(fileTokens, expectedValue) {
11
11
  return fileTokens.some(({ variant, value }) => {
12
12
  if (!variant) {
13
13
  return false;
@@ -97,5 +97,4 @@ export function auditFileContent(content) {
97
97
  }
98
98
  }
99
99
  return violations;
100
- }
101
- //# sourceMappingURL=audit-file.js.map
100
+ }
@@ -174,5 +174,4 @@ function compareParams(parameterListText, documented) {
174
174
  }
175
175
  }
176
176
  return missing;
177
- }
178
- //# sourceMappingURL=comment-content.js.map
177
+ }
@@ -11,12 +11,31 @@ const RULE_GLYPH = "─";
11
11
  const LEAD_GLYPHS = "──";
12
12
  /** Every glyph a divider has historically been drawn with, so legacy forms are recognised too. */
13
13
  const RULE_CHARACTER_CLASS = String.raw `[-=─_*~#]`;
14
- const ruleOnlyLinePattern = new RegExp(String.raw `^(?<indent>[ \t]*)(?:\/\/|\/\*|\*)[ \t]*${RULE_CHARACTER_CLASS}{4,}[ \t]*(?:\*\/)?[ \t]*$`);
15
- const titledLinePattern = new RegExp(String.raw `^(?<indent>[ \t]*)(?:\/\/|\/\*)[ \t]*${RULE_CHARACTER_CLASS}{2,}[ \t]+(?<title>.*?)[ \t]+${RULE_CHARACTER_CLASS}{2,}[ \t]*(?:\*\/)?[ \t]*$`);
16
- const commentLinePattern = /^[ \t]*(?:\/\/|\/\*|\*)/;
17
- const bareRuleClosePattern = new RegExp(String.raw `^[ \t]*${RULE_CHARACTER_CLASS}{4,}[ \t]*\*\/[ \t]*$`);
14
+ // The comment lead each syntax opens a divider with — `#` for ignore files, slash forms for code.
15
+ const commentLeadByLanguage = {
16
+ css: String.raw `\/\/|\/\*|\*`,
17
+ ignore: String.raw `#`,
18
+ js: String.raw `\/\/|\/\*|\*`,
19
+ };
20
+ // One compiled pattern set per language — the comment lead is the only part that varies.
21
+ const patternsByLanguage = new Map();
22
+ function patternsFor(language) {
23
+ const cached = patternsByLanguage.get(language);
24
+ if (cached !== undefined) {
25
+ return cached;
26
+ }
27
+ const lead = commentLeadByLanguage[language];
28
+ const built = {
29
+ ruleOnly: new RegExp(String.raw `^(?<indent>[ \t]*)(?:${lead})[ \t]*${RULE_CHARACTER_CLASS}{4,}[ \t]*(?:\*\/)?[ \t]*$`),
30
+ titled: new RegExp(String.raw `^(?<indent>[ \t]*)(?:${lead})[ \t]*${RULE_CHARACTER_CLASS}{2,}[ \t]+(?<title>.*?)[ \t]+${RULE_CHARACTER_CLASS}{2,}[ \t]*(?:\*\/)?[ \t]*$`),
31
+ commentLine: new RegExp(String.raw `^[ \t]*(?:${lead})`),
32
+ bareRuleClose: new RegExp(String.raw `^[ \t]*${RULE_CHARACTER_CLASS}{4,}[ \t]*\*\/[ \t]*$`),
33
+ commentPrefix: new RegExp(String.raw `^[ \t]*(?:${lead})[ \t]?`),
34
+ };
35
+ patternsByLanguage.set(language, built);
36
+ return built;
37
+ }
18
38
  const rulesOnlyPattern = new RegExp(String.raw `^(?:${RULE_CHARACTER_CLASS}|[ \t])*$`);
19
- const commentPrefixPattern = /^[ \t]*(?:\/\/|\/\*|\*)[ \t]?/;
20
39
  const commentSuffixPattern = /[ \t]*\*\/[ \t]*$/;
21
40
  /** A banner spanning more lines than this is prose that happens to start with a rule, not a divider. */
22
41
  const MAX_BANNER_SPAN = 16;
@@ -36,7 +55,8 @@ export function renderDivider(indent, title, language) {
36
55
  const head = `${indent}/* ${LEAD_GLYPHS} ${title} `;
37
56
  return `${head}${RULE_GLYPH.repeat(Math.max(2, DIVIDER_COLUMN - head.length - 3))} */`;
38
57
  }
39
- const head = `${indent}// ${LEAD_GLYPHS} ${title} `;
58
+ const prefix = language === "ignore" ? "#" : "//";
59
+ const head = `${indent}${prefix} ${LEAD_GLYPHS} ${title} `;
40
60
  return `${head}${RULE_GLYPH.repeat(Math.max(2, DIVIDER_COLUMN - head.length))}`;
41
61
  }
42
62
  /**
@@ -80,7 +100,8 @@ export function applyCommentDividerFixes(content, language) {
80
100
  }
81
101
  function readRegionAt(lines, index, language) {
82
102
  const line = lines[index];
83
- const titled = titledLinePattern.exec(line);
103
+ const patterns = patternsFor(language);
104
+ const titled = patterns.titled.exec(line);
84
105
  const title = titled?.groups?.title?.trim();
85
106
  if (titled !== null && title !== undefined && title.length > 0 && !rulesOnlyPattern.test(title)) {
86
107
  const indent = titled.groups?.indent ?? "";
@@ -94,26 +115,29 @@ function readRegionAt(lines, index, language) {
94
115
  defect: line === canonical ? null : usesCanonicalGlyphs(line) ? "bad-width" : "legacy-form",
95
116
  };
96
117
  }
97
- const ruleOnly = ruleOnlyLinePattern.exec(line);
118
+ const ruleOnly = patterns.ruleOnly.exec(line);
98
119
  if (ruleOnly === null) {
99
120
  return null;
100
121
  }
101
- return readBannerAt(lines, index, ruleOnly.groups?.indent ?? "");
122
+ return readBannerAt(lines, index, ruleOnly.groups?.indent ?? "", language);
102
123
  }
103
- function readBannerAt(lines, index, indent) {
124
+ function readBannerAt(lines, index, indent, language) {
104
125
  const line = lines[index];
126
+ const patterns = patternsFor(language);
105
127
  // An unterminated `/*` opens a block whose body needs no per-line marker, so the run ends at `*/`.
106
128
  const insideBlock = line.trimStart().startsWith("/*") && !line.includes("*/");
107
129
  const body = [];
108
130
  let cursor = index + 1;
109
- while (cursor < lines.length && cursor - index <= MAX_BANNER_SPAN && !isBannerClose(lines[cursor], insideBlock)) {
110
- if (!insideBlock && !commentLinePattern.test(lines[cursor])) {
131
+ while (cursor < lines.length &&
132
+ cursor - index <= MAX_BANNER_SPAN &&
133
+ !isBannerClose(lines[cursor], insideBlock, language)) {
134
+ if (!insideBlock && !patterns.commentLine.test(lines[cursor])) {
111
135
  break;
112
136
  }
113
- body.push(stripCommentPrefix(lines[cursor]));
137
+ body.push(stripCommentPrefix(lines[cursor], language));
114
138
  cursor++;
115
139
  }
116
- const closed = cursor < lines.length && cursor - index <= MAX_BANNER_SPAN && isBannerClose(lines[cursor], insideBlock);
140
+ const closed = cursor < lines.length && cursor - index <= MAX_BANNER_SPAN && isBannerClose(lines[cursor], insideBlock, language);
117
141
  const meaningful = body.filter((entry) => entry.length > 0);
118
142
  // A frame around prose is a doc block, and an unclosed rule is prose formatting inside one.
119
143
  if (!closed || meaningful.length !== 1 || !looksLikeTitle(meaningful[0])) {
@@ -131,16 +155,19 @@ function readBannerAt(lines, index, indent) {
131
155
  function looksLikeTitle(text) {
132
156
  return text.length <= MAX_TITLE_LENGTH && !text.endsWith(".");
133
157
  }
134
- function isBannerClose(line, insideBlock) {
135
- if (ruleOnlyLinePattern.test(line)) {
158
+ function isBannerClose(line, insideBlock, language) {
159
+ const patterns = patternsFor(language);
160
+ if (patterns.ruleOnly.test(line)) {
136
161
  return true;
137
162
  }
138
- return insideBlock && bareRuleClosePattern.test(line);
163
+ return insideBlock && patterns.bareRuleClose.test(line);
139
164
  }
140
165
  function usesCanonicalGlyphs(line) {
141
- return line.trimStart().startsWith(`// ${LEAD_GLYPHS} `) || line.trimStart().startsWith(`/* ${LEAD_GLYPHS} `);
142
- }
143
- function stripCommentPrefix(line) {
144
- return line.replace(commentPrefixPattern, "").replace(commentSuffixPattern, "").trim();
166
+ const trimmed = line.trimStart();
167
+ return (trimmed.startsWith(`// ${LEAD_GLYPHS} `) ||
168
+ trimmed.startsWith(`/* ${LEAD_GLYPHS} `) ||
169
+ trimmed.startsWith(`# ${LEAD_GLYPHS} `));
145
170
  }
146
- //# sourceMappingURL=comment-dividers.js.map
171
+ function stripCommentPrefix(line, language) {
172
+ return line.replace(patternsFor(language).commentPrefix, "").replace(commentSuffixPattern, "").trim();
173
+ }
@@ -0,0 +1,71 @@
1
+ /** The owner: a kebab-case package, app or feature slug, or a scoped package name. */
2
+ const NAMESPACE = /^(?:@[a-z0-9-]+\/)?[a-z0-9]+(?:-[a-z0-9]+)*$/;
3
+ /** A token or module stands for a type or a unit of composition. */
4
+ const PASCAL_CASE = /^[A-Z][A-Za-z0-9]*$/;
5
+ /** A tag key stands for an attribute a request selects on, so it reads like a property. */
6
+ const CAMEL_CASE = /^[a-z][A-Za-z0-9]*$/;
7
+ /**
8
+ * A bare `token(…)` / `tag(…)` call, or a module factory call, whose first argument is a string literal.
9
+ *
10
+ * @remarks Generic arguments are skipped up to four levels of nesting, which is deeper than any
11
+ * declaration in the repo. Template literals are not matched: an interpolated name has no fixed
12
+ * text to check. The closing parenthesis is kept when the name is the only argument, so the
13
+ * reported text reads as the call was written.
14
+ */
15
+ const DISPLAY_NAME_CALL = /(?<![\w$.])(?:(token|tag)\s*(?:<(?:[^<>]|<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)*>)?|((?:Sync|Async)?Module)\s*\.\s*create(?:Async)?)\s*\(\s*(["'])((?:(?!\3)[^\\\n]|\\.)*)\3(\s*\))?/g;
16
+ const KIND_LABEL = {
17
+ token: "token display name",
18
+ tag: "tag key",
19
+ module: "module name",
20
+ };
21
+ /**
22
+ * Scans one source or markdown text for `token()`, `tag()` and module display names that break the convention.
23
+ *
24
+ * @remarks Runs on markdown as well as TypeScript because a doc sample is what a reader copies:
25
+ * a convention the docs break is not one the docs teach.
26
+ *
27
+ * @since 0.9.0
28
+ */
29
+ export function auditDisplayNames(sourceText) {
30
+ const violations = [];
31
+ for (const match of sourceText.matchAll(DISPLAY_NAME_CALL)) {
32
+ const kind = match[1] === undefined ? "module" : match[1];
33
+ const reason = reasonFor(kind, match[4]);
34
+ if (reason === null) {
35
+ continue;
36
+ }
37
+ violations.push({ line: lineOfOffset(sourceText, match.index), raw: match[0], reason });
38
+ }
39
+ return violations;
40
+ }
41
+ /** Why a display name breaks the convention, or `null` when it holds. */
42
+ function reasonFor(kind, name) {
43
+ const segments = name.split(":");
44
+ const label = KIND_LABEL[kind];
45
+ if (segments.length < 2) {
46
+ return `${label} '${name}' has no namespace — write '<namespace>:${name}'`;
47
+ }
48
+ const local = segments.at(-1);
49
+ for (const namespace of segments.slice(0, -1)) {
50
+ if (!NAMESPACE.test(namespace)) {
51
+ return `namespace '${namespace}' in '${name}' is not kebab-case (or a scoped package name)`;
52
+ }
53
+ }
54
+ if (kind === "tag") {
55
+ return CAMEL_CASE.test(local)
56
+ ? null
57
+ : `${label} '${local}' in '${name}' is not camelCase — a tag key names an attribute`;
58
+ }
59
+ return PASCAL_CASE.test(local)
60
+ ? null
61
+ : `${label} '${local}' in '${name}' is not PascalCase — a ${kind} stands for a ${kind === "token" ? "type" : "unit of composition"}`;
62
+ }
63
+ function lineOfOffset(sourceText, offset) {
64
+ let line = 1;
65
+ for (let index = 0; index < offset; index++) {
66
+ if (sourceText.charCodeAt(index) === 10) {
67
+ line++;
68
+ }
69
+ }
70
+ return line;
71
+ }
@@ -60,5 +60,4 @@ export function countHeadMentions(contents, heads) {
60
60
  }
61
61
  }
62
62
  return counts;
63
- }
64
- //# sourceMappingURL=link-references.js.map
63
+ }
@@ -101,5 +101,4 @@ export const SLIDE_PREFIXES = [
101
101
  "slide-in-from-right",
102
102
  "slide-out-to-left",
103
103
  "slide-out-to-right",
104
- ];
105
- //# sourceMappingURL=mappings.js.map
104
+ ];
@@ -72,5 +72,4 @@ function lineNumberAt(content, index) {
72
72
  }
73
73
  }
74
74
  return line;
75
- }
76
- //# sourceMappingURL=markdown-links.js.map
75
+ }
@@ -88,5 +88,4 @@ function lineOfOffset(sourceText, offset) {
88
88
  function firstLineOf(text) {
89
89
  const newlineIndex = text.indexOf("\n");
90
90
  return newlineIndex === -1 ? text : text.slice(0, newlineIndex);
91
- }
92
- //# sourceMappingURL=react-imports.js.map
91
+ }
@@ -82,5 +82,4 @@ export function scanImpossibleSinceTags(content, packageVersion) {
82
82
  }
83
83
  }
84
84
  return findings;
85
- }
86
- //# sourceMappingURL=since-versions.js.map
85
+ }
@@ -52,7 +52,7 @@ export function splitClassName(token) {
52
52
  *
53
53
  * @since 0.5.0-canary.6
54
54
  */
55
- export function* stringLiterals(content) {
55
+ function* stringLiterals(content) {
56
56
  const pattern = /"([^"\n]*)"|'([^'\n]*)'|`([^`]*)`/g;
57
57
  for (const match of content.matchAll(pattern)) {
58
58
  const text = match[1] ?? match[2] ?? match[3] ?? "";
@@ -81,5 +81,4 @@ export function collectTokens(content) {
81
81
  }
82
82
  }
83
83
  return tokens;
84
- }
85
- //# sourceMappingURL=tokenize.js.map
84
+ }
@@ -29,5 +29,4 @@ export function scanTsdocSyntax(content) {
29
29
  }
30
30
  }
31
31
  return findings;
32
- }
33
- //# sourceMappingURL=tsdoc-syntax.js.map
32
+ }
@@ -1,2 +1 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map
1
+ export {};
@@ -170,4 +170,44 @@ export function formatCommentAuditJsonOutput(result, rootDir) {
170
170
  function truncate(raw) {
171
171
  return raw.length <= 60 ? raw : `${raw.slice(0, 57)}…`;
172
172
  }
173
- //# sourceMappingURL=output.js.map
173
+ /**
174
+ * Exit `1` when any non-allowlisted display-name violation remains.
175
+ *
176
+ * @since 0.9.0
177
+ */
178
+ export function exitCodeForDisplayNameAuditResult(result) {
179
+ return result.violationCount > 0 ? CLI_EXIT_GENERAL_ERROR : CLI_EXIT_SUCCESS;
180
+ }
181
+ /**
182
+ * Human-readable display-name report.
183
+ *
184
+ * @since 0.9.0
185
+ */
186
+ export function presentDisplayNameAuditResult(result) {
187
+ for (const file of result.files) {
188
+ logger.out(`\n${file.relativePath}`);
189
+ for (const { line, raw, reason } of file.violations) {
190
+ logger.out(` ${line}: ${raw} → ${reason}`);
191
+ }
192
+ }
193
+ const allowlistSuffix = result.allowlistedCount > 0 ? ` (${result.allowlistedCount} allowlisted)` : "";
194
+ if (result.violationCount > 0) {
195
+ logger.out(`\n✖ ${result.violationCount} display name(s) off the convention${allowlistSuffix}`);
196
+ }
197
+ else {
198
+ logger.out(`✓ Every token, tag and module display name follows <namespace>:<Name> across ${result.scannedFileCount} file(s)${allowlistSuffix}`);
199
+ }
200
+ }
201
+ /**
202
+ * Machine-readable display-name summary for `--json`.
203
+ *
204
+ * @since 0.9.0
205
+ */
206
+ export function formatDisplayNameAuditJsonOutput(result, rootDir) {
207
+ return JSON.stringify({
208
+ schemaVersion: 1,
209
+ ok: result.violationCount === 0,
210
+ cwd: rootDir,
211
+ result,
212
+ });
213
+ }
@@ -132,4 +132,34 @@ export async function prepareCommentAudit(fs, args) {
132
132
  allowlist: commentsConfig.allowlist ?? [],
133
133
  });
134
134
  }
135
- //# sourceMappingURL=prepare.js.map
135
+ /**
136
+ * Loads config and resolves the scan target for `audit display-names`.
137
+ *
138
+ * @remarks Defaults to the repo root: a display name collides across packages, so the convention
139
+ * has to hold across them.
140
+ *
141
+ * @since 0.9.0
142
+ */
143
+ export async function prepareDisplayNameAudit(fs, args) {
144
+ let rootDir;
145
+ try {
146
+ rootDir = fs.canonicalPathSync(findRepoRoot(args.currentWorkingDirectory, fs));
147
+ }
148
+ catch (caughtError) {
149
+ return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
150
+ }
151
+ const loadedOutcome = await loadCodefastConfig(rootDir, fs);
152
+ if (!loadedOutcome.ok) {
153
+ return loadedOutcome;
154
+ }
155
+ const displayNamesConfig = loadedOutcome.value.config.audit?.displayNames ?? {};
156
+ const targetPath = args.rawTarget === undefined ? rootDir : resolveRepoRelativePath(args.currentWorkingDirectory, args.rawTarget);
157
+ if (!fs.existsSync(targetPath)) {
158
+ return err(new AppError("NOT_FOUND", `Not found: ${targetPath}`));
159
+ }
160
+ return ok({
161
+ rootDir,
162
+ targetPath: fs.canonicalPathSync(targetPath),
163
+ allowlist: displayNamesConfig.allowlist ?? [],
164
+ });
165
+ }
@@ -68,30 +68,34 @@ export function runCommentAudit(fs, args) {
68
68
  }
69
69
  breakages.push({ line: region.startLine, raw: region.raw, reason: reasonByDefect[region.defect] });
70
70
  }
71
- for (const finding of scanCommentContent(content, language)) {
72
- if (allowlist.has(finding.raw) || allowlist.has(`${relativePath}:${finding.raw}`)) {
73
- allowlistedCount++;
74
- continue;
75
- }
76
- breakages.push({ line: finding.line, raw: finding.raw, reason: reasonByDefect[finding.defect] });
77
- }
78
- const packageVersion = nearestVersion(fs, absolutePath, versionByDirectory);
79
- if (packageVersion !== null) {
80
- for (const finding of scanImpossibleSinceTags(content, packageVersion)) {
71
+ // The content rules — banned fragments, `@since`, TSDoc grammar — govern doc comments in
72
+ // code; an ignore file carries only the divider convention.
73
+ if (language !== "ignore") {
74
+ for (const finding of scanCommentContent(content, language)) {
81
75
  if (allowlist.has(finding.raw) || allowlist.has(`${relativePath}:${finding.raw}`)) {
82
76
  allowlistedCount++;
83
77
  continue;
84
78
  }
85
- breakages.push({ line: finding.line, raw: finding.raw, reason: reasonByDefect["since-impossible"] });
79
+ breakages.push({ line: finding.line, raw: finding.raw, reason: reasonByDefect[finding.defect] });
86
80
  }
87
- }
88
- if (language === "js") {
89
- for (const finding of scanTsdocSyntax(content)) {
90
- if (allowlist.has(finding.raw) || allowlist.has(`${relativePath}:${finding.raw}`)) {
91
- allowlistedCount++;
92
- continue;
81
+ const packageVersion = nearestVersion(fs, absolutePath, versionByDirectory);
82
+ if (packageVersion !== null) {
83
+ for (const finding of scanImpossibleSinceTags(content, packageVersion)) {
84
+ if (allowlist.has(finding.raw) || allowlist.has(`${relativePath}:${finding.raw}`)) {
85
+ allowlistedCount++;
86
+ continue;
87
+ }
88
+ breakages.push({ line: finding.line, raw: finding.raw, reason: reasonByDefect["since-impossible"] });
89
+ }
90
+ }
91
+ if (language === "js") {
92
+ for (const finding of scanTsdocSyntax(content)) {
93
+ if (allowlist.has(finding.raw) || allowlist.has(`${relativePath}:${finding.raw}`)) {
94
+ allowlistedCount++;
95
+ continue;
96
+ }
97
+ breakages.push({ line: finding.line, raw: finding.raw, reason: finding.reason });
93
98
  }
94
- breakages.push({ line: finding.line, raw: finding.raw, reason: finding.reason });
95
99
  }
96
100
  }
97
101
  if (breakages.length > 0) {
@@ -186,5 +190,4 @@ function collectScanPaths(fs, targetPath) {
186
190
  }
187
191
  function toPosixPath(filePath) {
188
192
  return filePath.split(path.sep).join("/");
189
- }
190
- //# sourceMappingURL=run-comments.js.map
193
+ }
@@ -0,0 +1,60 @@
1
+ import path from "node:path";
2
+ import { auditDisplayNames } from "#/audit/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
+ /**
8
+ * Trees the convention does not reach: a test or benchmark token is scoped by its file and never
9
+ * meets another author's, and a changelog quotes names as they were.
10
+ */
11
+ const SKIPPED_SEGMENTS = new Set(["tests", "benchmarks", ".changeset"]);
12
+ const SKIPPED_BASENAMES = new Set(["CHANGELOG.md"]);
13
+ /**
14
+ * Scans a target path for `token()`, `tag()` and module display names that break the convention.
15
+ *
16
+ * @since 0.9.0
17
+ */
18
+ export function runDisplayNameAudit(fs, args) {
19
+ try {
20
+ const allowlist = new Set(args.allowlist);
21
+ const { rootDir, targetPath } = args;
22
+ const filesToScan = collectScanPaths(fs, rootDir, targetPath);
23
+ const files = [];
24
+ let violationCount = 0;
25
+ let allowlistedCount = 0;
26
+ for (const absolutePath of filesToScan) {
27
+ const relativePath = toPosixPath(path.relative(rootDir, absolutePath));
28
+ const content = fs.readFileSync(absolutePath, "utf8");
29
+ const remaining = auditDisplayNames(content).filter(({ raw }) => {
30
+ const isAllowed = allowlist.has(raw) || allowlist.has(`${relativePath}:${raw}`);
31
+ if (isAllowed) {
32
+ allowlistedCount++;
33
+ }
34
+ return !isAllowed;
35
+ });
36
+ if (remaining.length === 0) {
37
+ continue;
38
+ }
39
+ violationCount += remaining.length;
40
+ files.push({ relativePath, violations: remaining });
41
+ }
42
+ return ok({ files, violationCount, allowlistedCount, scannedFileCount: filesToScan.length });
43
+ }
44
+ catch (caughtError) {
45
+ return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
46
+ }
47
+ }
48
+ function collectScanPaths(fs, rootDir, targetPath) {
49
+ const stats = fs.statSync(targetPath);
50
+ const candidates = stats.isFile()
51
+ ? [targetPath]
52
+ : [...walkTsxFiles(targetPath, fs), ...walkMarkdownFiles(targetPath, fs)];
53
+ return candidates.filter((absolutePath) => {
54
+ const relative = path.relative(rootDir, absolutePath).split(path.sep);
55
+ return !relative.some((segment) => SKIPPED_SEGMENTS.has(segment)) && !SKIPPED_BASENAMES.has(relative.at(-1));
56
+ });
57
+ }
58
+ function toPosixPath(filePath) {
59
+ return filePath.split(path.sep).join("/");
60
+ }
@@ -88,5 +88,4 @@ function collectScanPaths(fs, targetPath) {
88
88
  }
89
89
  function toPosixPath(filePath) {
90
90
  return filePath.split(path.sep).join("/");
91
- }
92
- //# sourceMappingURL=run-links.js.map
91
+ }
@@ -52,5 +52,4 @@ function collectScanPaths(fs, targetPath) {
52
52
  }
53
53
  function toPosixPath(filePath) {
54
54
  return filePath.split(path.sep).join("/");
55
- }
56
- //# sourceMappingURL=run-react.js.map
55
+ }
package/dist/audit/run.js CHANGED
@@ -53,5 +53,4 @@ function collectScanPaths(fs, targetPath) {
53
53
  }
54
54
  function toPosixPath(filePath) {
55
55
  return filePath.split(path.sep).join("/");
56
- }
57
- //# sourceMappingURL=run.js.map
56
+ }
package/dist/bin.js CHANGED
@@ -2,5 +2,4 @@
2
2
  import process from "node:process";
3
3
  import { runCli } from "#/cli";
4
4
  const code = await runCli(process.argv);
5
- process.exit(code);
6
- //# sourceMappingURL=bin.js.map
5
+ process.exit(code);
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import { Command } from "commander";
6
6
  import { createArrangeCommand } from "#/arrange/command";
7
7
  import { createAuditCommand } from "#/audit/command";
8
8
  import { createMirrorCommand } from "#/mirror/command";
9
+ import { createPackSlimCommand } from "#/pack-slim/command";
9
10
  import { createTagCommand } from "#/tag/command";
10
11
  function readVersion() {
11
12
  try {
@@ -39,9 +40,9 @@ export async function runCli(argv) {
39
40
  program.addCommand(createArrangeCommand());
40
41
  program.addCommand(createAuditCommand());
41
42
  program.addCommand(createMirrorCommand());
43
+ program.addCommand(createPackSlimCommand());
42
44
  program.addCommand(createTagCommand());
43
45
  await program.parseAsync(argv, { from: "node" });
44
46
  const code = process.exitCode ?? 0;
45
47
  return typeof code === "number" ? code : Number(code) || 0;
46
- }
47
- //# sourceMappingURL=cli.js.map
48
+ }
@@ -17,5 +17,4 @@ export function formatAppError(error) {
17
17
  return `Unhandled error code: ${String(exhaustive)}`;
18
18
  }
19
19
  }
20
- }
21
- //# sourceMappingURL=format-error.js.map
20
+ }
@@ -6,5 +6,4 @@ import { z } from "zod";
6
6
  */
7
7
  export const globalCliCommanderOptionsSchema = z.object({
8
8
  color: z.boolean().optional(),
9
- });
10
- //# sourceMappingURL=global-options.js.map
9
+ });
@@ -5,5 +5,4 @@
5
5
  */
6
6
  export function readOptionalPositionalArg(candidate) {
7
7
  return typeof candidate === "string" ? candidate : undefined;
8
- }
9
- //# sourceMappingURL=positional.js.map
8
+ }