@codefast/cli 0.9.0 → 0.10.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 (159) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +329 -149
  3. package/dist/arrange/analyze.d.ts +10 -0
  4. package/dist/arrange/cli-schema.d.ts +50 -0
  5. package/dist/arrange/command.d.ts +7 -0
  6. package/dist/arrange/domain/analyze-service.d.ts +18 -0
  7. package/dist/arrange/domain/ast/ast-node.d.ts +394 -0
  8. package/dist/arrange/domain/ast/collectors-cn.d.ts +26 -0
  9. package/dist/arrange/domain/ast/collectors-jsx.d.ts +8 -0
  10. package/dist/arrange/domain/ast/collectors-tv.d.ts +34 -0
  11. package/dist/arrange/domain/ast/helpers.d.ts +36 -0
  12. package/dist/arrange/domain/ast/helpers.js +1 -0
  13. package/dist/arrange/domain/ast/simplify-targets.d.ts +22 -0
  14. package/dist/arrange/domain/ast/targets.d.ts +20 -0
  15. package/dist/arrange/domain/constants.d.ts +111 -0
  16. package/dist/arrange/domain/grouping-service.d.ts +100 -0
  17. package/dist/arrange/domain/grouping.d.ts +21 -0
  18. package/dist/arrange/domain/imports.d.ts +14 -0
  19. package/dist/arrange/domain/source-text-formatters.d.ts +33 -0
  20. package/dist/arrange/domain/tailwind-token.d.ts +24 -0
  21. package/dist/arrange/domain/token-classifier.d.ts +47 -0
  22. package/dist/arrange/domain/types.d.ts +208 -0
  23. package/dist/arrange/output.d.ts +26 -0
  24. package/dist/arrange/process-file.d.ts +11 -0
  25. package/dist/arrange/resolve-target.d.ts +10 -0
  26. package/dist/arrange/resolve-target.js +3 -16
  27. package/dist/arrange/scan-target.d.ts +7 -0
  28. package/dist/arrange/simplify-process-file.d.ts +11 -0
  29. package/dist/arrange/simplify-sync.d.ts +13 -0
  30. package/dist/arrange/source-parse.d.ts +7 -0
  31. package/dist/arrange/suggest.d.ts +8 -0
  32. package/dist/arrange/sync.d.ts +11 -0
  33. package/dist/arrange/typescript-ast-translator.d.ts +31 -0
  34. package/dist/arrange/workspace.d.ts +13 -0
  35. package/dist/arrange/workspace.js +2 -2
  36. package/dist/audit/cli-schema.d.ts +93 -0
  37. package/dist/audit/cli-schema.js +3 -3
  38. package/dist/audit/command.d.ts +8 -0
  39. package/dist/audit/command.js +12 -12
  40. package/dist/audit/domain/audit-file.d.ts +7 -0
  41. package/dist/audit/domain/comment-content.d.ts +26 -0
  42. package/dist/audit/domain/comment-dividers.d.ts +62 -0
  43. package/dist/audit/domain/display-names.d.ts +11 -0
  44. package/dist/audit/domain/import-policy.d.ts +34 -0
  45. package/dist/audit/domain/import-policy.js +147 -0
  46. package/dist/audit/domain/link-references.d.ts +40 -0
  47. package/dist/audit/domain/mappings.d.ts +45 -0
  48. package/dist/audit/domain/markdown-links.d.ts +44 -0
  49. package/dist/audit/domain/since-versions.d.ts +26 -0
  50. package/dist/audit/domain/tokenize.d.ts +14 -0
  51. package/dist/audit/domain/tsdoc-syntax.d.ts +20 -0
  52. package/dist/audit/domain/types.d.ts +171 -0
  53. package/dist/audit/output.d.ts +91 -0
  54. package/dist/audit/output.js +11 -11
  55. package/dist/audit/prepare.d.ts +70 -0
  56. package/dist/audit/prepare.js +11 -11
  57. package/dist/audit/run-comments.d.ts +17 -0
  58. package/dist/audit/run-display-names.d.ts +14 -0
  59. package/dist/audit/run-imports.d.ts +14 -0
  60. package/dist/audit/{run-react.js → run-imports.js} +15 -5
  61. package/dist/audit/run-links.d.ts +14 -0
  62. package/dist/audit/run.d.ts +14 -0
  63. package/dist/bin.d.ts +2 -0
  64. package/dist/cli.d.ts +6 -0
  65. package/dist/core/cli/format-error.d.ts +7 -0
  66. package/dist/core/cli/global-options.d.ts +15 -0
  67. package/dist/core/cli/positional.d.ts +6 -0
  68. package/dist/core/cli/result-handle.d.ts +19 -0
  69. package/dist/core/config/define-config.d.ts +7 -0
  70. package/dist/core/config/define-config.js +8 -0
  71. package/dist/core/config/loader.d.ts +18 -0
  72. package/dist/core/config/loader.js +2 -7
  73. package/dist/core/config/schema.d.ts +99 -0
  74. package/dist/core/config/schema.js +7 -85
  75. package/dist/core/config/warnings.d.ts +6 -0
  76. package/dist/core/config.d.ts +12 -0
  77. package/dist/core/errors.d.ts +25 -0
  78. package/dist/core/exit-codes.d.ts +18 -0
  79. package/dist/core/filesystem/node.d.ts +7 -0
  80. package/dist/core/filesystem/node.js +1 -0
  81. package/dist/core/filesystem/port.d.ts +44 -0
  82. package/dist/core/glob.d.ts +19 -0
  83. package/dist/core/logger.d.ts +9 -0
  84. package/dist/core/result.d.ts +30 -0
  85. package/dist/core/schema-parse.d.ts +9 -0
  86. package/dist/core/source-text-edit.d.ts +33 -0
  87. package/dist/core/verbose-diagnostics.d.ts +6 -0
  88. package/dist/core/workspace/ancestor-directories.d.ts +12 -0
  89. package/dist/core/workspace/ancestor-directories.js +30 -0
  90. package/dist/core/workspace/markdown-walk.d.ts +7 -0
  91. package/dist/core/workspace/markdown-walk.js +2 -20
  92. package/dist/core/workspace/package-version.d.ts +9 -0
  93. package/dist/core/workspace/package-version.js +8 -12
  94. package/dist/core/workspace/resolver.d.ts +39 -0
  95. package/dist/core/workspace/resolver.js +58 -75
  96. package/dist/core/workspace/skip-directories.d.ts +6 -0
  97. package/dist/core/workspace/source-walk.d.ts +16 -0
  98. package/dist/core/workspace/source-walk.js +2 -19
  99. package/dist/core/workspace/typescript-walk.d.ts +7 -0
  100. package/dist/core/workspace/typescript-walk.js +2 -23
  101. package/dist/core/workspace/walk-files.d.ts +7 -0
  102. package/dist/core/workspace/walk-files.js +27 -0
  103. package/dist/core/workspace/well-known-files.d.ts +18 -0
  104. package/dist/core/workspace/well-known-files.js +18 -0
  105. package/dist/index.d.ts +6 -0
  106. package/dist/index.js +5 -0
  107. package/dist/mirror/cli-result.d.ts +13 -0
  108. package/dist/mirror/cli-schema.d.ts +8 -0
  109. package/dist/mirror/command.d.ts +7 -0
  110. package/dist/mirror/dist-filesystem-impl.d.ts +8 -0
  111. package/dist/mirror/domain/constants.d.ts +18 -0
  112. package/dist/mirror/domain/constants.js +0 -12
  113. package/dist/mirror/domain/dirent-guard.d.ts +10 -0
  114. package/dist/mirror/domain/dist-filesystem.d.ts +9 -0
  115. package/dist/mirror/domain/errors.d.ts +24 -0
  116. package/dist/mirror/domain/exports.d.ts +36 -0
  117. package/dist/mirror/domain/package-display-name.d.ts +8 -0
  118. package/dist/mirror/domain/path-normalizer.d.ts +6 -0
  119. package/dist/mirror/domain/types.d.ts +131 -0
  120. package/dist/mirror/output.d.ts +23 -0
  121. package/dist/mirror/package-path.d.ts +19 -0
  122. package/dist/mirror/prepare.d.ts +15 -0
  123. package/dist/mirror/prepare.js +2 -2
  124. package/dist/mirror/supplement-exports.d.ts +27 -0
  125. package/dist/mirror/supplement-exports.js +2 -2
  126. package/dist/mirror/sync-reporter.d.ts +60 -0
  127. package/dist/mirror/sync-reporter.js +4 -0
  128. package/dist/mirror/sync-types.d.ts +43 -0
  129. package/dist/mirror/sync-workspace-package.d.ts +9 -0
  130. package/dist/mirror/sync-workspace-package.js +3 -3
  131. package/dist/mirror/sync.d.ts +12 -0
  132. package/dist/mirror/sync.js +5 -3
  133. package/dist/mirror/write-exports.d.ts +15 -0
  134. package/dist/pack-slim/cli-result.d.ts +13 -0
  135. package/dist/pack-slim/cli-schema.d.ts +17 -0
  136. package/dist/pack-slim/command.d.ts +7 -0
  137. package/dist/pack-slim/command.js +2 -2
  138. package/dist/pack-slim/domain/transform.d.ts +69 -0
  139. package/dist/pack-slim/domain/types.d.ts +46 -0
  140. package/dist/pack-slim/output.d.ts +15 -0
  141. package/dist/pack-slim/sync.d.ts +23 -0
  142. package/dist/pack-slim/sync.js +4 -4
  143. package/dist/pack-slim/working-tree.d.ts +20 -0
  144. package/dist/tag/cli-result.d.ts +7 -0
  145. package/dist/tag/cli-schema.d.ts +8 -0
  146. package/dist/tag/command.d.ts +7 -0
  147. package/dist/tag/domain/types.d.ts +111 -0
  148. package/dist/tag/output.d.ts +17 -0
  149. package/dist/tag/prepare.d.ts +13 -0
  150. package/dist/tag/prepare.js +2 -2
  151. package/dist/tag/resolve-target-path.d.ts +10 -0
  152. package/dist/tag/since-writer.d.ts +32 -0
  153. package/dist/tag/sync.d.ts +42 -0
  154. package/dist/tag/target-candidates.d.ts +8 -0
  155. package/dist/tag/target-candidates.js +1 -1
  156. package/dist/tag/target-runner.d.ts +8 -0
  157. package/dist/tag/version-resolver.d.ts +7 -0
  158. package/package.json +12 -1
  159. package/dist/audit/domain/react-imports.js +0 -91
package/README.md CHANGED
@@ -1,74 +1,109 @@
1
1
  # @codefast/cli
2
2
 
3
- The `codefast` command line for the [codefast monorepo](https://github.com/codefastlabs/codefast): `arrange` Tailwind
4
- class strings, `audit` source conventions, `mirror` export maps from `dist/`, `pack-slim` the publish artifact, and
5
- `tag` exported APIs with `@since`.
3
+ `codefast` is a small, dependency-light CLI toolkit for TypeScript projects — a **pnpm workspace** or a **single
4
+ package**. It reorders Tailwind classes, generates `package.json#exports` from a package's built `dist/`, slims the npm
5
+ tarball at publish time, stamps `@since` on your public API, and audits a handful of source and documentation
6
+ conventions.
7
+
8
+ It was built for — and is exercised daily by — the [codefast monorepo](https://github.com/codefastlabs/codefast), but
9
+ nothing here is codefast-only: run it in any pnpm workspace, or in a standalone package, and it works. A few audits
10
+ encode an opinionated house style (called out below) that you can adopt, ignore, or narrow with an allowlist.
6
11
 
7
12
  [![npm version](https://img.shields.io/npm/v/@codefast/cli)](https://www.npmjs.com/package/@codefast/cli)
8
13
  [![license](https://img.shields.io/npm/l/@codefast/cli)](./LICENSE)
9
14
 
10
- ## Overview
11
-
12
- `codefast` is the command line for the [codefast monorepo](https://github.com/codefastlabs/codefast). It has five
13
- commands: `arrange` regroups Tailwind class strings, `audit` checks source conventions, `mirror` writes
14
- `package.json#exports` from `dist/`, `pack-slim` slims the publish artifact down to what a consumer reads, and `tag`
15
- stamps exported APIs with `@since`.
15
+ ## Design principles
16
16
 
17
- This is repo tooling, published to npm. It runs in any pnpm workspace with a similar layout, but its flags and defaults
18
- follow the codefast conventions rather than aiming to be a general-purpose product.
19
-
20
- - **Safe by default.** Every writing command has `--dry-run`, and every audit is read-only except
17
+ - **Safe by default.** Every writing command supports `--dry-run`, and every audit is read-only except
21
18
  `audit comments --fix`.
22
- - **Scriptable.** `--json` prints one JSON object on stdout and suppresses human progress output.
23
- - **CI-ready.** Audits exit non-zero when findings remain, so they gate a pipeline without extra glue.
19
+ - **Scriptable.** `--json` prints one JSON object on stdout and suppresses the human progress output.
20
+ - **CI-ready.** Audits exit non-zero when findings remain, so they gate a pipeline with no extra glue.
24
21
  - **Configurable.** An optional `codefast.config.*` file, validated by a strict schema, adjusts every command.
25
22
 
26
- ## Installation and usage
23
+ ## Requirements
24
+
25
+ - **Node.js ≥ 24** (the CLI is published as ESM).
26
+ - **A project root — workspace or single package.** Commands resolve their root by walking up from the current
27
+ directory: the nearest `pnpm-workspace.yaml` marks a **workspace** (every package under it is in scope), and with no
28
+ workspace file the nearest `package.json` marks a **single package** (that one package is the whole scope). Only
29
+ `arrange group` needs no project at all — it just formats a string you paste in.
30
+ - **pnpm is not required to _run_ the CLI** — npm, npx, or a plain `node` invocation are all fine. pnpm matters only for
31
+ workspace-wide behavior, which is keyed off `pnpm-workspace.yaml`.
27
32
 
28
- Inside the codefast monorepo, the CLI runs from its built output via root `package.json` scripts:
33
+ ## Install
34
+
35
+ Install it globally, or run it once without installing:
29
36
 
30
37
  ```bash
31
- pnpm --filter @codefast/cli build # produce dist/bin.js first
38
+ # global install (pick your package manager)
39
+ pnpm add -g @codefast/cli
40
+ npm install -g @codefast/cli
32
41
 
33
- pnpm run codefast <command> # generic entry: node ./packages/cli/dist/bin.js
42
+ # one-off, no install
43
+ pnpm dlx @codefast/cli --help
44
+ npx @codefast/cli --help
45
+ ```
34
46
 
35
- # Convenience wrappers
36
- pnpm run cli:arrange # codefast arrange
37
- pnpm run cli:arrange:inspect # codefast arrange inspect
38
- pnpm run cli:arrange:preview # codefast arrange --dry-run
39
- pnpm run cli:arrange:simplify # codefast arrange simplify
40
- pnpm run cli:arrange:simplify:preview
41
- pnpm run cli:mirror # codefast mirror
42
- pnpm run cli:mirror:preview # codefast mirror --dry-run
43
- pnpm run cli:audit:rtl # codefast audit rtl
44
- pnpm run cli:audit:links # codefast audit links
45
- pnpm run cli:audit:comments # codefast audit comments
46
- pnpm run cli:audit:react # codefast audit react
47
- pnpm run cli:audit:display-names # codefast audit display-names
47
+ As a project dev dependency:
48
+
49
+ ```bash
50
+ pnpm add -D @codefast/cli
51
+ pnpm exec codefast --help
48
52
  ```
49
53
 
50
- `pnpm run version-packages` runs `changeset version` and then `codefast tag`, so published APIs are stamped at release.
54
+ The package is published on the `0.x` line and versioned on its own track: **breaking changes ship as minor versions**,
55
+ so pin the minor (`@codefast/cli@~0.9.0`) when you need stability.
51
56
 
52
- Standalone install (Node >= 24):
57
+ ## Quick start
53
58
 
54
59
  ```bash
55
- pnpm add -g @codefast/cli
56
- # or one-off
57
- pnpm dlx @codefast/cli --help
60
+ codefast --help # list commands
61
+ codefast arrange group "flex h-10 w-full rounded-md bg-primary" # works anywhere, no project needed
62
+
63
+ # in a workspace or a single-package project:
64
+ codefast arrange inspect packages/ui/src # read-only report of arrange findings
65
+ codefast arrange --dry-run packages/ui/src # preview a class reorder
66
+ codefast mirror --dry-run # preview generated exports for each package in scope
67
+ codefast audit links # find broken markdown cross-references
58
68
  ```
59
69
 
60
- The package is published on 0.x and versioned on its own track: breaking changes ship as minor versions, so pin the
61
- minor version when you need stability.
62
-
63
- Every writing command writes by default; pass `--dry-run` to preview. The global `--no-color` flag must come before the
64
- command name (`codefast --no-color mirror`). Commands that accept `--json` print a single JSON object on stdout and
65
- suppress human progress output.
70
+ ## Global options and conventions
71
+
72
+ - `codefast --help` lists the commands, and `--help` on any command shows its usage; `codefast --version` prints the
73
+ installed version.
74
+ - **The global `--no-color` flag must come _before_ the command name** — `codefast --no-color mirror`, not
75
+ `codefast mirror --no-color`.
76
+ - **Writing commands write by default; pass `--dry-run` to preview.** The audits are read-only — the one exception is
77
+ `audit comments --fix`, which repairs section dividers in place.
78
+ - **`--json` prints a single JSON object on stdout** and suppresses the human-readable progress output, so any command
79
+ can gate a script or a CI job.
80
+
81
+ ## Commands at a glance
82
+
83
+ | Command | What it does | Writes? |
84
+ | --------------------- | -------------------------------------------------------------------------- | ----------------- |
85
+ | `arrange` | Regroup Tailwind classes in `cn()` / `tv()` calls in render-pipeline order | yes (`--dry-run`) |
86
+ | `mirror` | Write each package's `package.json#exports` from its built `dist/` | yes (`--dry-run`) |
87
+ | `pack-slim` | Strip the dev-only surface from a package right before publish | yes (`--dry-run`) |
88
+ | `tag` | Stamp `@since <version>` on exported declarations that lack one | yes (`--dry-run`) |
89
+ | `audit links` | Report markdown cross-references that resolve to nothing | no |
90
+ | `audit rtl` | Report physical-direction Tailwind classes that should be logical | no |
91
+ | `audit imports` | Enforce the import policy (React by-name, Zod namespace in front-end) | no (report only) |
92
+ | `audit comments` | Check doc-comment conventions; repair section dividers | `--fix` only |
93
+ | `audit display-names` | Enforce the `namespace:Name` display-name convention | no |
94
+
95
+ **Which of these are for you?** `arrange`, `mirror`, `pack-slim`, `tag`, and `audit links` are general-purpose — they
96
+ work for any pnpm workspace or single package that builds with `tsc`. The other four audits encode codefast's own house
97
+ style (logical Tailwind directions, named React imports, a specific comment/divider grammar, a `namespace:Name` scheme
98
+ for `@codefast/di` tokens). Adopt them if they fit your project; otherwise skip them, or use an allowlist to narrow
99
+ their scope.
66
100
 
67
101
  ## `arrange`
68
102
 
69
103
  Rewrites Tailwind class strings inside `cn()` and `tv()` calls, regrouping utilities in render-pipeline order —
70
104
  existence, position, layout, sizing, spacing, shape, background, shadow, typography, composite, motion, starting,
71
- behavior, state, selector — instead of alphabetically.
105
+ behavior, state, selector — rather than alphabetically. (This is codefast's ordering, deliberately different from the
106
+ official Prettier Tailwind plugin's sort.)
72
107
 
73
108
  ```bash
74
109
  codefast arrange inspect packages/ui/src # read-only report
@@ -76,9 +111,14 @@ codefast arrange --dry-run packages/ui/src # preview the rewrite
76
111
  codefast arrange packages/ui/src # write
77
112
  ```
78
113
 
79
- When `[target]` is omitted, `arrange` uses the nearest directory with a `package.json` found by walking up from the
80
- current working directory. Directory scans skip test files (`*.test.*` / `*.spec.*`), because a `cn()` inside an
81
- assertion is intentional; pass such a file explicitly to process it.
114
+ When `[target]` is omitted, `arrange` uses the nearest directory with a `package.json`, walking up from the current
115
+ directory. Directory scans skip test files (`*.test.*` / `*.spec.*`), because a `cn()` inside an assertion is
116
+ intentional; pass such a file explicitly to process it.
117
+
118
+ `arrange` rewrites a `cn()` / `tv()` call only when its binding is imported from a recognized module — `clsx`,
119
+ `class-variance-authority`, `tailwind-variants`, `@codefast/tailwind-variants`, a `@/lib/utils` / `~/lib/utils` /
120
+ `#lib/utils` re-export, any `…/utils` path, or a dedicated `cn.ts` module — so an unrelated local `cn` is left alone.
121
+ Long static JSX `className` strings are regrouped regardless of where `cn` comes from.
82
122
 
83
123
  | Flag | Description |
84
124
  | -------------------- | ----------------------------------------------------------------------------- |
@@ -100,7 +140,8 @@ Accepts `--dry-run` and `--json`.
100
140
 
101
141
  ### `arrange group <tokens...>`
102
142
 
103
- Groups a pasted class string without touching the filesystem — useful for checking how classes would be bucketed:
143
+ Groups a pasted class string without touching the filesystem — the one command that needs no workspace, useful for
144
+ checking how classes would be bucketed:
104
145
 
105
146
  ```bash
106
147
  codefast arrange group "relative flex h-10 w-full items-center rounded-md bg-primary"
@@ -115,10 +156,10 @@ codefast arrange group --tv "flex items-center gap-2"
115
156
 
116
157
  ## `mirror`
117
158
 
118
- Scans each workspace package's built `dist/` tree and writes its `package.json#exports` map, plus top-level `main`,
119
- `module`, and `types` mirrored from the root export and a `files` entry for `dist`. The workspace root is the directory
120
- holding `pnpm-workspace.yaml`, so it runs from anywhere inside the repo. Build first — `mirror` reads `dist/`, and stale
121
- output produces stale exports.
159
+ Scans each package's built `dist/` tree and writes its `package.json#exports` map, plus top-level `main`, `module`, and
160
+ `types` mirrored from the root export and a `files` entry for `dist`. In a workspace it processes every package under
161
+ `pnpm-workspace.yaml`; in a single-package project it processes that one package. **Build first** — `mirror` reads
162
+ `dist/`, and stale output produces stale exports.
122
163
 
123
164
  ```bash
124
165
  codefast mirror # all workspace packages
@@ -134,21 +175,45 @@ codefast mirror --dry-run # report changes without writing
134
175
 
135
176
  Exits `1` when any package fails, `0` otherwise.
136
177
 
178
+ ### Per-package `mirror` configuration
179
+
180
+ The `mirror` config is a record keyed by package name, set under `mirror` in `codefast.config.*`. Set a package to
181
+ `false` to skip it entirely; omit a package to process it with defaults. For a package you do configure, these keys
182
+ apply (see the [Configuration](#configuration) example for their shape):
183
+
184
+ - **`source`** (`boolean | string`, default `true`) — emit a `source` condition pointing at the original `.ts` so a
185
+ consumer using the `source` condition resolves your `src/`. A string overrides the root-export source path explicitly.
186
+ - **`types`** (`boolean`, default `true`) — emit the `types` condition when a matching `.d.ts` exists.
187
+ - **`import`** (`boolean`, default `true`) — emit the `import` condition.
188
+ - **`preserve`** (`boolean`) — keep the existing `package.json#exports` map as written and only fill in the missing
189
+ `source` / `types` / `import` conditions; no `dist/` scan runs, so the public surface stays exactly what you declared.
190
+ - **`strip`** (`string`) — a `dist/` path prefix to flatten out of the generated specifiers, so `./components/button` is
191
+ published as `./button` rather than leaking the internal folder.
192
+ - **`exclude`** (`string[]`) — specifiers to leave out of the generated map, making a package's public surface a
193
+ decision rather than a consequence of its `dist/` layout. Matched against the specifier as it appears in `exports`
194
+ (after `strip`); a trailing `/*` excludes a whole subtree. The root export and `./package.json` are never excluded.
195
+ - **`exports`** (`Record<string, string>`) — extra or overriding entries merged into the generated map, for specifiers
196
+ the `dist/` scan does not produce (for example a raw CSS source path).
197
+ - **`css`** (`boolean | { enabled?, forceExportFiles?, customExports? }`) — how CSS files in `dist/` become exports:
198
+ `true` enables the default wildcard handling, and the object form tunes it (`enabled` toggles it, `forceExportFiles`
199
+ adds them to `files`, `customExports` sets explicit per-file CSS entries).
200
+
201
+ `source`, `types`, and `import` default to `true`, so an empty config object still emits all three.
202
+
137
203
  ## `pack-slim`
138
204
 
139
- Slims published packages down to what a consumer's `tsc` and Node read, so the npm tarball ships `dist` runtime and
140
- types only and its `package.json` describes nothing else. Where `mirror` writes the full exports — including the
141
- `source` condition — for repo dev, `pack-slim` removes the development lane for publish: it drops `src` from `files`,
142
- every `source` condition from `exports`/`imports`, every `imports` entry left pointing outside `files` (the `#/tests/*`
143
- and `#/examples/*` aliases), every script that is not an install or publish lifecycle hook, `devDependencies`, and the
144
- `dist` source maps plus their dangling `sourceMappingURL` directives. Private packages are skipped, since
145
- `changeset publish` never publishes them. It is meant to run on an ephemeral CI checkout right before publish (the
146
- release workflow runs it as its publish step), so it is never committed.
205
+ Slims a published package down to what a consumer's `tsc` and Node actually read, so the npm tarball ships `dist`
206
+ runtime and types only. Where `mirror` writes the full exports — including the `source` condition — for local
207
+ development, `pack-slim` removes that development lane for publish: it drops `src` from `files`, every `source`
208
+ condition from `exports`/`imports`, every `imports` entry left pointing outside `files`, every script that is not an
209
+ install or publish lifecycle hook, `devDependencies`, and the `dist` source maps plus their dangling `sourceMappingURL`
210
+ directives. Private packages are skipped. It is meant to run on an ephemeral CI checkout right before publish, so its
211
+ result is **never committed**.
147
212
 
148
- Because its result must never be committed, `pack-slim` refuses to write when the git working tree has uncommitted
149
- tracked changes — guarding against an accidental local run landing on real work. `--dry-run` is exempt (it writes
150
- nothing) and `--force` overrides the guard. In CI the check is transparent: `dist` is gitignored, so the tree is clean
151
- when the release workflow runs it.
213
+ Because that result must never be committed, `pack-slim` refuses to write when the git working tree has uncommitted
214
+ tracked changes — a guard against an accidental local run landing on real work. `--dry-run` is exempt (it writes
215
+ nothing) and `--force` overrides the guard. In CI the guard is invisible: `dist` is gitignored, so the tree is already
216
+ clean when the release workflow runs `pack-slim`.
152
217
 
153
218
  ```bash
154
219
  codefast pack-slim # every published package
@@ -164,33 +229,37 @@ codefast pack-slim --dry-run # report what would be stripped without touch
164
229
 
165
230
  Exits `1` when any package fails, `0` otherwise.
166
231
 
167
- ## `audit rtl`
232
+ ## `tag`
168
233
 
169
- Read-only scan for physical-direction Tailwind classes (e.g. `ml-*`, `left-*`, `text-left`) that should use logical
170
- equivalents (`ms-*`, `start-*`, `text-start`) or an `rtl:` companion (`translate-x`, `space-x`, resize cursors). Exits
171
- non-zero when violations remain so it can gate CI.
234
+ Adds `@since <version>` tags to the doc comments of exported declarations that lack one, creating the doc block when
235
+ there is none. The version comes from the nearest `package.json` above each target file, and declarations that already
236
+ carry `@since` are left alone. Run it at release time so published APIs carry accurate version metadata — never
237
+ hand-write `@since`.
172
238
 
173
239
  ```bash
174
- codefast audit rtl # uses audit.rtl.target from config
175
- codefast audit rtl packages/ui/src # explicit target
176
- codefast audit rtl --json # machine-readable summary
240
+ codefast tag # auto-discover packages from cwd (or the single package)
241
+ codefast tag packages/ui/src # tag one directory or file
242
+ codefast tag --dry-run # summary only, no writes
177
243
  ```
178
244
 
179
- | Flag | Description |
180
- | -------- | --------------------------------- |
181
- | `--json` | Print one JSON summary on stdout. |
245
+ | Flag | Description |
246
+ | ----------- | ------------------------------------------------------------- |
247
+ | `--dry-run` | Show summary without writing files. |
248
+ | `--json` | Print one JSON summary on stdout (suppresses human progress). |
249
+
250
+ Exits `1` when no target is selected, when any target fails, or when the `tag.onAfterWrite` hook fails.
182
251
 
183
- With no `[target]`, the scan root is `audit.rtl.target` from the config; when neither is set the command fails.
184
- Configure intentional exceptions via `audit.rtl.allowlist` — each entry is a bare class token or
185
- `repo/relative/path.tsx:token`.
252
+ ## `audit`
186
253
 
187
- ## `audit links`
254
+ Every audit is read-only, exits non-zero when findings remain (so it gates a CI pipeline with no extra glue), and takes
255
+ an optional `[target]` plus `--json`. Each also reads an `allowlist` from configuration for intentional exceptions.
188
256
 
189
- Read-only scan for markdown cross-references that point at nothing: a relative path that does not exist, an in-document
190
- anchor with no matching heading or `<a id>`, and an anchor into another document that the target does not offer. That
191
- last one is the reason this exists — a browser fails it silently by scrolling to the top. External URLs are not checked,
192
- and links inside fenced code are treated as examples rather than references. Exits non-zero when breakages remain so it
193
- can gate CI.
257
+ ### `audit links`
258
+
259
+ _General-purpose._ Scans markdown for cross-references that point at nothing: a relative path that does not exist, an
260
+ in-document anchor with no matching heading or `<a id>`, and an anchor into another document that the target does not
261
+ offer. That last case is the reason this exists — a browser fails it silently by scrolling to the top. External URLs are
262
+ not checked, and links inside fenced code are treated as examples rather than references.
194
263
 
195
264
  ```bash
196
265
  codefast audit links # whole repo
@@ -198,20 +267,48 @@ codefast audit links packages/di # explicit target
198
267
  codefast audit links --json # machine-readable summary
199
268
  ```
200
269
 
201
- | Flag | Description |
202
- | -------- | --------------------------------- |
203
- | `--json` | Print one JSON summary on stdout. |
270
+ Configure exceptions via `audit.links.allowlist` — each entry is a bare link target or `repo/relative/doc.md:target`.
271
+
272
+ ### `audit rtl`
273
+
274
+ _House style._ Scans for physical-direction Tailwind classes (e.g. `ml-*`, `left-*`, `text-left`) that should use
275
+ logical equivalents (`ms-*`, `start-*`, `text-start`) or an `rtl:` companion (`translate-x`, `space-x`, resize cursors).
276
+
277
+ ```bash
278
+ codefast audit rtl # uses audit.rtl.target from config
279
+ codefast audit rtl packages/ui/src # explicit target
280
+ codefast audit rtl --json # machine-readable summary
281
+ ```
282
+
283
+ With no `[target]`, the scan root is `audit.rtl.target` from the config; when neither is set the command fails.
284
+ Configure exceptions via `audit.rtl.allowlist` — each entry is a bare class token or `repo/relative/path.tsx:token`.
204
285
 
205
- Configure intentional exceptions via `audit.links.allowlist` — each entry is a bare link target or
206
- `repo/relative/doc.md:target`.
286
+ ### `audit imports`
207
287
 
208
- ## `audit comments`
288
+ _House style._ Enforces the monorepo's import policy over `.ts`/`.tsx` files:
209
289
 
210
- Scans source comments for the repo's comment conventions. Section dividers that are not in the one allowed form are
211
- mechanical, so `--fix` rewrites them in place. The rest is reported for a person to fix: TSDoc grammar errors, JSDoc
212
- `{type}` payloads, comments pointing at repo documents, `@param` lists that name some parameters but not all, `@param`
213
- descriptions without the `-` separator, `@since` tags out of position or naming a version the package has not reached,
214
- and comment links to missing paths. Exits non-zero when unfixed findings remain so it can gate CI.
290
+ - **React** — members must be imported by name. Flags `import * as React` and default `React` imports (type-only
291
+ included), plus an implicit `React.*` UMD-global type reference (`e: React.FormEvent` with no import) that `tsc`
292
+ accepts silently through the `export as namespace React` declaration in `@types/react`.
293
+ - **Zod** (front-end packages only) — flags a named `import { z } from "zod"`, which pins Zod's full locale set into the
294
+ bundle; `import * as z from "zod"` lets bundlers tree-shake it.
295
+
296
+ ```bash
297
+ codefast audit imports # whole repo
298
+ codefast audit imports apps/web/src # explicit target
299
+ codefast audit imports --json # machine-readable summary
300
+ ```
301
+
302
+ Configure exceptions via `audit.imports.allowlist` — each entry is the offending source text as written or
303
+ `repo/relative/path.tsx:<text>`.
304
+
305
+ ### `audit comments`
306
+
307
+ _House style._ Checks doc-comment conventions. Section dividers not in the one allowed form are mechanical, so `--fix`
308
+ rewrites them in place. The rest is reported for a person to fix: TSDoc grammar errors, JSDoc `{type}` payloads,
309
+ comments pointing at repo documents, `@param` lists that name some parameters but not all, `@param` descriptions without
310
+ the `-` separator, `@since` tags out of position or naming a version the package has not reached, and comment links to
311
+ missing paths.
215
312
 
216
313
  ```bash
217
314
  codefast audit comments # whole repo
@@ -225,80 +322,121 @@ codefast audit comments --json # machine-readable summary
225
322
  | `--fix` | Rewrite every mechanically fixable divider in place. |
226
323
  | `--json` | Print one JSON summary on stdout. |
227
324
 
228
- Configure intentional exceptions via `audit.comments.allowlist` — each entry is a divider line as written or
325
+ Configure exceptions via `audit.comments.allowlist` — each entry is a divider line as written or
229
326
  `repo/relative/path.ts:<divider>`.
230
327
 
231
- ## `audit react`
328
+ ### `audit display-names`
232
329
 
233
- Read-only scan enforcing the repo's React import policy: members are imported by name. Flags `import * as React` and
234
- default `React` imports (type-only included), plus an implicit `React.*` UMD-global type reference (`e: React.FormEvent`
235
- with no import), which `tsc` accepts silently through the `export as namespace React` declaration in `@types/react`.
236
- Exits non-zero when violations remain so it can gate CI.
330
+ _House style._ Enforces the display-name convention for every string a `token()`, `tag()`, or module factory takes: a
331
+ name is spelled like the TS symbol it stands for, under its owner's namespace — `namespace:Name`. The namespace is a
332
+ kebab-case package, app, or feature slug (or a scoped package name); a token or module name is PascalCase, a tag key is
333
+ camelCase. It scans TypeScript and markdown alike, since a doc sample is what a reader copies, and skips `tests/`,
334
+ `benchmarks/`, `.changeset/`, and `CHANGELOG.md`.
237
335
 
238
336
  ```bash
239
- codefast audit react # whole repo
240
- codefast audit react apps/web/src # explicit target
241
- codefast audit react --json # machine-readable summary
337
+ codefast audit display-names # whole repo
338
+ codefast audit display-names packages/di/examples # explicit target
339
+ codefast audit display-names --json # machine-readable summary
242
340
  ```
243
341
 
244
- | Flag | Description |
245
- | -------- | --------------------------------- |
246
- | `--json` | Print one JSON summary on stdout. |
342
+ Configure exceptions via `audit.displayNames.allowlist` — each entry is the call as written, through its closing quote
343
+ (or parenthesis when the name is the only argument), or `repo/relative/path.ts:<call>`.
247
344
 
248
- Configure intentional exceptions via `audit.react.allowlist` — each entry is the offending source text as written or
249
- `repo/relative/path.tsx:<text>`.
345
+ ## Configuration
250
346
 
251
- ## `audit display-names`
347
+ **You do not need a config file.** Every command has sensible defaults and works with none. Add a `codefast.config.*`
348
+ file at your project root only to change a default — and add only the sections for the commands you actually use.
252
349
 
253
- Read-only scan enforcing the display-name convention for every string a `token()`, `tag()` or module factory takes: a
254
- name is spelled like the TS symbol it stands for, under its owner's namespace — `<namespace>:<Name>`. The namespace is a
255
- kebab-case package, app or feature slug (or a scoped package name); a token or module name is PascalCase, because it
256
- stands for a type or a unit of composition; a tag key is camelCase, because it names an attribute. Scans TypeScript and
257
- markdown alike, since a doc sample is what a reader copies; skips `tests/`, `benchmarks/`, `.changeset/` and
258
- `CHANGELOG.md`, where a name is scoped by its file or quoted as it was. Exits non-zero when violations remain so it can
259
- gate CI.
350
+ **Where it goes and how it loads.** The CLI walks up from the working directory and uses the first match, checking
351
+ `codefast.config.mjs`, `codefast.config.js`, `codefast.config.cjs`, then `codefast.config.json` in each directory. JS
352
+ configs are loaded via [jiti](https://github.com/unjs/jiti) — so **only run the CLI in repositories you trust**, and
353
+ note that only a JS config can define `onAfterWrite` hooks (JSON can't hold functions). The schema is **strict**: an
354
+ unknown key is an error, which catches typos immediately.
260
355
 
261
- ```bash
262
- codefast audit display-names # whole repo
263
- codefast audit display-names packages/di/examples # explicit target
264
- codefast audit display-names --json # machine-readable summary
356
+ ### Start small
357
+
358
+ The smallest valid config is empty. Grow it one section at a time — each top-level key configures one command:
359
+
360
+ ```js
361
+ // codefast.config.js
362
+ export default {};
265
363
  ```
266
364
 
267
- | Flag | Description |
268
- | -------- | --------------------------------- |
269
- | `--json` | Print one JSON summary on stdout. |
365
+ | Key | Command | What it configures |
366
+ | --------- | --------- | ---------------------------------------------------------------------------------------------- |
367
+ | `mirror` | `mirror` | per-package `exports` generation — see [per-package config](#per-package-mirror-configuration) |
368
+ | `tag` | `tag` | package names to skip, and a hook to run after writing |
369
+ | `arrange` | `arrange` | a hook to run after writing |
370
+ | `audit` | `audit *` | each audit's default scan target and its `allowlist` of accepted exceptions |
270
371
 
271
- Configure intentional exceptions via `audit.displayNames.allowlist` — each entry is the call as written, through its
272
- closing quote (or parenthesis when the name is the only argument), or `repo/relative/path.ts:<call>`.
372
+ ### Author it with types
273
373
 
274
- ## `tag`
374
+ Don't memorize the shape. Import `defineConfig` (or annotate with the `CodefastConfig` type) and your editor completes
375
+ every key, checks the values, and catches typos **before you run anything** — the types _are_ the reference for what's
376
+ valid, and the strict runtime schema is the backstop.
275
377
 
276
- Adds `@since <version>` tags to the doc comments of exported declarations that lack one, creating the doc block when
277
- there is none. The version comes from the nearest `package.json` above each target file. Declarations that already carry
278
- `@since` are left alone.
378
+ ```ts
379
+ // codefast.config.ts
380
+ import { defineConfig } from "@codefast/cli";
279
381
 
280
- ```bash
281
- codefast tag # auto-discover workspace packages from cwd
282
- codefast tag packages/ui/src # tag one directory or file
283
- codefast tag --dry-run # summary only, no writes
382
+ export default defineConfig({
383
+ mirror: { "@acme/ui": { strip: "./components/" } }, // autocomplete: strip, exclude, source, types, css, …
384
+ });
284
385
  ```
285
386
 
286
- | Flag | Description |
287
- | ----------- | ------------------------------------------------------------- |
288
- | `--dry-run` | Show summary without writing files. |
289
- | `--json` | Print one JSON summary on stdout (suppresses human progress). |
387
+ A plain `.js` config gets the same help through a JSDoc type — no build step, no `.ts`:
290
388
 
291
- Exits `1` when no target is selected, when any target fails, or when the `tag.onAfterWrite` hook fails. In this repo,
292
- `tag` runs inside `pnpm run version-packages` so published APIs carry accurate version metadata — never hand-write
293
- `@since` tags.
389
+ ```js
390
+ // codefast.config.js
391
+ /** @type {import("@codefast/cli").CodefastConfig} */
392
+ export default {
393
+ mirror: { "@acme/ui": { strip: "./components/" } },
394
+ };
395
+ ```
294
396
 
295
- ## Configuration
397
+ ### Common recipes
296
398
 
297
- An optional `codefast.config.*` file adjusts `mirror`, `tag`, `arrange`, and `audit`. The CLI walks up from the working
298
- directory and uses the first match, checking `codefast.config.mjs`, `codefast.config.js`, `codefast.config.cjs`, then
299
- `codefast.config.json` in each directory. JS configs are loaded via [jiti](https://github.com/unjs/jiti), so only run
300
- the CLI in repositories you trust; JSON configs cannot define hooks. The schema is strict: an unknown key is a
301
- configuration error.
399
+ **Run a formatter after a command rewrites files.** `tag` and `arrange` take an `onAfterWrite` hook (sync or async). It
400
+ runs only when files were actually written — never on `--dry-run`:
401
+
402
+ ```js
403
+ // codefast.config.js
404
+ import { execSync } from "node:child_process";
405
+
406
+ const format = ({ files }) => execSync(`oxfmt ${files.join(" ")}`, { stdio: "inherit" });
407
+
408
+ export default {
409
+ tag: { onAfterWrite: format },
410
+ arrange: { onAfterWrite: format },
411
+ };
412
+ ```
413
+
414
+ **Skip packages.** `tag.skipPackages` takes globs matched against package names; `mirror` skips any package set to
415
+ `false`:
416
+
417
+ ```js
418
+ export default {
419
+ tag: { skipPackages: ["@acme/internal", "@apps/*"] },
420
+ mirror: { "@acme/internal": false },
421
+ };
422
+ ```
423
+
424
+ **Accept a known audit finding.** Every audit takes an `allowlist`. An entry is the offending text exactly as it
425
+ appears, or `repo/relative/path:<text>` to scope it to a single file:
426
+
427
+ ```js
428
+ export default {
429
+ audit: {
430
+ imports: { allowlist: [`packages/legacy/src/x.ts:import { z } from "zod";`] },
431
+ rtl: { allowlist: ["packages/ui/src/variants/sheet.ts:data-open:slide-in-from-left-10"] },
432
+ },
433
+ };
434
+ ```
435
+
436
+ ### Complete reference
437
+
438
+ Every section together — see [per-package `mirror` configuration](#per-package-mirror-configuration) for the `mirror`
439
+ keys:
302
440
 
303
441
  ```js
304
442
  // codefast.config.js
@@ -336,13 +474,15 @@ export default {
336
474
  },
337
475
  links: { allowlist: [] }, // bare link target, or `repo/relative/doc.md:target`
338
476
  comments: { allowlist: [] }, // divider as written, or `repo/relative/path.ts:<divider>`
339
- react: { allowlist: [] }, // offending text as written, or `repo/relative/path.tsx:<text>`
477
+ imports: { allowlist: [] }, // offending import text as written, or `repo/relative/path.tsx:<text>`
478
+ displayNames: { allowlist: [] }, // call as written, or `repo/relative/path.ts:<call>`
340
479
  },
341
480
  };
342
481
  ```
343
482
 
344
- `source`, `types`, and `import` default to `true`. The `onAfterWrite` hooks (sync or async) run only when files were
345
- actually written — never on `--dry-run`. A hook failure is reported on stderr and the command exits `1`.
483
+ `source`, `types`, and `import` default to `true`, so an empty `mirror` entry still emits all three. The `onAfterWrite`
484
+ hooks run only when files were actually written — never on `--dry-run`; a hook failure is reported on stderr and the
485
+ command exits `1`.
346
486
 
347
487
  ## Exit codes
348
488
 
@@ -352,6 +492,46 @@ actually written — never on `--dry-run`. A hook failure is reported on stderr
352
492
  | `1` | General failure (missing paths, failed packages, failed hooks). |
353
493
  | `2` | Invalid arguments or configuration. |
354
494
 
495
+ ## Programmatic use
496
+
497
+ `@codefast/cli` is importable as well as executable. `runCli` runs the same CLI in-process and resolves to the exit code
498
+ it would have exited with — the `codefast` binary is a thin wrapper around it.
499
+
500
+ ```ts
501
+ import { runCli } from "@codefast/cli";
502
+
503
+ // `argv` follows the `process.argv` layout: the first two entries are ignored,
504
+ // exactly as when Node runs the binary.
505
+ const exitCode = await runCli(["node", "codefast", "mirror", "--dry-run", "--json"]);
506
+
507
+ if (exitCode !== 0) {
508
+ throw new Error(`codefast exited with ${exitCode}`);
509
+ }
510
+ ```
511
+
512
+ The command still writes its human or `--json` output to stdout/stderr; `runCli` does not capture it. Read stdout
513
+ yourself when you need the structured summary.
514
+
515
+ ## How the codefast monorepo uses it
516
+
517
+ The tool is general; the codefast monorepo just wires convenience scripts and a release step around it — a good template
518
+ if you adopt the CLI in your own workspace. It runs from the built output via root `package.json` scripts:
519
+
520
+ ```bash
521
+ pnpm run codefast <command> # generic entry: node ./packages/cli/dist/bin.js
522
+
523
+ pnpm run cli:arrange # codefast arrange
524
+ pnpm run cli:mirror # codefast mirror
525
+ pnpm run cli:audit:links # codefast audit links
526
+ pnpm run cli:audit:rtl # codefast audit rtl
527
+ pnpm run cli:audit:comments # codefast audit comments
528
+ pnpm run cli:audit:imports # codefast audit imports
529
+ pnpm run cli:audit:display-names # codefast audit display-names
530
+ ```
531
+
532
+ `pnpm run version-packages` runs `changeset version` and then `codefast tag`, so published APIs are stamped at release,
533
+ and the release workflow runs `codefast pack-slim` as its publish step on a clean CI checkout.
534
+
355
535
  ## Documentation
356
536
 
357
537
  - [codefastlabs.com/docs/cli](https://codefastlabs.com/docs/cli) — this document, rendered.