@codefast/cli 0.10.0 → 0.11.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 (220) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +16 -2
  3. package/dist/arrange/command.js +96 -132
  4. package/dist/arrange/domain/ast/simplify-targets.js +18 -23
  5. package/dist/arrange/{typescript-ast-translator.d.ts → domain/ast/translator.d.ts} +5 -4
  6. package/dist/arrange/{typescript-ast-translator.js → domain/ast/translator.js} +19 -22
  7. package/dist/arrange/group/cli-result.d.ts +7 -0
  8. package/dist/arrange/group/cli-result.js +12 -0
  9. package/dist/arrange/group/cli-schema.d.ts +17 -0
  10. package/dist/arrange/group/cli-schema.js +13 -0
  11. package/dist/arrange/group/output.d.ts +7 -0
  12. package/dist/arrange/group/output.js +10 -0
  13. package/dist/arrange/{suggest.d.ts → group/suggest.d.ts} +1 -1
  14. package/dist/arrange/inspect/cli-result.d.ts +7 -0
  15. package/dist/arrange/inspect/cli-result.js +8 -0
  16. package/dist/arrange/inspect/cli-schema.d.ts +15 -0
  17. package/dist/arrange/inspect/cli-schema.js +9 -0
  18. package/dist/arrange/inspect/output.d.ts +7 -0
  19. package/dist/arrange/inspect/output.js +42 -0
  20. package/dist/arrange/{analyze.d.ts → inspect/run.d.ts} +2 -2
  21. package/dist/arrange/{analyze.js → inspect/run.js} +2 -2
  22. package/dist/arrange/{workspace.d.ts → prepare.d.ts} +2 -2
  23. package/dist/arrange/{workspace.js → prepare.js} +5 -8
  24. package/dist/arrange/regroup/cli-result.d.ts +13 -0
  25. package/dist/arrange/regroup/cli-result.js +23 -0
  26. package/dist/arrange/regroup/cli-schema.d.ts +20 -0
  27. package/dist/arrange/regroup/cli-schema.js +14 -0
  28. package/dist/arrange/regroup/output.d.ts +14 -0
  29. package/dist/arrange/regroup/output.js +72 -0
  30. package/dist/arrange/{process-file.d.ts → regroup/process-file.d.ts} +2 -2
  31. package/dist/arrange/{sync.d.ts → regroup/run.d.ts} +3 -3
  32. package/dist/arrange/{sync.js → regroup/run.js} +2 -2
  33. package/dist/arrange/resolve-target.d.ts +2 -2
  34. package/dist/arrange/scan-target.d.ts +2 -2
  35. package/dist/arrange/simplify/cli-result.d.ts +7 -0
  36. package/dist/arrange/simplify/cli-result.js +8 -0
  37. package/dist/arrange/simplify/cli-schema.d.ts +17 -0
  38. package/dist/arrange/simplify/cli-schema.js +11 -0
  39. package/dist/arrange/simplify/fold-targets.d.ts +13 -0
  40. package/dist/arrange/simplify/fold-targets.js +139 -0
  41. package/dist/arrange/simplify/output.d.ts +7 -0
  42. package/dist/arrange/simplify/output.js +15 -0
  43. package/dist/arrange/{simplify-process-file.d.ts → simplify/process-file.d.ts} +4 -2
  44. package/dist/arrange/simplify/process-file.js +49 -0
  45. package/dist/arrange/{simplify-sync.d.ts → simplify/run.d.ts} +3 -2
  46. package/dist/arrange/simplify/run.js +38 -0
  47. package/dist/arrange/simplify/variant-classname-probe.d.ts +35 -0
  48. package/dist/arrange/simplify/variant-classname-probe.js +95 -0
  49. package/dist/arrange/source-parse.js +1 -1
  50. package/dist/audit/command.js +134 -211
  51. package/dist/audit/comments/cli-result.d.ts +13 -0
  52. package/dist/audit/comments/cli-result.js +22 -0
  53. package/dist/audit/comments/cli-schema.d.ts +19 -0
  54. package/dist/audit/comments/cli-schema.js +13 -0
  55. package/dist/audit/comments/output.d.ts +7 -0
  56. package/dist/audit/comments/output.js +27 -0
  57. package/dist/audit/comments/prepare.d.ts +16 -0
  58. package/dist/audit/comments/prepare.js +12 -0
  59. package/dist/audit/{run-comments.d.ts → comments/run.d.ts} +2 -2
  60. package/dist/audit/{run-comments.js → comments/run.js} +5 -5
  61. package/dist/audit/display-names/cli-result.d.ts +13 -0
  62. package/dist/audit/display-names/cli-result.js +22 -0
  63. package/dist/audit/display-names/cli-schema.d.ts +18 -0
  64. package/dist/audit/display-names/cli-schema.js +12 -0
  65. package/dist/audit/display-names/output.d.ts +7 -0
  66. package/dist/audit/display-names/output.js +21 -0
  67. package/dist/audit/display-names/prepare.d.ts +16 -0
  68. package/dist/audit/display-names/prepare.js +12 -0
  69. package/dist/audit/{run-display-names.d.ts → display-names/run.d.ts} +2 -2
  70. package/dist/audit/{run-display-names.js → display-names/run.js} +1 -1
  71. package/dist/audit/imports/cli-result.d.ts +13 -0
  72. package/dist/audit/imports/cli-result.js +22 -0
  73. package/dist/audit/imports/cli-schema.d.ts +18 -0
  74. package/dist/audit/imports/cli-schema.js +12 -0
  75. package/dist/audit/{domain → imports/domain}/import-policy.d.ts +2 -2
  76. package/dist/audit/{domain → imports/domain}/import-policy.js +4 -11
  77. package/dist/audit/imports/output.d.ts +7 -0
  78. package/dist/audit/imports/output.js +21 -0
  79. package/dist/audit/imports/prepare.d.ts +16 -0
  80. package/dist/audit/imports/prepare.js +12 -0
  81. package/dist/audit/{run-imports.d.ts → imports/run.d.ts} +2 -2
  82. package/dist/audit/{run-imports.js → imports/run.js} +2 -2
  83. package/dist/audit/links/cli-result.d.ts +13 -0
  84. package/dist/audit/links/cli-result.js +22 -0
  85. package/dist/audit/links/cli-schema.d.ts +18 -0
  86. package/dist/audit/links/cli-schema.js +12 -0
  87. package/dist/audit/links/output.d.ts +7 -0
  88. package/dist/audit/links/output.js +21 -0
  89. package/dist/audit/links/prepare.d.ts +16 -0
  90. package/dist/audit/links/prepare.js +12 -0
  91. package/dist/audit/{run-links.d.ts → links/run.d.ts} +2 -2
  92. package/dist/audit/{run-links.js → links/run.js} +1 -1
  93. package/dist/audit/prepare.d.ts +11 -50
  94. package/dist/audit/prepare.js +9 -132
  95. package/dist/audit/rtl/cli-result.d.ts +13 -0
  96. package/dist/audit/rtl/cli-result.js +22 -0
  97. package/dist/audit/rtl/cli-schema.d.ts +18 -0
  98. package/dist/audit/rtl/cli-schema.js +12 -0
  99. package/dist/audit/{domain → rtl/domain}/audit-file.js +2 -2
  100. package/dist/audit/rtl/output.d.ts +7 -0
  101. package/dist/audit/rtl/output.js +21 -0
  102. package/dist/audit/rtl/prepare.d.ts +13 -0
  103. package/dist/audit/rtl/prepare.js +41 -0
  104. package/dist/audit/{run.d.ts → rtl/run.d.ts} +2 -2
  105. package/dist/audit/{run.js → rtl/run.js} +1 -1
  106. package/dist/core/cli/command-pipeline.d.ts +91 -0
  107. package/dist/core/cli/command-pipeline.js +83 -0
  108. package/dist/core/cli/global-options.d.ts +1 -1
  109. package/dist/core/cli/global-options.js +1 -1
  110. package/dist/core/cli/resolve-root.d.ts +9 -0
  111. package/dist/core/cli/resolve-root.js +16 -0
  112. package/dist/core/cli/result-handle.d.ts +1 -7
  113. package/dist/core/cli/result-handle.js +0 -13
  114. package/dist/core/config/loader.d.ts +2 -2
  115. package/dist/core/config/schema.d.ts +1 -1
  116. package/dist/core/config/schema.js +1 -1
  117. package/dist/core/config.d.ts +2 -2
  118. package/dist/core/filesystem/{port.d.ts → filesystem.d.ts} +1 -1
  119. package/dist/core/filesystem/node.d.ts +3 -3
  120. package/dist/core/filesystem/node.js +1 -1
  121. package/dist/core/source-text-edit.d.ts +14 -0
  122. package/dist/core/source-text-edit.js +22 -0
  123. package/dist/core/workspace/markdown-walk.d.ts +2 -2
  124. package/dist/core/workspace/package-version.d.ts +2 -2
  125. package/dist/core/workspace/resolver.d.ts +3 -3
  126. package/dist/core/workspace/source-walk.d.ts +2 -2
  127. package/dist/core/workspace/typescript-walk.d.ts +2 -2
  128. package/dist/core/workspace/walk-files.d.ts +2 -2
  129. package/dist/mirror/cli-schema.d.ts +2 -2
  130. package/dist/mirror/cli-schema.js +1 -1
  131. package/dist/mirror/command.js +31 -60
  132. package/dist/mirror/{dist-filesystem-impl.d.ts → dist-filesystem-node.d.ts} +3 -3
  133. package/dist/mirror/{dist-filesystem-impl.js → dist-filesystem-node.js} +1 -1
  134. package/dist/mirror/domain/dirent-guard.d.ts +1 -1
  135. package/dist/mirror/domain/types.d.ts +57 -0
  136. package/dist/mirror/output.d.ts +1 -3
  137. package/dist/mirror/output.js +126 -1
  138. package/dist/mirror/package-path.d.ts +3 -3
  139. package/dist/mirror/prepare.d.ts +3 -3
  140. package/dist/mirror/prepare.js +6 -10
  141. package/dist/mirror/run.d.ts +12 -0
  142. package/dist/mirror/supplement-exports.d.ts +3 -3
  143. package/dist/mirror/sync-workspace-package.d.ts +2 -2
  144. package/dist/mirror/sync-workspace-package.js +1 -1
  145. package/dist/mirror/write-exports.d.ts +2 -2
  146. package/dist/pack-slim/cli-schema.d.ts +1 -1
  147. package/dist/pack-slim/cli-schema.js +1 -1
  148. package/dist/pack-slim/command.js +31 -66
  149. package/dist/pack-slim/prepare.d.ts +21 -0
  150. package/dist/pack-slim/prepare.js +14 -0
  151. package/dist/pack-slim/{sync.d.ts → run.d.ts} +2 -2
  152. package/dist/tag/cli-result.d.ts +8 -2
  153. package/dist/tag/cli-result.js +14 -1
  154. package/dist/tag/cli-schema.d.ts +4 -4
  155. package/dist/tag/cli-schema.js +3 -4
  156. package/dist/tag/command.js +33 -60
  157. package/dist/tag/domain/skip-filter.d.ts +13 -0
  158. package/dist/tag/domain/skip-filter.js +29 -0
  159. package/dist/tag/domain/types.d.ts +21 -1
  160. package/dist/tag/domain/version-summary.d.ts +13 -0
  161. package/dist/tag/domain/version-summary.js +24 -0
  162. package/dist/tag/output.d.ts +4 -4
  163. package/dist/tag/output.js +6 -9
  164. package/dist/tag/prepare.d.ts +2 -2
  165. package/dist/tag/prepare.js +2 -2
  166. package/dist/tag/run.d.ts +10 -0
  167. package/dist/tag/{sync.js → run.js} +20 -50
  168. package/dist/tag/{target-candidates.d.ts → target/candidates.d.ts} +2 -2
  169. package/dist/tag/{target-candidates.js → target/candidates.js} +1 -1
  170. package/dist/tag/{resolve-target-path.d.ts → target/resolve-path.d.ts} +2 -2
  171. package/dist/tag/{target-runner.d.ts → target/runner.d.ts} +2 -2
  172. package/dist/tag/{target-runner.js → target/runner.js} +2 -2
  173. package/dist/tag/{since-writer.d.ts → writer/since-writer.d.ts} +2 -2
  174. package/dist/tag/{version-resolver.d.ts → writer/version-resolver.d.ts} +2 -2
  175. package/package.json +10 -2
  176. package/dist/arrange/cli-schema.d.ts +0 -50
  177. package/dist/arrange/cli-schema.js +0 -34
  178. package/dist/arrange/output.d.ts +0 -26
  179. package/dist/arrange/output.js +0 -127
  180. package/dist/arrange/simplify-process-file.js +0 -30
  181. package/dist/arrange/simplify-sync.js +0 -30
  182. package/dist/audit/cli-schema.d.ts +0 -93
  183. package/dist/audit/cli-schema.js +0 -66
  184. package/dist/audit/output.d.ts +0 -91
  185. package/dist/audit/output.js +0 -213
  186. package/dist/mirror/sync-reporter.d.ts +0 -60
  187. package/dist/mirror/sync-reporter.js +0 -128
  188. package/dist/mirror/sync-types.d.ts +0 -43
  189. package/dist/mirror/sync-types.js +0 -1
  190. package/dist/mirror/sync.d.ts +0 -12
  191. package/dist/tag/sync.d.ts +0 -42
  192. /package/dist/arrange/{suggest.js → group/suggest.js} +0 -0
  193. /package/dist/arrange/{domain → inspect/domain}/analyze-service.d.ts +0 -0
  194. /package/dist/arrange/{domain → inspect/domain}/analyze-service.js +0 -0
  195. /package/dist/arrange/{process-file.js → regroup/process-file.js} +0 -0
  196. /package/dist/audit/{domain → comments/domain}/comment-content.d.ts +0 -0
  197. /package/dist/audit/{domain → comments/domain}/comment-content.js +0 -0
  198. /package/dist/audit/{domain → comments/domain}/comment-dividers.d.ts +0 -0
  199. /package/dist/audit/{domain → comments/domain}/comment-dividers.js +0 -0
  200. /package/dist/audit/{domain → comments/domain}/link-references.d.ts +0 -0
  201. /package/dist/audit/{domain → comments/domain}/link-references.js +0 -0
  202. /package/dist/audit/{domain → comments/domain}/since-versions.d.ts +0 -0
  203. /package/dist/audit/{domain → comments/domain}/since-versions.js +0 -0
  204. /package/dist/audit/{domain → comments/domain}/tsdoc-syntax.d.ts +0 -0
  205. /package/dist/audit/{domain → comments/domain}/tsdoc-syntax.js +0 -0
  206. /package/dist/audit/{domain → display-names/domain}/display-names.d.ts +0 -0
  207. /package/dist/audit/{domain → display-names/domain}/display-names.js +0 -0
  208. /package/dist/audit/{domain → links/domain}/markdown-links.d.ts +0 -0
  209. /package/dist/audit/{domain → links/domain}/markdown-links.js +0 -0
  210. /package/dist/audit/{domain → rtl/domain}/audit-file.d.ts +0 -0
  211. /package/dist/audit/{domain → rtl/domain}/mappings.d.ts +0 -0
  212. /package/dist/audit/{domain → rtl/domain}/mappings.js +0 -0
  213. /package/dist/audit/{domain → rtl/domain}/tokenize.d.ts +0 -0
  214. /package/dist/audit/{domain → rtl/domain}/tokenize.js +0 -0
  215. /package/dist/core/filesystem/{port.js → filesystem.js} +0 -0
  216. /package/dist/mirror/{sync.js → run.js} +0 -0
  217. /package/dist/pack-slim/{sync.js → run.js} +0 -0
  218. /package/dist/tag/{resolve-target-path.js → target/resolve-path.js} +0 -0
  219. /package/dist/tag/{since-writer.js → writer/since-writer.js} +0 -0
  220. /package/dist/tag/{version-resolver.js → writer/version-resolver.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # @codefast/cli
2
2
 
3
+ ## 0.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#855](https://github.com/codefastlabs/codefast/pull/855) [`76cf7a6`](https://github.com/codefastlabs/codefast/commit/76cf7a65372c5f13f779c7b59200cb956d93b882) Thanks [@thevuong](https://github.com/thevuong)! - Fix `codefast arrange simplify` changing rendered output: a mixed `cn()` call moved every static literal to the front,
8
+ which flipped tailwind-merge precedence and dropped overrides that sat after a variant call. It now coalesces only
9
+ adjacent static literals and preserves argument order.
10
+
11
+ Add `codefast arrange simplify --fold-variant-classname` (alias `--fold-variant-class-name`), which folds
12
+ `cn(buttonVariants({ size: "sm" }), "flex-1")` into `buttonVariants({ size: "sm", className: "flex-1" })`. The fold is
13
+ gated on the native TypeScript type server confirming the callee accepts a `className`/`class`, so it loads the
14
+ `typescript` package (a new optional peer, v7) and applies only inside a `tsconfig`; the default pass is unchanged.
15
+
16
+ The fold splices `className` in after the last option, so a trailing comma or line comment in the options object no
17
+ longer breaks the rewrite. Overlapping edits are now resolved before they are applied — keeping the outermost of any
18
+ nested `cn()`/variant pair — so nested calls no longer corrupt the output.
19
+
20
+ - [#854](https://github.com/codefastlabs/codefast/pull/854) [`a7ad2d8`](https://github.com/codefastlabs/codefast/commit/a7ad2d8b24b10b5554a5809be943aca00e22e844) Thanks [@thevuong](https://github.com/thevuong)! - `codefast audit imports` now enforces the Zod namespace form (`import * as z from "zod"`) in every workspace package,
21
+ not only the front-end/bundled ones. The rule's `scope` restriction is dropped, so a named `import { z } from "zod"` is
22
+ flagged everywhere — backend/tsc packages such as `cli` itself included. Type-only imports
23
+ (`import type { ZodType } from "zod"`) stay allowed; only the value `z` is banned.
24
+
25
+ ### Patch Changes
26
+
27
+ - [#855](https://github.com/codefastlabs/codefast/pull/855) [`9e481c5`](https://github.com/codefastlabs/codefast/commit/9e481c554b7f60216b130495fd157fe3a459721e) Thanks [@thevuong](https://github.com/thevuong)! - Fix `codefast arrange --dry-run --json` printing human-readable preview lines to stdout ahead of the JSON summary, which
28
+ broke piping the output to a JSON parser. Dry-run previews are now emitted only in human mode; `--json` writes a single
29
+ JSON object.
30
+
31
+ - [#854](https://github.com/codefastlabs/codefast/pull/854) [`504a61a`](https://github.com/codefastlabs/codefast/commit/504a61a996684fbd4fb6703141612d3cd97dbfde) Thanks [@thevuong](https://github.com/thevuong)! - Reorganize the CLI source into a uniform per-command and per-subcommand directory layout. Each command follows one
32
+ skeleton (`command.ts`, `cli-schema.ts`, `prepare.ts`, `run*.ts`, `output.ts`, `cli-result.ts`, `domain/`), and every
33
+ `audit`/`arrange` subcommand is now its own mini-command directory (`audit/rtl/`, `arrange/inspect/`, …) with the shared
34
+ runner and grouping engine kept at the command root. Internal refactor only — no public API or behavior change.
35
+
3
36
  ## 0.10.0
4
37
 
5
38
  ### Minor Changes
package/README.md CHANGED
@@ -125,7 +125,7 @@ Long static JSX `className` strings are regrouped regardless of where `cn` comes
125
125
  | `--dry-run` | Preview suggested replacements without writing files. |
126
126
  | `--with-classname` | Append `className` as the final `cn()` argument (alias: `--with-class-name`). |
127
127
  | `--cn-import <spec>` | Override the module specifier used when a missing `cn` import is added. |
128
- | `--json` | Print one JSON object on stdout (suppresses human progress). |
128
+ | `--json` | Print one JSON summary on stdout (suppresses human progress). |
129
129
 
130
130
  Exits `1` when the `arrange.onAfterWrite` hook fails, `0` otherwise.
131
131
 
@@ -136,7 +136,21 @@ Read-only report of long strings, nested `cn` inside `tv()`, and related finding
136
136
  ### `arrange simplify [target]`
137
137
 
138
138
  Flattens grouped arrays and static-only `cn()` calls back to plain strings in `tv()` slots — the inverse cleanup pass.
139
- Accepts `--dry-run` and `--json`.
139
+ In a mixed `cn()` call it coalesces only _adjacent_ static literals and keeps argument order, so tailwind-merge
140
+ precedence is unchanged (a later argument still overrides an earlier one).
141
+
142
+ | Flag | Description |
143
+ | -------------------------- | -------------------------------------------------------------------------------------------------------- |
144
+ | `--dry-run` | Show what simplify would change without writing files. |
145
+ | `--fold-variant-classname` | Fold `cn()` overrides into a variant function's `className` option (alias: `--fold-variant-class-name`). |
146
+ | `--json` | Print one JSON summary on stdout. |
147
+
148
+ With `--fold-variant-classname`, `cn(buttonVariants({ size: "sm" }), "flex-1")` becomes
149
+ `buttonVariants({ size: "sm", className: "flex-1" })`, and a dynamic or multi-part override folds into a `className`
150
+ array. The fold fires only when the native TypeScript type server confirms the callee's options accept a `className` (or
151
+ `class`) of the right shape, so it loads the `typescript` package (an optional peer, v7) and needs the target inside a
152
+ `tsconfig`; files outside a project keep the base pass. Run a formatter afterward — a folded call can exceed the print
153
+ width until it is wrapped.
140
154
 
141
155
  ### `arrange group <tokens...>`
142
156
 
@@ -1,18 +1,96 @@
1
- import process from "node:process";
2
1
  import { Command } from "commander";
3
- import { analyzeDirectory } from "#/arrange/analyze";
4
- import { arrangeAnalyzeDirectoryRequestSchema, arrangeSuggestGroupsRequestSchema, arrangeSyncRunRequestSchema, } from "#/arrange/cli-schema";
5
- import { printAnalyzeReport, printGroupFilePreviewFromWork, printSimplifyResult, printSyncResult, } from "#/arrange/output";
6
- import { runArrangeSimplify } from "#/arrange/simplify-sync";
7
- import { suggestCnGroupsFromCli } from "#/arrange/suggest";
8
- import { runArrangeSync } from "#/arrange/sync";
9
- import { prepareArrangeWorkspace } from "#/arrange/workspace";
10
- import { readOptionalPositionalArg } from "#/core/cli/positional";
11
- import { consumeCliAppError, runCliResultAsync } from "#/core/cli/result-handle";
12
- import { CLI_EXIT_GENERAL_ERROR, CLI_EXIT_SUCCESS } from "#/core/exit-codes";
2
+ import { formatArrangeGroupJsonOutput } from "#/arrange/group/cli-result";
3
+ import { arrangeSuggestGroupsRequestSchema } from "#/arrange/group/cli-schema";
4
+ import { presentArrangeGroupResult } from "#/arrange/group/output";
5
+ import { suggestCnGroupsFromCli } from "#/arrange/group/suggest";
6
+ import { formatArrangeAnalyzeJsonOutput } from "#/arrange/inspect/cli-result";
7
+ import { arrangeAnalyzeDirectoryRequestSchema } from "#/arrange/inspect/cli-schema";
8
+ import { presentAnalyzeReport } from "#/arrange/inspect/output";
9
+ import { runArrangeInspect } from "#/arrange/inspect/run";
10
+ import { prepareArrangeWorkspace } from "#/arrange/prepare";
11
+ import { exitCodeForArrangeResult, formatArrangeJsonOutput } from "#/arrange/regroup/cli-result";
12
+ import { arrangeRunRequestSchema } from "#/arrange/regroup/cli-schema";
13
+ import { presentArrangeResult, presentGroupFilePreviewFromWork } from "#/arrange/regroup/output";
14
+ import { runArrange } from "#/arrange/regroup/run";
15
+ import { formatArrangeSimplifyJsonOutput } from "#/arrange/simplify/cli-result";
16
+ import { arrangeSimplifyRunRequestSchema } from "#/arrange/simplify/cli-schema";
17
+ import { presentSimplifyResult } from "#/arrange/simplify/output";
18
+ import { runArrangeSimplify } from "#/arrange/simplify/run";
19
+ import { applyCommandPipeline, registerPipelineSubcommand } from "#/core/cli/command-pipeline";
20
+ import { consumeCliAppError } from "#/core/cli/result-handle";
21
+ import { CLI_EXIT_SUCCESS } from "#/core/exit-codes";
13
22
  import { nodeFilesystem } from "#/core/filesystem/node";
14
23
  import { logger } from "#/core/logger";
15
24
  import { parseWithSchema } from "#/core/schema-parse";
25
+ const targetHelp = "Directory or file (default: nearest package directory from cwd)";
26
+ const prepareWorkspace = (fs, input) => prepareArrangeWorkspace(fs, { currentWorkingDirectory: input.currentWorkingDirectory, rawTarget: input.rawArg });
27
+ const regroupPipeline = {
28
+ positional: { name: "[target]", help: targetHelp },
29
+ schema: arrangeRunRequestSchema,
30
+ configureArgv: (command) => {
31
+ command.option("--dry-run", "Preview suggested replacements without writing files", false);
32
+ command.option("--with-classname, --with-class-name", "Append className as final cn() argument", false);
33
+ command.option("--cn-import <spec>", "Override module specifier when adding cn import");
34
+ },
35
+ prepare: prepareWorkspace,
36
+ buildRequest: ({ prelude, opts }) => ({
37
+ rootDir: prelude.rootDir,
38
+ targetPath: prelude.resolvedTarget,
39
+ write: !opts.dryRun,
40
+ withClassName: opts.withClassName,
41
+ cnImport: opts.cnImport,
42
+ config: prelude.config.arrange ?? {},
43
+ }),
44
+ run: (fs, request) => runArrange(fs, request),
45
+ presentHuman: ({ result, opts }) => {
46
+ // Dry-run previews are human-only; the --json summary omits the non-serializable plans.
47
+ if (opts.dryRun) {
48
+ for (const plan of result.previewPlans) {
49
+ presentGroupFilePreviewFromWork(plan);
50
+ }
51
+ }
52
+ presentArrangeResult(result, !opts.dryRun);
53
+ },
54
+ formatJson: ({ result, opts }) => formatArrangeJsonOutput(result, !opts.dryRun),
55
+ exitCode: exitCodeForArrangeResult,
56
+ };
57
+ const inspectPipeline = {
58
+ name: "inspect",
59
+ description: "Report long strings, nested cn in tv(), and related findings (read-only)",
60
+ positional: { name: "[target]", help: targetHelp },
61
+ jsonHelp: "Print one JSON object on stdout instead of a human report",
62
+ schema: arrangeAnalyzeDirectoryRequestSchema,
63
+ prepare: prepareWorkspace,
64
+ buildRequest: ({ prelude }) => ({ analyzeRootPath: prelude.resolvedTarget }),
65
+ run: async (fs, request) => runArrangeInspect(fs, request.analyzeRootPath),
66
+ presentHuman: ({ result, prelude }) => {
67
+ presentAnalyzeReport(prelude.resolvedTarget, result);
68
+ },
69
+ formatJson: ({ result, prelude }) => formatArrangeAnalyzeJsonOutput(prelude.resolvedTarget, result),
70
+ exitCode: () => CLI_EXIT_SUCCESS,
71
+ };
72
+ const simplifyPipeline = {
73
+ name: "simplify",
74
+ description: "Flatten grouped arrays and static-only cn() calls back to plain strings in tv() slots",
75
+ positional: { name: "[target]", help: targetHelp },
76
+ schema: arrangeSimplifyRunRequestSchema,
77
+ configureArgv: (command) => {
78
+ command.option("--dry-run", "Show what simplify would change without writing files", false);
79
+ command.option("--fold-variant-classname, --fold-variant-class-name", "Fold cn() overrides into a variant function's className option (uses the TypeScript type server)", false);
80
+ },
81
+ prepare: prepareWorkspace,
82
+ buildRequest: ({ prelude, opts }) => ({
83
+ targetPath: prelude.resolvedTarget,
84
+ write: !opts.dryRun,
85
+ foldVariantClassName: opts.foldVariantClassName,
86
+ }),
87
+ run: (fs, request) => runArrangeSimplify(fs, request),
88
+ presentHuman: ({ result, opts }) => {
89
+ presentSimplifyResult(result, !opts.dryRun);
90
+ },
91
+ formatJson: ({ result, opts }) => formatArrangeSimplifyJsonOutput(result, !opts.dryRun),
92
+ exitCode: () => CLI_EXIT_SUCCESS,
93
+ };
16
94
  /**
17
95
  * Creates the `arrange` command and its subcommands.
18
96
  *
@@ -23,103 +101,12 @@ export function createArrangeCommand() {
23
101
  .description("Regroup Tailwind classes in cn() / tv() calls in render-pipeline order")
24
102
  // The parent action shares option names (--json) with subcommands; positional
25
103
  // options ensure tokens after a subcommand bind to that subcommand, not the parent.
26
- .enablePositionalOptions()
27
- .argument("[target]", "Directory or file (default: nearest package directory from cwd)")
28
- .option("--dry-run", "Preview suggested replacements without writing files", false)
29
- .option("--with-classname, --with-class-name", "Append className as final cn() argument", false)
30
- .option("--cn-import <spec>", "Override module specifier when adding cn import")
31
- .option("--json", "Print one JSON object on stdout (suppresses human progress)", false)
32
- .action(async (target, opts) => {
33
- const write = !opts.dryRun;
34
- const prelude = await prepareArrangeWorkspace(nodeFilesystem, {
35
- currentWorkingDirectory: process.cwd(),
36
- rawTarget: readOptionalPositionalArg(target),
37
- });
38
- if (!consumeCliAppError(prelude)) {
39
- return;
40
- }
41
- const { resolvedTarget, rootDir, config } = prelude.value;
42
- const parsed = parseWithSchema(arrangeSyncRunRequestSchema, {
43
- rootDir,
44
- targetPath: resolvedTarget,
45
- write,
46
- withClassName: opts.withClassName,
47
- cnImport: opts.cnImport,
48
- config: config.arrange ?? {},
49
- });
50
- if (!consumeCliAppError(parsed)) {
51
- return;
52
- }
53
- await runCliResultAsync(runArrangeSync(nodeFilesystem, parsed.value), (value) => {
54
- if (!write) {
55
- for (const plan of value.previewPlans) {
56
- printGroupFilePreviewFromWork(plan);
57
- }
58
- }
59
- if (opts.json) {
60
- logger.out(formatArrangeSyncJsonOutput(value, write));
61
- return exitCodeForArrangeSyncResult(value);
62
- }
63
- printSyncResult(value, write);
64
- return exitCodeForArrangeSyncResult(value);
65
- });
66
- });
67
- cmd
68
- .command("inspect")
69
- .description("Report long strings, nested cn in tv(), and related findings (read-only)")
70
- .argument("[target]", "Directory or file (default: nearest package directory from cwd)")
71
- .option("--json", "Print one JSON object on stdout instead of a human report", false)
72
- .action(async (target, opts) => {
73
- const prelude = await prepareArrangeWorkspace(nodeFilesystem, {
74
- currentWorkingDirectory: process.cwd(),
75
- rawTarget: readOptionalPositionalArg(target),
76
- });
77
- if (!consumeCliAppError(prelude)) {
78
- return;
79
- }
80
- const { resolvedTarget } = prelude.value;
81
- const parsed = parseWithSchema(arrangeAnalyzeDirectoryRequestSchema, {
82
- analyzeRootPath: resolvedTarget,
83
- });
84
- if (!consumeCliAppError(parsed)) {
85
- return;
86
- }
87
- const outcome = analyzeDirectory(nodeFilesystem, parsed.value.analyzeRootPath);
88
- if (!consumeCliAppError(outcome)) {
89
- return;
90
- }
91
- if (opts.json) {
92
- logger.out(formatArrangeAnalyzeJsonOutput(resolvedTarget, outcome.value));
93
- }
94
- else {
95
- printAnalyzeReport(resolvedTarget, outcome.value);
96
- }
97
- });
98
- cmd
99
- .command("simplify")
100
- .description("Flatten grouped arrays and static-only cn() calls back to plain strings in tv() slots")
101
- .argument("[target]", "Directory or file (default: nearest package directory from cwd)")
102
- .option("--dry-run", "Show what simplify would change without writing files", false)
103
- .option("--json", "Print one JSON object on stdout (suppresses human progress)", false)
104
- .action(async (target, opts) => {
105
- const write = !opts.dryRun;
106
- const prelude = await prepareArrangeWorkspace(nodeFilesystem, {
107
- currentWorkingDirectory: process.cwd(),
108
- rawTarget: readOptionalPositionalArg(target),
109
- });
110
- if (!consumeCliAppError(prelude)) {
111
- return;
112
- }
113
- const { resolvedTarget } = prelude.value;
114
- await runCliResultAsync(runArrangeSimplify(nodeFilesystem, { targetPath: resolvedTarget, write }), (value) => {
115
- if (opts.json) {
116
- logger.out(JSON.stringify({ schemaVersion: 1, ok: true, write, result: value }));
117
- return CLI_EXIT_SUCCESS;
118
- }
119
- printSimplifyResult(value, write);
120
- return CLI_EXIT_SUCCESS;
121
- });
122
- });
104
+ .enablePositionalOptions();
105
+ applyCommandPipeline(cmd, nodeFilesystem, regroupPipeline);
106
+ registerPipelineSubcommand(cmd, nodeFilesystem, inspectPipeline);
107
+ registerPipelineSubcommand(cmd, nodeFilesystem, simplifyPipeline);
108
+ // `group` is a pure token-string transform — no workspace prelude, a variadic positional — so it
109
+ // does not fit the workspace pipeline and stays wired by hand.
123
110
  cmd
124
111
  .command("group")
125
112
  .description("Try grouping on a pasted class string (stdout: cn(...) or tv array with --tv)")
@@ -141,31 +128,8 @@ export function createArrangeCommand() {
141
128
  logger.out(formatArrangeGroupJsonOutput(output));
142
129
  }
143
130
  else {
144
- logger.out(output.primaryLine);
145
- logger.out(output.bucketsCommentLine);
131
+ presentArrangeGroupResult(output);
146
132
  }
147
133
  });
148
134
  return cmd;
149
- }
150
- function formatArrangeAnalyzeJsonOutput(analyzeRootPath, report) {
151
- return JSON.stringify({ schemaVersion: 1, analyzeRootPath, report });
152
- }
153
- function formatArrangeSyncJsonOutput(result, write) {
154
- const { previewPlans: _plans, ...serializableResult } = result;
155
- return JSON.stringify({
156
- schemaVersion: 1,
157
- ok: result.hookError === null,
158
- write,
159
- result: serializableResult,
160
- });
161
- }
162
- function formatArrangeGroupJsonOutput(output) {
163
- return JSON.stringify({
164
- schemaVersion: 1,
165
- primaryLine: output.primaryLine,
166
- bucketsCommentLine: output.bucketsCommentLine,
167
- });
168
- }
169
- function exitCodeForArrangeSyncResult(result) {
170
- return result.hookError !== null ? CLI_EXIT_GENERAL_ERROR : CLI_EXIT_SUCCESS;
171
135
  }
@@ -15,47 +15,42 @@ function joinLiterals(args) {
15
15
  function isAllStaticLiterals(args) {
16
16
  return args.length > 0 && args.every(isDomainTailwindClassLiteral);
17
17
  }
18
- // ---------------------------------------------------------------------------
19
- // Mixed cn() merge: ALL static args → one string first, dynamic args follow
20
- // ---------------------------------------------------------------------------
21
18
  /**
22
- * For a cn() call that has both static and dynamic args:
23
- * - Merge ALL static string literals (regardless of position) into one string.
24
- * - Place the merged string first.
25
- * - Append all dynamic args in their original relative order.
19
+ * Builds a mixed cn() replacement that coalesces each run of adjacent static literals into one string.
26
20
  *
27
- * Returns `null` when nothing changes (0 statics, or already 1 static at arg[0]).
21
+ * @remarks Argument order is preserved so tailwind-merge precedence is unchanged — a later cn()
22
+ * argument still overrides an earlier one. Returns `null` when no run of two-or-more adjacent statics
23
+ * exists, since reordering a lone static across a dynamic argument would change what renders.
28
24
  */
29
25
  function buildMixedCnReplacement(call, sourceText) {
30
26
  const args = [...call.arguments];
31
27
  if (args.length === 0) {
32
28
  return null;
33
29
  }
34
- const staticTexts = [];
35
- const dynamicSrcs = [];
30
+ const pieces = [];
36
31
  for (const arg of args) {
37
32
  if (isDomainTailwindClassLiteral(arg)) {
38
- staticTexts.push(arg.text);
33
+ const last = pieces.at(-1);
34
+ if (last?.kind === "static") {
35
+ last.texts.push(arg.text);
36
+ }
37
+ else {
38
+ pieces.push({ kind: "static", texts: [arg.text] });
39
+ }
39
40
  }
40
41
  else {
41
- dynamicSrcs.push(sourceText.slice(arg.pos, arg.end));
42
+ pieces.push({ kind: "dynamic", src: sourceText.slice(arg.pos, arg.end) });
42
43
  }
43
44
  }
44
- if (staticTexts.length === 0) {
45
- return null;
46
- }
47
- // Already simplest form: 1 static arg already at the front.
48
- const firstArg = args[0];
49
- if (staticTexts.length === 1 && firstArg !== undefined && isDomainTailwindClassLiteral(firstArg)) {
45
+ const merged = pieces.some((piece) => piece.kind === "static" && piece.texts.length > 1);
46
+ if (!merged) {
50
47
  return null;
51
48
  }
52
- const flatStatic = staticTexts.join(" ").trim();
53
49
  const baseIndent = indentOfLineContaining(sourceText, call.pos);
54
50
  const argIndent = `${baseIndent} `;
55
- const lines = [
56
- `${argIndent}"${escapeTsStringLiteralContent(flatStatic)}",`,
57
- ...dynamicSrcs.map((src) => `${argIndent}${src},`),
58
- ];
51
+ const lines = pieces.map((piece) => piece.kind === "static"
52
+ ? `${argIndent}"${escapeTsStringLiteralContent(piece.texts.join(" ").trim())}",`
53
+ : `${argIndent}${piece.src},`);
59
54
  return `cn(\n${lines.join("\n")}\n${baseIndent})`;
60
55
  }
61
56
  // ── tv() array collector ─────────────────────────────────────────────────────────────────────────────────────────────
@@ -11,11 +11,12 @@ import type { DomainSourceFile } from "#/arrange/domain/ast/ast-node";
11
11
  export declare class TypeScriptAstTranslator {
12
12
  translateSourceFile(filePath: string, sourceText: string): DomainSourceFile;
13
13
  /**
14
- * Own child nodes in source-declaration order, mirroring `ts.forEachChild`:
15
- * recurses into nested node objects and arrays of nodes, skipping primitives
16
- * and plain records (e.g. a template element's `value`) that carry no `type`.
14
+ * Translates any node the typed cases do not cover, recursing into own child nodes and arrays of
15
+ * nodes in source-declaration order and skipping primitives and records that carry no `type`.
16
+ *
17
+ * @remarks Children are walked inline rather than through a generator — the `yield` per child is
18
+ * several times slower than a plain loop on this hot path, and this fallback runs for most nodes.
17
19
  */
18
- private childNodesOf;
19
20
  private translateUnknown;
20
21
  private stringLiteralText;
21
22
  private noSubstitutionTemplateText;
@@ -20,11 +20,21 @@ export class TypeScriptAstTranslator {
20
20
  return this.parseDomainSourceFile(filePath, sourceText);
21
21
  }
22
22
  /**
23
- * Own child nodes in source-declaration order, mirroring `ts.forEachChild`:
24
- * recurses into nested node objects and arrays of nodes, skipping primitives
25
- * and plain records (e.g. a template element's `value`) that carry no `type`.
23
+ * Translates any node the typed cases do not cover, recursing into own child nodes and arrays of
24
+ * nodes in source-declaration order and skipping primitives and records that carry no `type`.
25
+ *
26
+ * @remarks Children are walked inline rather than through a generator — the `yield` per child is
27
+ * several times slower than a plain loop on this hot path, and this fallback runs for most nodes.
26
28
  */
27
- *childNodesOf(node) {
29
+ translateUnknown(node, parent) {
30
+ const self = {
31
+ kind: DomainSyntaxKind.Unknown,
32
+ pos: node.start,
33
+ end: node.end,
34
+ parent,
35
+ children: [],
36
+ };
37
+ const childList = [];
28
38
  for (const key in node) {
29
39
  if (key === "type" || key === "start" || key === "end" || key === "range") {
30
40
  continue;
@@ -33,29 +43,16 @@ export class TypeScriptAstTranslator {
33
43
  if (Array.isArray(value)) {
34
44
  for (const element of value) {
35
45
  if (isOxcNode(element)) {
36
- yield element;
46
+ childList.push(this.translateNode(element, self));
37
47
  }
38
48
  }
39
- continue;
40
49
  }
41
- if (isOxcNode(value)) {
42
- yield value;
50
+ else if (isOxcNode(value)) {
51
+ childList.push(this.translateNode(value, self));
43
52
  }
44
53
  }
45
- }
46
- translateUnknown(node, parent) {
47
- const self = {
48
- kind: DomainSyntaxKind.Unknown,
49
- pos: node.start,
50
- end: node.end,
51
- parent,
52
- children: [],
53
- };
54
- const childList = [];
55
- for (const child of this.childNodesOf(node)) {
56
- childList.push(this.translateNode(child, self));
57
- }
58
- return { ...self, children: childList };
54
+ self.children = childList;
55
+ return self;
59
56
  }
60
57
  stringLiteralText(node) {
61
58
  if (node.type === "Literal" && typeof node.value === "string") {
@@ -0,0 +1,7 @@
1
+ import type { ArrangeSuggestGroupsOutput } from "#/arrange/domain/types";
2
+ /**
3
+ * Machine-readable `arrange group` suggestion for `--json`.
4
+ *
5
+ * @since 0.11.0
6
+ */
7
+ export declare function formatArrangeGroupJsonOutput(output: ArrangeSuggestGroupsOutput): string;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Machine-readable `arrange group` suggestion for `--json`.
3
+ *
4
+ * @since 0.11.0
5
+ */
6
+ export function formatArrangeGroupJsonOutput(output) {
7
+ return JSON.stringify({
8
+ schemaVersion: 1,
9
+ primaryLine: output.primaryLine,
10
+ bucketsCommentLine: output.bucketsCommentLine,
11
+ });
12
+ }
@@ -0,0 +1,17 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * The request payload for `arrange group`.
4
+ *
5
+ * @since 0.3.16-canary.0
6
+ */
7
+ export type ArrangeSuggestGroupsRequest = {
8
+ inlineClasses: string;
9
+ emitTvStyleArray: boolean;
10
+ trailingClassName: boolean;
11
+ };
12
+ /**
13
+ * The `zod` schema validating an `arrange group` request.
14
+ *
15
+ * @since 0.3.16-canary.0
16
+ */
17
+ export declare const arrangeSuggestGroupsRequestSchema: z.ZodType<ArrangeSuggestGroupsRequest>;
@@ -0,0 +1,13 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * The `zod` schema validating an `arrange group` request.
4
+ *
5
+ * @since 0.3.16-canary.0
6
+ */
7
+ export const arrangeSuggestGroupsRequestSchema = z.object({
8
+ inlineClasses: z
9
+ .string()
10
+ .min(1, 'Pass a class string. Example: codefast arrange group "flex gap-2 text-sm rounded-md"'),
11
+ emitTvStyleArray: z.boolean(),
12
+ trailingClassName: z.boolean(),
13
+ });
@@ -0,0 +1,7 @@
1
+ import type { ArrangeSuggestGroupsOutput } from "#/arrange/domain/types";
2
+ /**
3
+ * Presents the suggested grouping lines for an `arrange group` run.
4
+ *
5
+ * @since 0.11.0
6
+ */
7
+ export declare function presentArrangeGroupResult(output: ArrangeSuggestGroupsOutput): void;
@@ -0,0 +1,10 @@
1
+ import { logger } from "#/core/logger";
2
+ /**
3
+ * Presents the suggested grouping lines for an `arrange group` run.
4
+ *
5
+ * @since 0.11.0
6
+ */
7
+ export function presentArrangeGroupResult(output) {
8
+ logger.out(output.primaryLine);
9
+ logger.out(output.bucketsCommentLine);
10
+ }
@@ -1,5 +1,5 @@
1
- import type { ArrangeSuggestGroupsRequest } from "#/arrange/cli-schema";
2
1
  import type { ArrangeSuggestGroupsOutput } from "#/arrange/domain/types";
2
+ import type { ArrangeSuggestGroupsRequest } from "#/arrange/group/cli-schema";
3
3
  /**
4
4
  * Formats the suggested grouping for an inline class string as `arrange group` output lines.
5
5
  *
@@ -0,0 +1,7 @@
1
+ import type { AnalyzeReport } from "#/arrange/domain/types";
2
+ /**
3
+ * Machine-readable `arrange inspect` report for `--json`.
4
+ *
5
+ * @since 0.11.0
6
+ */
7
+ export declare function formatArrangeAnalyzeJsonOutput(analyzeRootPath: string, report: AnalyzeReport): string;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Machine-readable `arrange inspect` report for `--json`.
3
+ *
4
+ * @since 0.11.0
5
+ */
6
+ export function formatArrangeAnalyzeJsonOutput(analyzeRootPath, report) {
7
+ return JSON.stringify({ schemaVersion: 1, analyzeRootPath, report });
8
+ }
@@ -0,0 +1,15 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * The request payload for `arrange inspect`.
4
+ *
5
+ * @since 0.3.16-canary.0
6
+ */
7
+ export type ArrangeAnalyzeDirectoryRequest = {
8
+ analyzeRootPath: string;
9
+ };
10
+ /**
11
+ * The `zod` schema validating an `arrange inspect` request.
12
+ *
13
+ * @since 0.3.16-canary.0
14
+ */
15
+ export declare const arrangeAnalyzeDirectoryRequestSchema: z.ZodType<ArrangeAnalyzeDirectoryRequest>;
@@ -0,0 +1,9 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * The `zod` schema validating an `arrange inspect` request.
4
+ *
5
+ * @since 0.3.16-canary.0
6
+ */
7
+ export const arrangeAnalyzeDirectoryRequestSchema = z.object({
8
+ analyzeRootPath: z.string().min(1, "analyzeRootPath is required"),
9
+ });
@@ -0,0 +1,7 @@
1
+ import type { AnalyzeReport } from "#/arrange/domain/types";
2
+ /**
3
+ * Presents the human-readable summary of an `arrange inspect` report.
4
+ *
5
+ * @since 0.3.16-canary.0
6
+ */
7
+ export declare function presentAnalyzeReport(resolvedTargetPath: string, report: AnalyzeReport): void;