@codefast/cli 0.7.0 → 0.8.1

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/README.md +152 -37
  2. package/dist/arrange/analyze.js +3 -2
  3. package/dist/arrange/cli-schema.js +7 -2
  4. package/dist/arrange/command.js +3 -2
  5. package/dist/arrange/domain/analyze-service.js +3 -2
  6. package/dist/arrange/domain/ast/ast-node.js +35 -2
  7. package/dist/arrange/domain/ast/collectors-cn.js +9 -2
  8. package/dist/arrange/domain/ast/collectors-jsx.js +3 -2
  9. package/dist/arrange/domain/ast/collectors-tv.js +11 -2
  10. package/dist/arrange/domain/ast/helpers.js +5 -2
  11. package/dist/arrange/domain/ast/simplify-targets.js +1 -2
  12. package/dist/arrange/domain/ast/targets.js +7 -2
  13. package/dist/arrange/domain/constants.js +1 -2
  14. package/dist/arrange/domain/grouping-service.js +15 -2
  15. package/dist/arrange/domain/grouping.js +3 -2
  16. package/dist/arrange/domain/imports.js +3 -2
  17. package/dist/arrange/domain/source-text-formatters.js +9 -2
  18. package/dist/arrange/domain/tailwind-token.js +3 -2
  19. package/dist/arrange/domain/token-classifier.js +5 -2
  20. package/dist/arrange/domain/types.js +1 -2
  21. package/dist/arrange/output.js +9 -2
  22. package/dist/arrange/process-file.js +3 -2
  23. package/dist/arrange/resolve-target.js +3 -2
  24. package/dist/arrange/scan-target.js +3 -2
  25. package/dist/arrange/simplify-process-file.js +3 -2
  26. package/dist/arrange/simplify-sync.js +3 -2
  27. package/dist/arrange/source-parse.js +3 -2
  28. package/dist/arrange/suggest.js +3 -2
  29. package/dist/arrange/sync.js +3 -2
  30. package/dist/arrange/typescript-ast-translator.js +3 -2
  31. package/dist/arrange/workspace.js +3 -2
  32. package/dist/audit/cli-schema.js +14 -4
  33. package/dist/audit/command.js +45 -6
  34. package/dist/audit/domain/audit-file.js +4 -5
  35. package/dist/audit/domain/comment-content.js +1 -2
  36. package/dist/audit/domain/comment-dividers.js +1 -2
  37. package/dist/audit/domain/link-references.js +1 -2
  38. package/dist/audit/domain/mappings.js +10 -9
  39. package/dist/audit/domain/markdown-links.js +1 -2
  40. package/dist/audit/domain/react-imports.js +91 -0
  41. package/dist/audit/domain/since-versions.js +85 -0
  42. package/dist/audit/domain/tokenize.js +7 -6
  43. package/dist/audit/domain/tsdoc-syntax.js +1 -2
  44. package/dist/audit/domain/types.js +1 -2
  45. package/dist/audit/output.js +45 -5
  46. package/dist/audit/prepare.js +33 -3
  47. package/dist/audit/run-comments.js +26 -2
  48. package/dist/audit/run-links.js +1 -2
  49. package/dist/audit/run-react.js +55 -0
  50. package/dist/audit/run.js +2 -3
  51. package/dist/bin.js +1 -2
  52. package/dist/cli.js +5 -2
  53. package/dist/core/cli/format-error.js +3 -2
  54. package/dist/core/cli/global-options.js +1 -2
  55. package/dist/core/cli/positional.js +1 -2
  56. package/dist/core/cli/result-handle.js +5 -20
  57. package/dist/core/config/loader.js +3 -2
  58. package/dist/core/config/schema.js +27 -9
  59. package/dist/core/config/warnings.js +3 -2
  60. package/dist/core/config.js +3 -2
  61. package/dist/core/errors.js +3 -2
  62. package/dist/core/exit-codes.js +5 -2
  63. package/dist/core/filesystem/node.js +3 -2
  64. package/dist/core/filesystem/port.js +1 -2
  65. package/dist/core/glob.js +1 -2
  66. package/dist/core/logger.js +3 -2
  67. package/dist/core/result.js +5 -2
  68. package/dist/core/schema-parse.js +3 -2
  69. package/dist/core/source-text-edit.js +9 -2
  70. package/dist/core/verbose-diagnostics.js +3 -2
  71. package/dist/core/workspace/markdown-walk.js +1 -2
  72. package/dist/core/workspace/package-version.js +24 -0
  73. package/dist/core/workspace/resolver.js +3 -2
  74. package/dist/core/workspace/skip-directories.js +3 -2
  75. package/dist/core/workspace/source-walk.js +1 -2
  76. package/dist/core/workspace/typescript-walk.js +3 -2
  77. package/dist/mirror/cli-result.js +5 -2
  78. package/dist/mirror/cli-schema.js +3 -2
  79. package/dist/mirror/command.js +3 -2
  80. package/dist/mirror/dist-filesystem-impl.js +3 -2
  81. package/dist/mirror/domain/constants.js +11 -2
  82. package/dist/mirror/domain/dirent-guard.js +1 -2
  83. package/dist/mirror/domain/dist-filesystem.js +1 -2
  84. package/dist/mirror/domain/errors.js +5 -2
  85. package/dist/mirror/domain/exports.js +5 -2
  86. package/dist/mirror/domain/package-display-name.js +1 -2
  87. package/dist/mirror/domain/path-normalizer.js +3 -2
  88. package/dist/mirror/domain/types.js +1 -2
  89. package/dist/mirror/output.js +3 -2
  90. package/dist/mirror/package-path.js +5 -2
  91. package/dist/mirror/prepare.js +3 -2
  92. package/dist/mirror/supplement-exports.js +3 -2
  93. package/dist/mirror/sync-reporter.js +1 -2
  94. package/dist/mirror/sync-types.js +1 -2
  95. package/dist/mirror/sync-workspace-package.js +3 -2
  96. package/dist/mirror/sync.js +3 -2
  97. package/dist/mirror/write-exports.js +1 -2
  98. package/dist/pack-slim/cli-result.js +23 -0
  99. package/dist/pack-slim/cli-schema.js +11 -0
  100. package/dist/pack-slim/command.js +79 -0
  101. package/dist/pack-slim/domain/transform.js +95 -0
  102. package/dist/pack-slim/domain/types.js +2 -0
  103. package/dist/pack-slim/output.js +53 -0
  104. package/dist/pack-slim/sync.js +151 -0
  105. package/dist/pack-slim/working-tree.js +45 -0
  106. package/dist/tag/cli-result.js +3 -2
  107. package/dist/tag/cli-schema.js +3 -2
  108. package/dist/tag/command.js +3 -2
  109. package/dist/tag/domain/types.js +1 -2
  110. package/dist/tag/output.js +5 -2
  111. package/dist/tag/prepare.js +3 -2
  112. package/dist/tag/resolve-target-path.js +3 -2
  113. package/dist/tag/since-writer.js +3 -2
  114. package/dist/tag/sync.js +3 -2
  115. package/dist/tag/target-candidates.js +3 -2
  116. package/dist/tag/target-runner.js +3 -2
  117. package/dist/tag/version-resolver.js +8 -23
  118. package/package.json +9 -16
  119. package/dist/arrange/analyze.js.map +0 -1
  120. package/dist/arrange/cli-schema.js.map +0 -1
  121. package/dist/arrange/command.js.map +0 -1
  122. package/dist/arrange/domain/analyze-service.js.map +0 -1
  123. package/dist/arrange/domain/ast/ast-node.js.map +0 -1
  124. package/dist/arrange/domain/ast/collectors-cn.js.map +0 -1
  125. package/dist/arrange/domain/ast/collectors-jsx.js.map +0 -1
  126. package/dist/arrange/domain/ast/collectors-tv.js.map +0 -1
  127. package/dist/arrange/domain/ast/helpers.js.map +0 -1
  128. package/dist/arrange/domain/ast/simplify-targets.js.map +0 -1
  129. package/dist/arrange/domain/ast/targets.js.map +0 -1
  130. package/dist/arrange/domain/constants.js.map +0 -1
  131. package/dist/arrange/domain/grouping-service.js.map +0 -1
  132. package/dist/arrange/domain/grouping.js.map +0 -1
  133. package/dist/arrange/domain/imports.js.map +0 -1
  134. package/dist/arrange/domain/source-text-formatters.js.map +0 -1
  135. package/dist/arrange/domain/tailwind-token.js.map +0 -1
  136. package/dist/arrange/domain/token-classifier.js.map +0 -1
  137. package/dist/arrange/domain/types.js.map +0 -1
  138. package/dist/arrange/output.js.map +0 -1
  139. package/dist/arrange/process-file.js.map +0 -1
  140. package/dist/arrange/resolve-target.js.map +0 -1
  141. package/dist/arrange/scan-target.js.map +0 -1
  142. package/dist/arrange/simplify-process-file.js.map +0 -1
  143. package/dist/arrange/simplify-sync.js.map +0 -1
  144. package/dist/arrange/source-parse.js.map +0 -1
  145. package/dist/arrange/suggest.js.map +0 -1
  146. package/dist/arrange/sync.js.map +0 -1
  147. package/dist/arrange/typescript-ast-translator.js.map +0 -1
  148. package/dist/arrange/workspace.js.map +0 -1
  149. package/dist/audit/cli-schema.js.map +0 -1
  150. package/dist/audit/command.js.map +0 -1
  151. package/dist/audit/domain/audit-file.js.map +0 -1
  152. package/dist/audit/domain/comment-content.js.map +0 -1
  153. package/dist/audit/domain/comment-dividers.js.map +0 -1
  154. package/dist/audit/domain/link-references.js.map +0 -1
  155. package/dist/audit/domain/mappings.js.map +0 -1
  156. package/dist/audit/domain/markdown-links.js.map +0 -1
  157. package/dist/audit/domain/tokenize.js.map +0 -1
  158. package/dist/audit/domain/tsdoc-syntax.js.map +0 -1
  159. package/dist/audit/domain/types.js.map +0 -1
  160. package/dist/audit/output.js.map +0 -1
  161. package/dist/audit/prepare.js.map +0 -1
  162. package/dist/audit/run-comments.js.map +0 -1
  163. package/dist/audit/run-links.js.map +0 -1
  164. package/dist/audit/run.js.map +0 -1
  165. package/dist/bin.js.map +0 -1
  166. package/dist/cli.js.map +0 -1
  167. package/dist/core/cli/format-error.js.map +0 -1
  168. package/dist/core/cli/global-options.js.map +0 -1
  169. package/dist/core/cli/positional.js.map +0 -1
  170. package/dist/core/cli/result-handle.js.map +0 -1
  171. package/dist/core/config/loader.js.map +0 -1
  172. package/dist/core/config/schema.js.map +0 -1
  173. package/dist/core/config/warnings.js.map +0 -1
  174. package/dist/core/config.js.map +0 -1
  175. package/dist/core/errors.js.map +0 -1
  176. package/dist/core/exit-codes.js.map +0 -1
  177. package/dist/core/filesystem/node.js.map +0 -1
  178. package/dist/core/filesystem/port.js.map +0 -1
  179. package/dist/core/glob.js.map +0 -1
  180. package/dist/core/logger.js.map +0 -1
  181. package/dist/core/result.js.map +0 -1
  182. package/dist/core/schema-parse.js.map +0 -1
  183. package/dist/core/source-text-edit.js.map +0 -1
  184. package/dist/core/verbose-diagnostics.js.map +0 -1
  185. package/dist/core/workspace/markdown-walk.js.map +0 -1
  186. package/dist/core/workspace/resolver.js.map +0 -1
  187. package/dist/core/workspace/skip-directories.js.map +0 -1
  188. package/dist/core/workspace/source-walk.js.map +0 -1
  189. package/dist/core/workspace/typescript-walk.js.map +0 -1
  190. package/dist/mirror/cli-result.js.map +0 -1
  191. package/dist/mirror/cli-schema.js.map +0 -1
  192. package/dist/mirror/command.js.map +0 -1
  193. package/dist/mirror/dist-filesystem-impl.js.map +0 -1
  194. package/dist/mirror/domain/constants.js.map +0 -1
  195. package/dist/mirror/domain/dirent-guard.js.map +0 -1
  196. package/dist/mirror/domain/dist-filesystem.js.map +0 -1
  197. package/dist/mirror/domain/errors.js.map +0 -1
  198. package/dist/mirror/domain/exports.js.map +0 -1
  199. package/dist/mirror/domain/package-display-name.js.map +0 -1
  200. package/dist/mirror/domain/path-normalizer.js.map +0 -1
  201. package/dist/mirror/domain/types.js.map +0 -1
  202. package/dist/mirror/output.js.map +0 -1
  203. package/dist/mirror/package-path.js.map +0 -1
  204. package/dist/mirror/prepare.js.map +0 -1
  205. package/dist/mirror/supplement-exports.js.map +0 -1
  206. package/dist/mirror/sync-reporter.js.map +0 -1
  207. package/dist/mirror/sync-types.js.map +0 -1
  208. package/dist/mirror/sync-workspace-package.js.map +0 -1
  209. package/dist/mirror/sync.js.map +0 -1
  210. package/dist/mirror/write-exports.js.map +0 -1
  211. package/dist/tag/cli-result.js.map +0 -1
  212. package/dist/tag/cli-schema.js.map +0 -1
  213. package/dist/tag/command.js.map +0 -1
  214. package/dist/tag/domain/types.js.map +0 -1
  215. package/dist/tag/output.js.map +0 -1
  216. package/dist/tag/prepare.js.map +0 -1
  217. package/dist/tag/resolve-target-path.js.map +0 -1
  218. package/dist/tag/since-writer.js.map +0 -1
  219. package/dist/tag/sync.js.map +0 -1
  220. package/dist/tag/target-candidates.js.map +0 -1
  221. package/dist/tag/target-runner.js.map +0 -1
  222. package/dist/tag/version-resolver.js.map +0 -1
@@ -2,7 +2,7 @@
2
2
  * Physical → logical replacements. Order matters: negative before positive,
3
3
  * specific corners before general edges, with-value before bare.
4
4
  *
5
- * @since 1.0.0-canary.7
5
+ * @since 0.5.0-canary.6
6
6
  */
7
7
  export const RTL_MAPPINGS = [
8
8
  ["-ml-", "-ms-"],
@@ -53,7 +53,7 @@ export const RTL_MAPPINGS = [
53
53
  /**
54
54
  * translate-x has no logical equivalent — it needs an rtl:-negated twin.
55
55
  *
56
- * @since 1.0.0-canary.7
56
+ * @since 0.5.0-canary.6
57
57
  */
58
58
  export const RTL_TRANSLATE_X_MAPPINGS = [
59
59
  ["-translate-x-", "translate-x-"],
@@ -62,7 +62,7 @@ export const RTL_TRANSLATE_X_MAPPINGS = [
62
62
  /**
63
63
  * Classes that need an rtl:*-reverse companion.
64
64
  *
65
- * @since 1.0.0-canary.7
65
+ * @since 0.5.0-canary.6
66
66
  */
67
67
  export const RTL_REVERSE_MAPPINGS = [
68
68
  ["space-x-", "space-x-reverse"],
@@ -71,7 +71,7 @@ export const RTL_REVERSE_MAPPINGS = [
71
71
  /**
72
72
  * Classes that need an rtl: companion with the swapped value.
73
73
  *
74
- * @since 1.0.0-canary.7
74
+ * @since 0.5.0-canary.6
75
75
  */
76
76
  export const RTL_SWAP_MAPPINGS = [
77
77
  ["cursor-w-resize", "cursor-e-resize"],
@@ -81,23 +81,24 @@ export const RTL_SWAP_MAPPINGS = [
81
81
  * Anything anchored to a physical side variant stays physical: Radix resolves
82
82
  * `side` per direction, and a border/position/slide tied to that side must follow it.
83
83
  *
84
- * @since 1.0.0-canary.7
84
+ * @since 0.5.0-canary.6
85
85
  */
86
86
  export const PHYSICAL_SIDE_VARIANT = /data-side-(?:left|right)|data-\[side=(?:left|right)\]|\[data-side=(?:left|right)\]/;
87
87
  /**
88
88
  * Slide animations under direction-resolved contexts are correct as-is:
89
89
  * Radix flips `side`/`motion` values itself under DirectionProvider.
90
90
  *
91
- * @since 1.0.0-canary.7
91
+ * @since 0.5.0-canary.6
92
92
  */
93
93
  export const DIRECTION_RESOLVED_VARIANT = /data-\[motion[=^]/;
94
94
  /**
95
- * @since 1.0.0-canary.7
95
+ * The physical slide-animation class prefixes the RTL audit inspects.
96
+ *
97
+ * @since 0.5.0-canary.6
96
98
  */
97
99
  export const SLIDE_PREFIXES = [
98
100
  "slide-in-from-left",
99
101
  "slide-in-from-right",
100
102
  "slide-out-to-left",
101
103
  "slide-out-to-right",
102
- ];
103
- //# 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
+ }
@@ -0,0 +1,91 @@
1
+ import { parseSync } from "oxc-parser";
2
+ function isOxcNode(value) {
3
+ return typeof value === "object" && value !== null && typeof value.type === "string";
4
+ }
5
+ function isIdentifierNamed(node, name) {
6
+ return isOxcNode(node) && node.type === "Identifier" && node.name === name;
7
+ }
8
+ /**
9
+ * Scans one TypeScript source for React import-policy violations.
10
+ *
11
+ * @remarks Three violation kinds: `import * as React`, a default `React` import (type-only
12
+ * included), and — only when no import binds `React` — an implicit `React.*` UMD-global type
13
+ * reference, which tsc accepts silently through the `export as namespace React` in `@types/react`.
14
+ *
15
+ * @since 0.8.0
16
+ */
17
+ export function auditReactImportSource(filePath, sourceText) {
18
+ const { program } = parseSync(filePath, sourceText);
19
+ const statements = program.body;
20
+ const violations = [];
21
+ let bindsReact = false;
22
+ for (const statement of statements) {
23
+ if (statement.type !== "ImportDeclaration") {
24
+ continue;
25
+ }
26
+ const source = statement.source;
27
+ if (!isOxcNode(source) || source.value !== "react") {
28
+ continue;
29
+ }
30
+ const specifiers = Array.isArray(statement.specifiers) ? statement.specifiers.filter(isOxcNode) : [];
31
+ bindsReact ||= specifiers.some((specifier) => isIdentifierNamed(specifier.local, "React"));
32
+ for (const specifier of specifiers) {
33
+ if (specifier.type === "ImportNamespaceSpecifier") {
34
+ violations.push({
35
+ line: lineOfOffset(sourceText, statement.start),
36
+ raw: firstLineOf(sourceText.slice(statement.start, statement.end)),
37
+ reason: 'namespace React import — import members by name from "react"',
38
+ });
39
+ }
40
+ else if (specifier.type === "ImportDefaultSpecifier") {
41
+ violations.push({
42
+ line: lineOfOffset(sourceText, statement.start),
43
+ raw: firstLineOf(sourceText.slice(statement.start, statement.end)),
44
+ reason: 'default React import — import members by name from "react"',
45
+ });
46
+ }
47
+ }
48
+ }
49
+ // A bound `React` means the import itself is flagged above; qualified names under it are
50
+ // symptoms, not extra findings.
51
+ if (!bindsReact) {
52
+ collectUmdGlobalReferences(program, sourceText, violations);
53
+ }
54
+ violations.sort((a, b) => a.line - b.line);
55
+ return violations;
56
+ }
57
+ function collectUmdGlobalReferences(node, sourceText, violations) {
58
+ if (node.type === "TSQualifiedName" && isIdentifierNamed(node.left, "React")) {
59
+ violations.push({
60
+ line: lineOfOffset(sourceText, node.start),
61
+ raw: sourceText.slice(node.start, node.end),
62
+ reason: 'implicit React.* UMD global — add import type { … } from "react"',
63
+ });
64
+ return;
65
+ }
66
+ for (const value of Object.values(node)) {
67
+ if (Array.isArray(value)) {
68
+ for (const item of value) {
69
+ if (isOxcNode(item)) {
70
+ collectUmdGlobalReferences(item, sourceText, violations);
71
+ }
72
+ }
73
+ }
74
+ else if (isOxcNode(value)) {
75
+ collectUmdGlobalReferences(value, sourceText, violations);
76
+ }
77
+ }
78
+ }
79
+ function lineOfOffset(sourceText, offset) {
80
+ let line = 1;
81
+ for (let index = 0; index < offset; index++) {
82
+ if (sourceText.charCodeAt(index) === 10) {
83
+ line++;
84
+ }
85
+ }
86
+ return line;
87
+ }
88
+ function firstLineOf(text) {
89
+ const newlineIndex = text.indexOf("\n");
90
+ return newlineIndex === -1 ? text : text.slice(0, newlineIndex);
91
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Flags `@since` tags naming a version the owning package has not reached.
3
+ */
4
+ const versionPattern = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.-]+)?$/;
5
+ // `@since` appears only inside comments; matching the comment lead-in keeps code and prose out.
6
+ const sinceLinePattern = /^\s*(?:\*|\/\/|\/\*+)\s*@since\s+(\S+)/;
7
+ const numericIdentifierPattern = /^\d+$/;
8
+ function parseVersion(value) {
9
+ const match = versionPattern.exec(value);
10
+ if (match === null) {
11
+ return null;
12
+ }
13
+ return {
14
+ core: [Number(match[1]), Number(match[2]), Number(match[3])],
15
+ prerelease: match[4] === undefined ? [] : match[4].split("."),
16
+ };
17
+ }
18
+ /**
19
+ * Compares two SemVer strings by precedence — positive when `left` is greater,
20
+ * null when either side is not a SemVer version.
21
+ *
22
+ * @since 0.8.0
23
+ */
24
+ export function compareVersionPrecedence(left, right) {
25
+ const a = parseVersion(left);
26
+ const b = parseVersion(right);
27
+ if (a === null || b === null) {
28
+ return null;
29
+ }
30
+ for (let index = 0; index < 3; index++) {
31
+ if (a.core[index] !== b.core[index]) {
32
+ return a.core[index] - b.core[index];
33
+ }
34
+ }
35
+ // A pre-release ranks below the release it precedes.
36
+ if (a.prerelease.length === 0 || b.prerelease.length === 0) {
37
+ return b.prerelease.length - a.prerelease.length;
38
+ }
39
+ const shared = Math.min(a.prerelease.length, b.prerelease.length);
40
+ for (let index = 0; index < shared; index++) {
41
+ const leftId = a.prerelease[index];
42
+ const rightId = b.prerelease[index];
43
+ const leftIsNumeric = numericIdentifierPattern.test(leftId);
44
+ const rightIsNumeric = numericIdentifierPattern.test(rightId);
45
+ if (leftIsNumeric && rightIsNumeric) {
46
+ const difference = Number(leftId) - Number(rightId);
47
+ if (difference !== 0) {
48
+ return difference;
49
+ }
50
+ }
51
+ else if (leftIsNumeric !== rightIsNumeric) {
52
+ // Numeric identifiers rank below alphanumeric ones.
53
+ return leftIsNumeric ? -1 : 1;
54
+ }
55
+ else if (leftId !== rightId) {
56
+ return leftId < rightId ? -1 : 1;
57
+ }
58
+ }
59
+ return a.prerelease.length - b.prerelease.length;
60
+ }
61
+ /**
62
+ * Scans a file's comments for `@since` tags stamped above the package's current
63
+ * version — a release that has not happened, so the stamp cannot be true.
64
+ *
65
+ * @since 0.8.0
66
+ */
67
+ export function scanImpossibleSinceTags(content, packageVersion) {
68
+ if (parseVersion(packageVersion) === null) {
69
+ return [];
70
+ }
71
+ const findings = [];
72
+ const lines = content.split(/\r?\n/);
73
+ for (let index = 0; index < lines.length; index++) {
74
+ const match = sinceLinePattern.exec(lines[index]);
75
+ if (match === null) {
76
+ continue;
77
+ }
78
+ const stamped = match[1];
79
+ const comparison = compareVersionPrecedence(stamped, packageVersion);
80
+ if (comparison !== null && comparison > 0) {
81
+ findings.push({ line: index + 1, raw: `@since ${stamped}` });
82
+ }
83
+ }
84
+ return findings;
85
+ }
@@ -2,7 +2,7 @@
2
2
  * Splits a class token into [variant, value, modifier], colon/slash-aware of
3
3
  * brackets and parens (arbitrary values like data-[side=left] or calc(...)).
4
4
  *
5
- * @since 1.0.0-canary.7
5
+ * @since 0.5.0-canary.6
6
6
  */
7
7
  export function splitClassName(token) {
8
8
  const segments = [];
@@ -50,9 +50,9 @@ export function splitClassName(token) {
50
50
  /**
51
51
  * Extracts string/template literal contents with their line numbers.
52
52
  *
53
- * @since 1.0.0-canary.7
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] ?? "";
@@ -61,7 +61,9 @@ export function* stringLiterals(content) {
61
61
  }
62
62
  }
63
63
  /**
64
- * @since 1.0.0-canary.7
64
+ * Collects the parsed class tokens from every string literal in a file's content.
65
+ *
66
+ * @since 0.5.0-canary.6
65
67
  */
66
68
  export function collectTokens(content) {
67
69
  const tokens = [];
@@ -79,5 +81,4 @@ export function collectTokens(content) {
79
81
  }
80
82
  }
81
83
  return tokens;
82
- }
83
- //# 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 {};
@@ -3,7 +3,7 @@ import { logger } from "#/core/logger";
3
3
  /**
4
4
  * Exit `1` when any non-allowlisted violation remains.
5
5
  *
6
- * @since 1.0.0-canary.7
6
+ * @since 0.5.0-canary.6
7
7
  */
8
8
  export function exitCodeForRtlAuditResult(result) {
9
9
  return result.violationCount > 0 ? CLI_EXIT_GENERAL_ERROR : CLI_EXIT_SUCCESS;
@@ -11,7 +11,7 @@ export function exitCodeForRtlAuditResult(result) {
11
11
  /**
12
12
  * Human-readable RTL audit report (matches the former packages/ui script shape).
13
13
  *
14
- * @since 1.0.0-canary.7
14
+ * @since 0.5.0-canary.6
15
15
  */
16
16
  export function presentRtlAuditResult(result) {
17
17
  for (const file of result.files) {
@@ -31,7 +31,7 @@ export function presentRtlAuditResult(result) {
31
31
  /**
32
32
  * Machine-readable RTL audit summary for `--json`.
33
33
  *
34
- * @since 1.0.0-canary.7
34
+ * @since 0.5.0-canary.6
35
35
  */
36
36
  export function formatRtlAuditJsonOutput(result, rootDir) {
37
37
  return JSON.stringify({
@@ -82,6 +82,47 @@ export function formatLinkAuditJsonOutput(result, rootDir) {
82
82
  result,
83
83
  });
84
84
  }
85
+ /**
86
+ * Exit `1` when any non-allowlisted React import-policy violation remains.
87
+ *
88
+ * @since 0.8.0
89
+ */
90
+ export function exitCodeForReactAuditResult(result) {
91
+ return result.violationCount > 0 ? CLI_EXIT_GENERAL_ERROR : CLI_EXIT_SUCCESS;
92
+ }
93
+ /**
94
+ * Human-readable React import-policy report.
95
+ *
96
+ * @since 0.8.0
97
+ */
98
+ export function presentReactAuditResult(result) {
99
+ for (const file of result.files) {
100
+ logger.out(`\n${file.relativePath}`);
101
+ for (const { line, raw, reason } of file.violations) {
102
+ logger.out(` ${line}: ${raw} → ${reason}`);
103
+ }
104
+ }
105
+ const allowlistSuffix = result.allowlistedCount > 0 ? ` (${result.allowlistedCount} allowlisted)` : "";
106
+ if (result.violationCount > 0) {
107
+ logger.out(`\n✖ ${result.violationCount} React import violation(s)${allowlistSuffix}`);
108
+ }
109
+ else {
110
+ logger.out(`✓ No namespace/default React imports or React.* UMD globals across ${result.scannedFileCount} file(s)${allowlistSuffix}`);
111
+ }
112
+ }
113
+ /**
114
+ * Machine-readable React import-policy summary for `--json`.
115
+ *
116
+ * @since 0.8.0
117
+ */
118
+ export function formatReactAuditJsonOutput(result, rootDir) {
119
+ return JSON.stringify({
120
+ schemaVersion: 1,
121
+ ok: result.violationCount === 0,
122
+ cwd: rootDir,
123
+ result,
124
+ });
125
+ }
85
126
  /**
86
127
  * Exit `1` when any non-allowlisted divider still breaks the convention.
87
128
  *
@@ -128,5 +169,4 @@ export function formatCommentAuditJsonOutput(result, rootDir) {
128
169
  }
129
170
  function truncate(raw) {
130
171
  return raw.length <= 60 ? raw : `${raw.slice(0, 57)}…`;
131
- }
132
- //# sourceMappingURL=output.js.map
172
+ }
@@ -6,7 +6,7 @@ import { findRepoRoot } from "#/core/workspace/resolver";
6
6
  /**
7
7
  * Loads config and resolves the scan target for `audit rtl`.
8
8
  *
9
- * @since 1.0.0-canary.7
9
+ * @since 0.5.0-canary.6
10
10
  */
11
11
  export async function prepareRtlAudit(fs, args) {
12
12
  let rootDir;
@@ -70,6 +70,37 @@ export async function prepareLinkAudit(fs, args) {
70
70
  allowlist: linksConfig.allowlist ?? [],
71
71
  });
72
72
  }
73
+ /**
74
+ * Loads config and resolves the scan target for `audit react`.
75
+ *
76
+ * @remarks Defaults to the repo root: the import policy is repo-wide, and generated or vendored
77
+ * trees are already excluded by the shared walk.
78
+ *
79
+ * @since 0.8.0
80
+ */
81
+ export async function prepareReactAudit(fs, args) {
82
+ let rootDir;
83
+ try {
84
+ rootDir = fs.canonicalPathSync(findRepoRoot(args.currentWorkingDirectory, fs));
85
+ }
86
+ catch (caughtError) {
87
+ return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
88
+ }
89
+ const loadedOutcome = await loadCodefastConfig(rootDir, fs);
90
+ if (!loadedOutcome.ok) {
91
+ return loadedOutcome;
92
+ }
93
+ const reactConfig = loadedOutcome.value.config.audit?.react ?? {};
94
+ const targetPath = args.rawTarget === undefined ? rootDir : resolveRepoRelativePath(args.currentWorkingDirectory, args.rawTarget);
95
+ if (!fs.existsSync(targetPath)) {
96
+ return err(new AppError("NOT_FOUND", `Not found: ${targetPath}`));
97
+ }
98
+ return ok({
99
+ rootDir,
100
+ targetPath: fs.canonicalPathSync(targetPath),
101
+ allowlist: reactConfig.allowlist ?? [],
102
+ });
103
+ }
73
104
  /**
74
105
  * Loads config and resolves the scan target for `audit comments`.
75
106
  *
@@ -100,5 +131,4 @@ export async function prepareCommentAudit(fs, args) {
100
131
  targetPath: fs.canonicalPathSync(targetPath),
101
132
  allowlist: commentsConfig.allowlist ?? [],
102
133
  });
103
- }
104
- //# sourceMappingURL=prepare.js.map
134
+ }
@@ -2,9 +2,11 @@ import path from "node:path";
2
2
  import { scanCommentContent } from "#/audit/domain/comment-content";
3
3
  import { applyCommentDividerFixes, DIVIDER_COLUMN, scanCommentDividers } from "#/audit/domain/comment-dividers";
4
4
  import { countHeadMentions, isPathLinkTarget, linkTargetHead, scanLinkReferences, } from "#/audit/domain/link-references";
5
+ import { scanImpossibleSinceTags } from "#/audit/domain/since-versions";
5
6
  import { scanTsdocSyntax } from "#/audit/domain/tsdoc-syntax";
6
7
  import { AppError, messageFrom } from "#/core/errors";
7
8
  import { err, ok } from "#/core/result";
9
+ import { findNearestPackageVersion } from "#/core/workspace/package-version";
8
10
  import { sourceCommentLanguage, walkSourceFiles } from "#/core/workspace/source-walk";
9
11
  const reasonByDefect = {
10
12
  "bad-width": `rule does not end at column ${DIVIDER_COLUMN}`,
@@ -15,6 +17,7 @@ const reasonByDefect = {
15
17
  "legacy-form": "legacy divider form",
16
18
  "param-coverage": "a block naming any parameter must name them all — a partial list reads as complete",
17
19
  "param-hyphen": "TSDoc separates the name from its description with ' - '",
20
+ "since-impossible": "@since names a version the package has not reached — set it to the release that shipped the symbol",
18
21
  "since-order": "@since is stamped at release and stays the block's last tag",
19
22
  "stacked-doc": "// run stacked above a doc block — fold it into the block",
20
23
  };
@@ -32,6 +35,7 @@ export function runCommentAudit(fs, args) {
32
35
  const filesToScan = collectScanPaths(fs, targetPath);
33
36
  const perFile = new Map();
34
37
  const scanned = [];
38
+ const versionByDirectory = new Map();
35
39
  let allowlistedCount = 0;
36
40
  let fixedCount = 0;
37
41
  let dividerCount = 0;
@@ -71,6 +75,16 @@ export function runCommentAudit(fs, args) {
71
75
  }
72
76
  breakages.push({ line: finding.line, raw: finding.raw, reason: reasonByDefect[finding.defect] });
73
77
  }
78
+ const packageVersion = nearestVersion(fs, absolutePath, versionByDirectory);
79
+ if (packageVersion !== null) {
80
+ for (const finding of scanImpossibleSinceTags(content, packageVersion)) {
81
+ if (allowlist.has(finding.raw) || allowlist.has(`${relativePath}:${finding.raw}`)) {
82
+ allowlistedCount++;
83
+ continue;
84
+ }
85
+ breakages.push({ line: finding.line, raw: finding.raw, reason: reasonByDefect["since-impossible"] });
86
+ }
87
+ }
74
88
  if (language === "js") {
75
89
  for (const finding of scanTsdocSyntax(content)) {
76
90
  if (allowlist.has(finding.raw) || allowlist.has(`${relativePath}:${finding.raw}`)) {
@@ -152,6 +166,17 @@ function appendDeadLinkBreakages(fs, scanned, allowlist, perFile) {
152
166
  }
153
167
  return allowlisted;
154
168
  }
169
+ // One package.json walk per directory — sibling files share the answer.
170
+ function nearestVersion(fs, absolutePath, cache) {
171
+ const directory = path.dirname(absolutePath);
172
+ const cached = cache.get(directory);
173
+ if (cached !== undefined) {
174
+ return cached;
175
+ }
176
+ const version = findNearestPackageVersion(fs, directory);
177
+ cache.set(directory, version);
178
+ return version;
179
+ }
155
180
  function collectScanPaths(fs, targetPath) {
156
181
  const stats = fs.statSync(targetPath);
157
182
  if (stats.isFile()) {
@@ -161,5 +186,4 @@ function collectScanPaths(fs, targetPath) {
161
186
  }
162
187
  function toPosixPath(filePath) {
163
188
  return filePath.split(path.sep).join("/");
164
- }
165
- //# sourceMappingURL=run-comments.js.map
189
+ }
@@ -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
+ }
@@ -0,0 +1,55 @@
1
+ import path from "node:path";
2
+ import { auditReactImportSource } from "#/audit/domain/react-imports";
3
+ import { AppError, messageFrom } from "#/core/errors";
4
+ import { err, ok } from "#/core/result";
5
+ import { walkTsxFiles } from "#/core/workspace/typescript-walk";
6
+ /**
7
+ * Scans a target path for React import-policy violations.
8
+ *
9
+ * @since 0.8.0
10
+ */
11
+ export function runReactAudit(fs, args) {
12
+ try {
13
+ const allowlist = new Set(args.allowlist);
14
+ const { rootDir, targetPath } = args;
15
+ const filesToScan = collectScanPaths(fs, targetPath);
16
+ const files = [];
17
+ let violationCount = 0;
18
+ let allowlistedCount = 0;
19
+ for (const absolutePath of filesToScan) {
20
+ const relativePath = toPosixPath(path.relative(rootDir, absolutePath));
21
+ const content = fs.readFileSync(absolutePath, "utf8");
22
+ const remaining = auditReactImportSource(absolutePath, content).filter(({ raw }) => {
23
+ const isAllowed = allowlist.has(raw) || allowlist.has(`${relativePath}:${raw}`);
24
+ if (isAllowed) {
25
+ allowlistedCount++;
26
+ }
27
+ return !isAllowed;
28
+ });
29
+ if (remaining.length === 0) {
30
+ continue;
31
+ }
32
+ violationCount += remaining.length;
33
+ files.push({ relativePath, violations: remaining });
34
+ }
35
+ return ok({
36
+ files,
37
+ violationCount,
38
+ allowlistedCount,
39
+ scannedFileCount: filesToScan.length,
40
+ });
41
+ }
42
+ catch (caughtError) {
43
+ return err(new AppError("INFRA_FAILURE", messageFrom(caughtError), caughtError));
44
+ }
45
+ }
46
+ function collectScanPaths(fs, targetPath) {
47
+ const stats = fs.statSync(targetPath);
48
+ if (stats.isFile()) {
49
+ return [targetPath];
50
+ }
51
+ return walkTsxFiles(targetPath, fs);
52
+ }
53
+ function toPosixPath(filePath) {
54
+ return filePath.split(path.sep).join("/");
55
+ }
package/dist/audit/run.js CHANGED
@@ -6,7 +6,7 @@ import { walkTsxFiles } from "#/core/workspace/typescript-walk";
6
6
  /**
7
7
  * Scans a target path for physical-direction Tailwind class violations.
8
8
  *
9
- * @since 1.0.0-canary.7
9
+ * @since 0.5.0-canary.6
10
10
  */
11
11
  export function runRtlAudit(fs, args) {
12
12
  try {
@@ -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 {
@@ -20,6 +21,8 @@ function readVersion() {
20
21
  }
21
22
  }
22
23
  /**
24
+ * Runs the `codefast` CLI over the given argv and returns the process exit code.
25
+ *
23
26
  * @since 0.3.16-canary.0
24
27
  */
25
28
  export async function runCli(argv) {
@@ -37,9 +40,9 @@ export async function runCli(argv) {
37
40
  program.addCommand(createArrangeCommand());
38
41
  program.addCommand(createAuditCommand());
39
42
  program.addCommand(createMirrorCommand());
43
+ program.addCommand(createPackSlimCommand());
40
44
  program.addCommand(createTagCommand());
41
45
  await program.parseAsync(argv, { from: "node" });
42
46
  const code = process.exitCode ?? 0;
43
47
  return typeof code === "number" ? code : Number(code) || 0;
44
- }
45
- //# sourceMappingURL=cli.js.map
48
+ }
@@ -1,4 +1,6 @@
1
1
  /**
2
+ * Formats an `AppError` as a `[CODE] message` line for CLI output.
3
+ *
2
4
  * @since 0.3.16-canary.0
3
5
  */
4
6
  export function formatAppError(error) {
@@ -15,5 +17,4 @@ export function formatAppError(error) {
15
17
  return `Unhandled error code: ${String(exhaustive)}`;
16
18
  }
17
19
  }
18
- }
19
- //# 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
+ }