@codefast/cli 0.3.12 → 0.3.13-canary.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.
- package/README.md +206 -51
- package/dist/analyze-DvtJ3j9m.mjs +92 -0
- package/dist/arrange-Bbh62DrU.mjs +107 -0
- package/dist/arrange-BqP2DI3w.mjs +8 -0
- package/dist/ast-helpers-MDRYuIAk.mjs +115 -0
- package/dist/{bin.js → bin.mjs} +4 -1
- package/dist/caught-unknown-message-CRhkDWO7.mjs +20 -0
- package/dist/collectors-cn-BS8TTQ8O.mjs +61 -0
- package/dist/collectors-jsx-D9tL2ZPM.mjs +20 -0
- package/dist/collectors-tv-D2PXZaRt.mjs +170 -0
- package/dist/colors-BjdkCfGK.mjs +12 -0
- package/dist/commands/arrange.mjs +2 -0
- package/dist/commands/mirror.mjs +2 -0
- package/dist/commands/mirror.test.mjs +48 -0
- package/dist/commands/tag.mjs +2 -0
- package/dist/config-DVia_f9a.mjs +3 -0
- package/dist/config-reporter-SCcWDZc1.mjs +10 -0
- package/dist/constants-AtPhF-Ct.mjs +169 -0
- package/dist/constants-Bq5wdyLC.mjs +8 -0
- package/dist/dirent-list-BSDeRG5i.mjs +14 -0
- package/dist/engine-CcWEHSCq.mjs +287 -0
- package/dist/engine-DRTdkc5u.mjs +236 -0
- package/dist/errors-CFH4w6ic.mjs +15 -0
- package/dist/errors-CpU1ogfA.mjs +19 -0
- package/dist/formatters-DGE8jJ53.mjs +52 -0
- package/dist/group-file-ClW7XdD6.mjs +113 -0
- package/dist/grouping-c1grpCQT.mjs +235 -0
- package/dist/imports-DSVq1xhB.mjs +54 -0
- package/dist/lib/arrange/application/analyze.mjs +2 -0
- package/dist/lib/arrange/application/analyze.test.mjs +171 -0
- package/dist/lib/arrange/application/group-file.mjs +2 -0
- package/dist/lib/arrange/application/group-file.test.mjs +273 -0
- package/dist/lib/arrange/application/run-target.mjs +2 -0
- package/dist/lib/arrange/application/run-target.test.mjs +97 -0
- package/dist/lib/arrange/domain/ast/ast-helpers.mjs +2 -0
- package/dist/lib/arrange/domain/ast/ast-helpers.test.mjs +148 -0
- package/dist/lib/arrange/domain/ast/collectors-cn.mjs +2 -0
- package/dist/lib/arrange/domain/ast/collectors-cn.test.mjs +58 -0
- package/dist/lib/arrange/domain/ast/collectors-jsx.mjs +2 -0
- package/dist/lib/arrange/domain/ast/collectors-jsx.test.mjs +20 -0
- package/dist/lib/arrange/domain/ast/collectors-tv.mjs +2 -0
- package/dist/lib/arrange/domain/ast/collectors-tv.test.mjs +145 -0
- package/dist/lib/arrange/domain/ast/targets.mjs +2 -0
- package/dist/lib/arrange/domain/ast/targets.test.mjs +50 -0
- package/dist/lib/arrange/domain/constants.mjs +2 -0
- package/dist/lib/arrange/domain/errors.mjs +2 -0
- package/dist/lib/arrange/domain/grouping.mjs +2 -0
- package/dist/lib/arrange/domain/grouping.test.mjs +186 -0
- package/dist/lib/arrange/domain/imports.mjs +2 -0
- package/dist/lib/arrange/domain/imports.test.mjs +48 -0
- package/dist/lib/arrange/domain/tokenizer.mjs +2 -0
- package/dist/lib/arrange/domain/tokenizer.test.mjs +165 -0
- package/dist/lib/arrange/index.mjs +16 -0
- package/dist/lib/arrange/infra/walk.mjs +2 -0
- package/dist/lib/arrange/infra/walk.test.mjs +50 -0
- package/dist/lib/arrange/presentation/formatters.mjs +2 -0
- package/dist/lib/arrange/presentation/formatters.test.mjs +73 -0
- package/dist/lib/arrange/presentation/report.mjs +2 -0
- package/dist/lib/config/domain/schema.mjs +2 -0
- package/dist/lib/config/index.mjs +4 -0
- package/dist/lib/config/infra/loader.mjs +2 -0
- package/dist/lib/config/infra/loader.test.mjs +116 -0
- package/dist/lib/infra/caught-unknown-message.mjs +2 -0
- package/dist/lib/infra/config-reporter.mjs +2 -0
- package/dist/lib/infra/node-io.mjs +2 -0
- package/dist/lib/infra/workspace/repo-root.mjs +2 -0
- package/dist/lib/infra/workspace/repo-root.test.mjs +45 -0
- package/dist/lib/mirror/application/engine.mjs +2 -0
- package/dist/lib/mirror/application/sync.mjs +2 -0
- package/dist/lib/mirror/application/sync.test.mjs +482 -0
- package/dist/lib/mirror/domain/constants.mjs +2 -0
- package/dist/lib/mirror/domain/errors.mjs +2 -0
- package/dist/lib/mirror/domain/types.mjs +1 -0
- package/dist/lib/mirror/index.mjs +3 -0
- package/dist/lib/mirror/infra/dirent-list.mjs +2 -0
- package/dist/lib/mirror/infra/dirent-list.test.mjs +18 -0
- package/dist/lib/mirror/infra/package-filter.mjs +2 -0
- package/dist/lib/mirror/infra/package-filter.test.mjs +32 -0
- package/dist/lib/mirror/infra/path-normalizer.mjs +2 -0
- package/dist/lib/mirror/infra/update-pkg.mjs +2 -0
- package/dist/lib/mirror/infra/workspace-packages.mjs +2 -0
- package/dist/lib/mirror/infra/workspace-packages.test.mjs +167 -0
- package/dist/lib/mirror/presentation/reporter.mjs +2 -0
- package/dist/lib/tag/application/engine.mjs +2 -0
- package/dist/lib/tag/application/engine.test.mjs +159 -0
- package/dist/lib/tag/domain/types.mjs +1 -0
- package/dist/lib/tag/index.mjs +5 -0
- package/dist/lib/tag/infra/target-resolver.mjs +2 -0
- package/dist/lib/tag/presentation/colors.mjs +2 -0
- package/dist/lib/tag/presentation/tag-presenter.mjs +2 -0
- package/dist/lib/tag/presentation/tag-presenter.test.mjs +29 -0
- package/dist/loader-DpN-zl5g.mjs +78 -0
- package/dist/mirror-CbGiLXKT.mjs +2 -0
- package/dist/mirror-DqJE3ejL.mjs +66 -0
- package/dist/node-io-DFRbHO6m.mjs +30 -0
- package/dist/package-filter-C_8ryqvz.mjs +26 -0
- package/dist/path-normalizer-D4ceBO3l.mjs +7 -0
- package/dist/program-DR-H4tH3.mjs +34 -0
- package/dist/program.mjs +2 -0
- package/dist/repo-root-HmtIEm6J.mjs +22 -0
- package/dist/report-DYxvw34T.mjs +24 -0
- package/dist/reporter-CwIe_tAM.mjs +108 -0
- package/dist/run-target-CJWrqNT4.mjs +65 -0
- package/dist/schema-Or8RAARd.mjs +25 -0
- package/dist/sync-e1Sp-shi.mjs +157 -0
- package/dist/tag-Cd9JujU5.mjs +2 -0
- package/dist/tag-hEKVZ_-7.mjs +58 -0
- package/dist/tag-presenter-Cs66UBsH.mjs +63 -0
- package/dist/target-resolver-CR3FXMTy.mjs +170 -0
- package/dist/targets-pCTslnFh.mjs +106 -0
- package/dist/tokenizer-CeZx73Zu.mjs +216 -0
- package/dist/update-pkg-CnGfjWBy.mjs +37 -0
- package/dist/walk-DjgQn66n.mjs +33 -0
- package/dist/workspace-packages-K2NYTHJq.mjs +159 -0
- package/package.json +11 -11
- package/dist/bin.d.ts +0 -3
- package/dist/bin.d.ts.map +0 -1
- package/dist/commands/arrange.d.ts +0 -3
- package/dist/commands/arrange.d.ts.map +0 -1
- package/dist/commands/arrange.js +0 -114
- package/dist/commands/mirror.d.ts +0 -4
- package/dist/commands/mirror.d.ts.map +0 -1
- package/dist/commands/mirror.js +0 -63
- package/dist/lib/arrange/analyze.d.ts +0 -4
- package/dist/lib/arrange/analyze.d.ts.map +0 -1
- package/dist/lib/arrange/analyze.js +0 -105
- package/dist/lib/arrange/ast/collectors-cn.d.ts +0 -10
- package/dist/lib/arrange/ast/collectors-cn.d.ts.map +0 -1
- package/dist/lib/arrange/ast/collectors-cn.js +0 -84
- package/dist/lib/arrange/ast/collectors-jsx.d.ts +0 -4
- package/dist/lib/arrange/ast/collectors-jsx.d.ts.map +0 -1
- package/dist/lib/arrange/ast/collectors-jsx.js +0 -18
- package/dist/lib/arrange/ast/collectors-tv.d.ts +0 -13
- package/dist/lib/arrange/ast/collectors-tv.d.ts.map +0 -1
- package/dist/lib/arrange/ast/collectors-tv.js +0 -273
- package/dist/lib/arrange/ast/targets.d.ts +0 -8
- package/dist/lib/arrange/ast/targets.d.ts.map +0 -1
- package/dist/lib/arrange/ast/targets.js +0 -143
- package/dist/lib/arrange/ast/utils.d.ts +0 -36
- package/dist/lib/arrange/ast/utils.d.ts.map +0 -1
- package/dist/lib/arrange/ast/utils.js +0 -138
- package/dist/lib/arrange/constants.d.ts +0 -68
- package/dist/lib/arrange/constants.d.ts.map +0 -1
- package/dist/lib/arrange/constants.js +0 -179
- package/dist/lib/arrange/errors.d.ts +0 -14
- package/dist/lib/arrange/errors.d.ts.map +0 -1
- package/dist/lib/arrange/errors.js +0 -16
- package/dist/lib/arrange/formatters.d.ts +0 -16
- package/dist/lib/arrange/formatters.d.ts.map +0 -1
- package/dist/lib/arrange/formatters.js +0 -57
- package/dist/lib/arrange/group-file.d.ts +0 -4
- package/dist/lib/arrange/group-file.d.ts.map +0 -1
- package/dist/lib/arrange/group-file.js +0 -115
- package/dist/lib/arrange/grouping.d.ts +0 -41
- package/dist/lib/arrange/grouping.d.ts.map +0 -1
- package/dist/lib/arrange/grouping.js +0 -280
- package/dist/lib/arrange/imports.d.ts +0 -6
- package/dist/lib/arrange/imports.d.ts.map +0 -1
- package/dist/lib/arrange/imports.js +0 -74
- package/dist/lib/arrange/report.d.ts +0 -4
- package/dist/lib/arrange/report.d.ts.map +0 -1
- package/dist/lib/arrange/report.js +0 -37
- package/dist/lib/arrange/run-target.d.ts +0 -4
- package/dist/lib/arrange/run-target.d.ts.map +0 -1
- package/dist/lib/arrange/run-target.js +0 -28
- package/dist/lib/arrange/tokenizer.d.ts +0 -37
- package/dist/lib/arrange/tokenizer.d.ts.map +0 -1
- package/dist/lib/arrange/tokenizer.js +0 -390
- package/dist/lib/arrange/types.d.ts +0 -104
- package/dist/lib/arrange/types.d.ts.map +0 -1
- package/dist/lib/arrange/types.js +0 -7
- package/dist/lib/arrange/walk.d.ts +0 -4
- package/dist/lib/arrange/walk.d.ts.map +0 -1
- package/dist/lib/arrange/walk.js +0 -34
- package/dist/lib/arrange.d.ts +0 -41
- package/dist/lib/arrange.d.ts.map +0 -1
- package/dist/lib/arrange.js +0 -38
- package/dist/lib/infra/fs-contract.d.ts +0 -28
- package/dist/lib/infra/fs-contract.d.ts.map +0 -1
- package/dist/lib/infra/node-io.d.ts +0 -4
- package/dist/lib/infra/node-io.d.ts.map +0 -1
- package/dist/lib/infra/node-io.js +0 -27
- package/dist/lib/mirror/config.d.ts +0 -7
- package/dist/lib/mirror/config.d.ts.map +0 -1
- package/dist/lib/mirror/config.js +0 -146
- package/dist/lib/mirror/constants.d.ts +0 -10
- package/dist/lib/mirror/constants.d.ts.map +0 -1
- package/dist/lib/mirror/constants.js +0 -13
- package/dist/lib/mirror/engine.d.ts +0 -12
- package/dist/lib/mirror/engine.d.ts.map +0 -1
- package/dist/lib/mirror/engine.js +0 -248
- package/dist/lib/mirror/errors.d.ts +0 -10
- package/dist/lib/mirror/errors.d.ts.map +0 -1
- package/dist/lib/mirror/errors.js +0 -12
- package/dist/lib/mirror/package-filter.d.ts +0 -8
- package/dist/lib/mirror/package-filter.d.ts.map +0 -1
- package/dist/lib/mirror/package-filter.js +0 -33
- package/dist/lib/mirror/reporter.d.ts +0 -24
- package/dist/lib/mirror/reporter.d.ts.map +0 -1
- package/dist/lib/mirror/reporter.js +0 -126
- package/dist/lib/mirror/sync.d.ts +0 -4
- package/dist/lib/mirror/sync.d.ts.map +0 -1
- package/dist/lib/mirror/sync.js +0 -135
- package/dist/lib/mirror/types.d.ts +0 -81
- package/dist/lib/mirror/types.d.ts.map +0 -1
- package/dist/lib/mirror/update-pkg.d.ts +0 -13
- package/dist/lib/mirror/update-pkg.d.ts.map +0 -1
- package/dist/lib/mirror/update-pkg.js +0 -39
- package/dist/lib/mirror/workspace-packages.d.ts +0 -26
- package/dist/lib/mirror/workspace-packages.d.ts.map +0 -1
- package/dist/lib/mirror/workspace-packages.js +0 -160
- package/dist/lib/mirror.d.ts +0 -7
- package/dist/lib/mirror.d.ts.map +0 -1
- package/dist/lib/mirror.js +0 -5
- package/dist/lib/repo-root.d.ts +0 -7
- package/dist/lib/repo-root.d.ts.map +0 -1
- package/dist/lib/repo-root.js +0 -23
- package/dist/lib/shared/utils.d.ts +0 -9
- package/dist/lib/shared/utils.d.ts.map +0 -1
- package/dist/lib/shared/utils.js +0 -12
- package/dist/program.d.ts +0 -4
- package/dist/program.d.ts.map +0 -1
- package/dist/program.js +0 -38
- /package/dist/lib/{infra/fs-contract.js → arrange/domain/types.mjs} +0 -0
- /package/dist/lib/{mirror/types.js → infra/fs-contract.mjs} +0 -0
package/README.md
CHANGED
|
@@ -1,100 +1,255 @@
|
|
|
1
1
|
# @codefast/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A focused CLI for two recurring maintenance tasks in monorepos:
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
- **`arrange`** — analyze and regroup Tailwind class strings inside `cn()` / `tv()` calls according to a consistent render-pipeline order.
|
|
6
|
+
- **`mirror`** — regenerate `package.json` `exports` fields from built `dist/` trees across a pnpm workspace.
|
|
7
|
+
- **`tag`** (alias: **`annotate`**) — auto-add `@since <version>` to exported TypeScript declarations that are still missing version metadata.
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart LR
|
|
11
|
+
R[codefast]
|
|
12
|
+
R --> A[arrange]
|
|
13
|
+
R --> M[mirror]
|
|
14
|
+
R --> T[tag]
|
|
15
|
+
|
|
16
|
+
A --> A0[analyze]
|
|
17
|
+
A --> A1[preview]
|
|
18
|
+
A --> A2[apply]
|
|
19
|
+
A --> A3[group]
|
|
20
|
+
|
|
21
|
+
M --> M0[sync]
|
|
22
|
+
```
|
|
9
23
|
|
|
10
24
|
---
|
|
11
25
|
|
|
12
|
-
## Requirements
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- Node.js `>=22.0.0`
|
|
29
|
+
- pnpm (recommended)
|
|
30
|
+
|
|
31
|
+
---
|
|
13
32
|
|
|
14
|
-
|
|
33
|
+
## Installation
|
|
15
34
|
|
|
16
35
|
```bash
|
|
17
|
-
#
|
|
36
|
+
# Install globally
|
|
18
37
|
pnpm add -g @codefast/cli
|
|
19
38
|
|
|
20
|
-
#
|
|
21
|
-
pnpm dlx @codefast/cli --
|
|
39
|
+
# Or run without installing
|
|
40
|
+
pnpm dlx @codefast/cli --help
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# 1. Preview proposed changes — no files written
|
|
49
|
+
codefast arrange preview packages/ui/src/components
|
|
50
|
+
|
|
51
|
+
# 2. Apply after reviewing the diff
|
|
52
|
+
codefast arrange apply packages/ui/src/components
|
|
53
|
+
|
|
54
|
+
# 3. Regenerate package exports from built dist/
|
|
55
|
+
codefast mirror sync
|
|
56
|
+
|
|
57
|
+
# 4. Add @since tags to exported APIs under src/
|
|
58
|
+
codefast tag
|
|
22
59
|
```
|
|
23
60
|
|
|
24
61
|
---
|
|
25
62
|
|
|
26
63
|
## `arrange`
|
|
27
64
|
|
|
28
|
-
|
|
65
|
+
Reads `cn()` and `tv()` call sites, classifies each Tailwind utility, and rewrites the class strings in render-pipeline order (see [Grouping philosophy](#grouping-philosophy--render-pipeline-order) below).
|
|
66
|
+
|
|
67
|
+
### Workflow
|
|
29
68
|
|
|
30
|
-
|
|
31
|
-
2. **`preview [target]`** — Same transforms as `apply`, but writes nothing. Inspect stdout before touching the tree.
|
|
32
|
-
3. **`apply [target]`** — Writes edits. Run `preview` first.
|
|
69
|
+
Run the three subcommands in order:
|
|
33
70
|
|
|
34
|
-
|
|
71
|
+
| Step | Command | Effect |
|
|
72
|
+
| ---- | ----------------------------------- | ------------------------------------- |
|
|
73
|
+
| 1 | `codefast arrange analyze [target]` | Report only — no files changed |
|
|
74
|
+
| 2 | `codefast arrange preview [target]` | Show exactly what `apply` would write |
|
|
75
|
+
| 3 | `codefast arrange apply [target]` | Write the changes |
|
|
35
76
|
|
|
36
|
-
|
|
77
|
+
The default `target` when omitted is `packages/ui/src/components`, resolved from `process.cwd()`.
|
|
37
78
|
|
|
38
|
-
|
|
39
|
-
- **`--cn-import <spec>`** — Override the module specifier when the tool adds a `cn` import.
|
|
79
|
+
### Flags
|
|
40
80
|
|
|
41
|
-
|
|
81
|
+
| Flag | Description |
|
|
82
|
+
| -------------------- | ----------------------------------------------------------------------- |
|
|
83
|
+
| `--with-class-name` | Append `className` as the last argument when rewriting a `cn(...)` call |
|
|
84
|
+
| `--cn-import <spec>` | Override the module specifier used when adding a missing `cn` import |
|
|
42
85
|
|
|
43
|
-
|
|
86
|
+
### `arrange group` — one-shot string grouping
|
|
87
|
+
|
|
88
|
+
Groups a single class string without touching the filesystem. Useful for checking how a string would be classified before running `apply`:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
codefast arrange group "relative flex items-center h-10 w-full rounded-md bg-primary text-white hover:bg-primary/90"
|
|
92
|
+
```
|
|
44
93
|
|
|
45
94
|
---
|
|
46
95
|
|
|
47
|
-
##
|
|
96
|
+
## `mirror sync`
|
|
48
97
|
|
|
49
|
-
`
|
|
98
|
+
Scans built `dist/` trees and regenerates the `exports` field in each `package.json`. Run from anywhere inside the monorepo — the workspace root is discovered automatically via `pnpm-workspace.yaml`.
|
|
50
99
|
|
|
51
|
-
|
|
100
|
+
```bash
|
|
101
|
+
codefast mirror sync # all packages in the workspace
|
|
102
|
+
codefast mirror sync packages/ui # a single package path
|
|
103
|
+
codefast mirror sync -v # verbose output
|
|
104
|
+
```
|
|
52
105
|
|
|
53
|
-
|
|
106
|
+
> **Note:** Packages must be built first so `dist/` exists. Run your build step before `mirror sync`.
|
|
107
|
+
|
|
108
|
+
### Configuration
|
|
109
|
+
|
|
110
|
+
Create a `codefast.config.js` (or `.mjs`, `.cjs`, `.json`) at the repo root with a `mirror` key:
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
// codefast.config.mjs
|
|
114
|
+
export default {
|
|
115
|
+
mirror: {
|
|
116
|
+
skipPackages: ["@acme/internal"],
|
|
117
|
+
pathTransformations: {
|
|
118
|
+
"@acme/ui": {
|
|
119
|
+
removePrefix: "./components/",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
customExports: {
|
|
123
|
+
"@acme/ui": {
|
|
124
|
+
"./css/*": "./src/styles/*",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
cssExports: {
|
|
128
|
+
"@acme/ui": {
|
|
129
|
+
enabled: true,
|
|
130
|
+
customExports: {
|
|
131
|
+
"./tokens.css": "./dist/tokens.css",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
```
|
|
54
138
|
|
|
55
|
-
|
|
56
|
-
| -------------- | ------------------------------------------------- | ------------------------------------------------- |
|
|
57
|
-
| **Existence** | Display / containment context | `hidden`, `block`, `@container`, `group`, `peer` |
|
|
58
|
-
| **Position** | Where the box sits | `absolute`, `inset-*`, `top-*`, `z-*` |
|
|
59
|
-
| **Layout** | How children flow | `flex`, `grid`, `gap-*`, `items-*` |
|
|
60
|
-
| **Sizing** | Box dimensions and overflow | `w-*`, `h-*`, `aspect-*`, `overflow-*` |
|
|
61
|
-
| **Spacing** | Padding and margin only (gaps stay with Layout) | `p-*`, `m-*` |
|
|
62
|
-
| **Shape** | Corners and strokes | `rounded-*`, `border-*`, `ring-*` |
|
|
63
|
-
| **Background** | Surfaces and masks | `bg-*`, `from-*`, `via-*`, `to-*`, `mask-*` |
|
|
64
|
-
| **Shadow** | Depth | `shadow-*`, `inset-shadow-*`, `text-shadow-*` |
|
|
65
|
-
| **Typography** | Text appearance | `font-*`, `text-*`, `leading-*` |
|
|
66
|
-
| **Composite** | Layers and transforms — 3D context → 3D → 2D | `opacity-*`, `rotate-x-*`, `translate-*` |
|
|
67
|
-
| **Motion** | Time-based change | `transition-*`, `animate-*` |
|
|
68
|
-
| **Starting** | Tailwind's `starting:` layer, kept next to Motion | `starting:*` |
|
|
69
|
-
| **Behavior** | Input / scrolling / chrome | `cursor-*`, `scroll-*`, `field-sizing-*`, `inert` |
|
|
70
|
-
| **State** | Everything with a variant stack | `hover:`, `md:`, `@md/sidebar:`, `data-[…]:` |
|
|
139
|
+
Use your real package names from `package.json#name` (for example `@acme/ui`) and adjust entries to match your workspace.
|
|
71
140
|
|
|
72
|
-
|
|
141
|
+
> **Migration:** Path-based keys (for example `packages/ui`) are deprecated for `pathTransformations`, `customExports`, `cssExports`, and `skipPackages`. Migrate to package-name keys.
|
|
73
142
|
|
|
74
|
-
|
|
143
|
+
> **Security note:** `.js`, `.mjs`, and `.cjs` config files are loaded via `import()`. Only run `mirror sync` in repositories you trust.
|
|
75
144
|
|
|
76
145
|
---
|
|
77
146
|
|
|
78
|
-
##
|
|
147
|
+
## Lifecycle hooks (`codefast.config.mjs`)
|
|
148
|
+
|
|
149
|
+
`codefast` supports lifecycle hooks so teams can plug in their own post-write workflow (formatter, lint-fix, codemods) without hardcoding any formatter inside CLI core.
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
import { execSync } from "node:child_process";
|
|
153
|
+
|
|
154
|
+
export default {
|
|
155
|
+
tag: {
|
|
156
|
+
onAfterWrite: ({ files }) => {
|
|
157
|
+
console.log(`Formatting ${files.length} files with Oxc...`);
|
|
158
|
+
execSync(`npx oxc format ${files.join(" ")}`, { stdio: "inherit" });
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
arrange: {
|
|
162
|
+
onAfterWrite: ({ files }) => {
|
|
163
|
+
execSync(`npx oxc format ${files.join(" ")}`, { stdio: "inherit" });
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Hook contract:
|
|
170
|
+
|
|
171
|
+
- `tag.onAfterWrite?.({ files })` runs after `codefast tag` writes files.
|
|
172
|
+
- `arrange.onAfterWrite?.({ files })` runs after `codefast arrange apply` writes files.
|
|
173
|
+
- Hooks support both sync and async functions (`void | Promise<void>`).
|
|
174
|
+
- Hook errors are logged but do not crash the CLI process.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## `tag` / `annotate`
|
|
79
179
|
|
|
80
|
-
|
|
180
|
+
Scans `.ts` / `.tsx` source files and annotates exported declarations with `@since <current-package-version>`. This keeps API evolution visible and reduces documentation drift in long-lived codebases.
|
|
81
181
|
|
|
82
182
|
```bash
|
|
83
|
-
codefast
|
|
84
|
-
codefast
|
|
85
|
-
codefast
|
|
183
|
+
codefast tag # annotate exports in ./src
|
|
184
|
+
codefast tag packages/ui/src # annotate a custom target
|
|
185
|
+
codefast annotate --dry-run # preview only, do not write files
|
|
86
186
|
```
|
|
87
187
|
|
|
88
|
-
|
|
188
|
+
What it updates:
|
|
89
189
|
|
|
90
|
-
|
|
190
|
+
- Adds `/** @since <version> */` when an exported declaration has no JSDoc.
|
|
191
|
+
- Injects `@since <version>` into an existing JSDoc block when missing.
|
|
192
|
+
- Leaves declarations unchanged when `@since` is already present.
|
|
193
|
+
|
|
194
|
+
The `<version>` value is read from the nearest `package.json` found by walking up from the target path.
|
|
91
195
|
|
|
92
196
|
---
|
|
93
197
|
|
|
94
|
-
##
|
|
198
|
+
## Grouping philosophy — Render Pipeline Order
|
|
199
|
+
|
|
200
|
+
`arrange` does **not** sort classes alphabetically. Instead, it groups utilities in roughly the order the browser applies them — from the box's existence, through its shape and surface, to interactive behavior. This makes class strings easier to scan and reason about at a glance.
|
|
201
|
+
|
|
202
|
+
**Existence → Position → Layout → Sizing → Spacing → Shape → Background → Shadow → Typography → Composite → Motion → Starting → Behavior → State → Selector**
|
|
203
|
+
|
|
204
|
+
| Bucket | What it covers | Examples |
|
|
205
|
+
| -------------- | --------------------------------------------------- | ------------------------------------------------- |
|
|
206
|
+
| **Existence** | Display and containment context | `hidden`, `block`, `@container`, `group`, `peer` |
|
|
207
|
+
| **Position** | Where the box sits | `absolute`, `inset-*`, `top-*`, `z-*` |
|
|
208
|
+
| **Layout** | How children flow | `flex`, `grid`, `gap-*`, `items-*` |
|
|
209
|
+
| **Sizing** | Box dimensions and overflow | `w-*`, `h-*`, `aspect-*`, `overflow-*` |
|
|
210
|
+
| **Spacing** | Padding and margin only (gaps stay with Layout) | `p-*`, `m-*` |
|
|
211
|
+
| **Shape** | Corners and strokes | `rounded-*`, `border-*`, `ring-*` |
|
|
212
|
+
| **Background** | Surfaces and masks | `bg-*`, `from-*`, `via-*`, `to-*`, `mask-*` |
|
|
213
|
+
| **Shadow** | Depth | `shadow-*`, `inset-shadow-*`, `text-shadow-*` |
|
|
214
|
+
| **Typography** | Text appearance | `font-*`, `text-*`, `leading-*` |
|
|
215
|
+
| **Composite** | Layers and transforms (3D context → 3D → 2D) | `opacity-*`, `rotate-x-*`, `translate-*` |
|
|
216
|
+
| **Motion** | Time-based change | `transition-*`, `animate-*` |
|
|
217
|
+
| **Starting** | Tailwind's `starting:` layer — kept next to Motion | `starting:*` |
|
|
218
|
+
| **Behavior** | Input, scrolling, and browser chrome | `cursor-*`, `scroll-*`, `field-sizing-*`, `inert` |
|
|
219
|
+
| **State** | Interactive and conditional variants (non-selector) | `hover:`, `md:`, `@md/sidebar:`, `data-[…]:` |
|
|
220
|
+
| **Selector** | Selector-driven variants | `[&…]:`, `*:`, `**:`, `has-*`, `group-[…]:` |
|
|
221
|
+
|
|
222
|
+
Adjacent buckets may be merged into one string literal when declared _compatible_ (e.g. `layout` + `sizing`). This keeps `cn()` calls readable without flattening unrelated concerns into a single undifferentiated blob.
|
|
223
|
+
|
|
224
|
+
To change a placement, edit `classifyBareUtility` in `src/lib/arrange/tokenizer.ts` and add a corresponding `classifyToken` test in `src/lib/arrange.test.ts`.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Troubleshooting
|
|
229
|
+
|
|
230
|
+
**`codefast: command not found`**
|
|
231
|
+
Install globally with `pnpm add -g @codefast/cli`, or run via `pnpm dlx @codefast/cli <command>`.
|
|
232
|
+
|
|
233
|
+
**`mirror sync` writes little or no output**
|
|
234
|
+
Packages must be built before syncing. Ensure `dist/` exists by running your build step first, then re-run `codefast mirror sync`.
|
|
235
|
+
|
|
236
|
+
**Unexpected class reorder after `arrange apply`**
|
|
237
|
+
Run `arrange preview` before applying and smoke-test the UI. Some components rely on cascade-sensitive ordering that `arrange` cannot detect automatically.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Contributing (monorepo setup)
|
|
95
242
|
|
|
96
243
|
```bash
|
|
244
|
+
# Build the local CLI (produces dist/bin.js)
|
|
245
|
+
pnpm --filter @codefast/cli build
|
|
246
|
+
|
|
247
|
+
# Run the local entrypoint
|
|
97
248
|
pnpm exec codefast --help
|
|
98
249
|
```
|
|
99
250
|
|
|
100
|
-
|
|
251
|
+
A few naming conventions to keep in mind:
|
|
252
|
+
|
|
253
|
+
- **`codefast <command>`** refers to CLI commands exposed via the `@codefast/cli` `bin` entry.
|
|
254
|
+
- **Scripts in `packages/cli/package.json`** (`build`, `test`, …) are package-local dev scripts, not CLI commands.
|
|
255
|
+
- The root `package.json` includes optional convenience wrappers such as `cli:mirror-sync` and `cli:arrange-analyze` for common dev workflows.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import "./constants-AtPhF-Ct.mjs";
|
|
2
|
+
import { d as tokenizeClassString } from "./tokenizer-CeZx73Zu.mjs";
|
|
3
|
+
import { r as forEachStringLiteralInClassExpression } from "./collectors-cn-BS8TTQ8O.mjs";
|
|
4
|
+
import { t as jsxClassNameStaticLiteral } from "./collectors-jsx-D9tL2ZPM.mjs";
|
|
5
|
+
import { a as isCnOrTvIdentifier, o as lineOf, r as buildKnownCnTvBindings } from "./ast-helpers-MDRYuIAk.mjs";
|
|
6
|
+
import { c as traverseTvObject, t as collectCnCallsInsideTv } from "./collectors-tv-D2PXZaRt.mjs";
|
|
7
|
+
import { n as walkTsxFiles } from "./walk-DjgQn66n.mjs";
|
|
8
|
+
import ts from "typescript";
|
|
9
|
+
//#region src/lib/arrange/application/analyze.ts
|
|
10
|
+
function analyzeCnCall(sf, call, report) {
|
|
11
|
+
for (const arg of call.arguments) forEachStringLiteralInClassExpression(arg, (lit) => {
|
|
12
|
+
const text = lit.text;
|
|
13
|
+
const tokenCount = tokenizeClassString(text).length;
|
|
14
|
+
if (tokenCount >= 18) report.longCnStringLiterals.push({
|
|
15
|
+
file: sf.fileName,
|
|
16
|
+
line: lineOf(sf, lit),
|
|
17
|
+
tokenCount,
|
|
18
|
+
preview: text.length > 72 ? `${text.slice(0, 72)}…` : text
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function visitCallExpressionForArrangeAnalyze(callExpression, sf, sourceText, knownBindings, report) {
|
|
23
|
+
if (isCnOrTvIdentifier(callExpression.expression, "cn", knownBindings)) {
|
|
24
|
+
report.cnCallExpressions++;
|
|
25
|
+
analyzeCnCall(sf, callExpression, report);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!isCnOrTvIdentifier(callExpression.expression, "tv", knownBindings)) return;
|
|
29
|
+
report.tvCallExpressions++;
|
|
30
|
+
const arg0 = callExpression.arguments[0];
|
|
31
|
+
if (!arg0 || !ts.isObjectLiteralExpression(arg0)) return;
|
|
32
|
+
for (const nestedCn of collectCnCallsInsideTv(sf, arg0, knownBindings, 0)) {
|
|
33
|
+
const src = sourceText.slice(nestedCn.getStart(sf), nestedCn.getEnd());
|
|
34
|
+
const preview = src.length > 72 ? `${src.slice(0, 72)}…` : src;
|
|
35
|
+
report.cnInsideTvCalls.push({
|
|
36
|
+
file: sf.fileName,
|
|
37
|
+
line: lineOf(sf, nestedCn),
|
|
38
|
+
argCount: nestedCn.arguments.length,
|
|
39
|
+
preview
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
traverseTvObject(sf, arg0, (classLiteral) => {
|
|
43
|
+
const text = classLiteral.text;
|
|
44
|
+
const tokenCount = tokenizeClassString(text).length;
|
|
45
|
+
if (tokenCount >= 18) report.longTvStringLiterals.push({
|
|
46
|
+
file: sf.fileName,
|
|
47
|
+
line: lineOf(sf, classLiteral),
|
|
48
|
+
tokenCount,
|
|
49
|
+
preview: text.length > 72 ? `${text.slice(0, 72)}…` : text
|
|
50
|
+
});
|
|
51
|
+
}, 0, knownBindings);
|
|
52
|
+
}
|
|
53
|
+
function visitJsxAttributeForArrangeAnalyze(jsxClassAttribute, sf, filePath, report) {
|
|
54
|
+
if (!filePath.endsWith(".tsx")) return;
|
|
55
|
+
const parsed = jsxClassNameStaticLiteral(jsxClassAttribute);
|
|
56
|
+
if (!parsed) return;
|
|
57
|
+
const text = parsed.lit.text;
|
|
58
|
+
const tokenCount = tokenizeClassString(text).length;
|
|
59
|
+
if (tokenCount >= 18) report.longJsxClassNameLiterals.push({
|
|
60
|
+
file: sf.fileName,
|
|
61
|
+
line: lineOf(sf, parsed.lit),
|
|
62
|
+
tokenCount,
|
|
63
|
+
preview: text.length > 72 ? `${text.slice(0, 72)}…` : text
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function analyzeDirectory(analyzeRootPath, fs) {
|
|
67
|
+
const report = {
|
|
68
|
+
files: 0,
|
|
69
|
+
cnCallExpressions: 0,
|
|
70
|
+
tvCallExpressions: 0,
|
|
71
|
+
cnInsideTvCalls: [],
|
|
72
|
+
longCnStringLiterals: [],
|
|
73
|
+
longTvStringLiterals: [],
|
|
74
|
+
longJsxClassNameLiterals: []
|
|
75
|
+
};
|
|
76
|
+
const files = fs.statSync(analyzeRootPath).isDirectory() ? walkTsxFiles(analyzeRootPath, fs) : [analyzeRootPath];
|
|
77
|
+
for (const filePath of files) {
|
|
78
|
+
const sourceText = fs.readFileSync(filePath, "utf8");
|
|
79
|
+
const sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
80
|
+
report.files++;
|
|
81
|
+
const knownBindings = buildKnownCnTvBindings(sf);
|
|
82
|
+
const visitTypeScriptSubtree = (tsNode) => {
|
|
83
|
+
if (ts.isCallExpression(tsNode)) visitCallExpressionForArrangeAnalyze(tsNode, sf, sourceText, knownBindings, report);
|
|
84
|
+
if (ts.isJsxAttribute(tsNode)) visitJsxAttributeForArrangeAnalyze(tsNode, sf, filePath, report);
|
|
85
|
+
ts.forEachChild(tsNode, visitTypeScriptSubtree);
|
|
86
|
+
};
|
|
87
|
+
visitTypeScriptSubtree(sf);
|
|
88
|
+
}
|
|
89
|
+
return report;
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { analyzeDirectory as t };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { t as messageFromCaughtUnknown } from "./caught-unknown-message-CRhkDWO7.mjs";
|
|
2
|
+
import { t as loadConfig } from "./loader-DpN-zl5g.mjs";
|
|
3
|
+
import "./config-DVia_f9a.mjs";
|
|
4
|
+
import { t as printConfigSchemaWarnings } from "./config-reporter-SCcWDZc1.mjs";
|
|
5
|
+
import { i as DEFAULT_ARRANGE_TARGET } from "./constants-AtPhF-Ct.mjs";
|
|
6
|
+
import { t as analyzeDirectory } from "./analyze-DvtJ3j9m.mjs";
|
|
7
|
+
import { t as printAnalyzeReport } from "./report-DYxvw34T.mjs";
|
|
8
|
+
import { a as suggestCnGroups, o as summarizeGroupBucketLabels } from "./grouping-c1grpCQT.mjs";
|
|
9
|
+
import { i as formatCnCall, n as formatArray } from "./formatters-DGE8jJ53.mjs";
|
|
10
|
+
import { t as runArrangeSync } from "./run-target-CJWrqNT4.mjs";
|
|
11
|
+
import { n as createNodeCliLogger, t as createNodeCliFs } from "./node-io-DFRbHO6m.mjs";
|
|
12
|
+
import "./arrange-BqP2DI3w.mjs";
|
|
13
|
+
import { t as findRepoRoot } from "./repo-root-HmtIEm6J.mjs";
|
|
14
|
+
import process from "node:process";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { Option } from "commander";
|
|
17
|
+
//#region src/commands/arrange.ts
|
|
18
|
+
/** Commander attribute `withClassName` (second long flag `--with-class-name`). */
|
|
19
|
+
function createWithClassNameOption() {
|
|
20
|
+
return new Option("--with-classname, --with-class-name", "Append className as final cn() argument").default(false);
|
|
21
|
+
}
|
|
22
|
+
function defaultTargetPath() {
|
|
23
|
+
return path.resolve(process.cwd(), DEFAULT_ARRANGE_TARGET);
|
|
24
|
+
}
|
|
25
|
+
function checkTargetExists(resolved, fs, logger) {
|
|
26
|
+
if (!fs.existsSync(resolved)) {
|
|
27
|
+
logger.err(`Not found: ${resolved}`);
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
async function loadArrangeCommandConfig(fs, logger, rootDir) {
|
|
34
|
+
try {
|
|
35
|
+
const { config, warnings } = await loadConfig(fs, rootDir);
|
|
36
|
+
printConfigSchemaWarnings(logger, warnings);
|
|
37
|
+
return { arrangeConfig: config.arrange ?? {} };
|
|
38
|
+
} catch (caughtConfigError) {
|
|
39
|
+
logger.err(messageFromCaughtUnknown(caughtConfigError));
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function registerArrangeCommand(program) {
|
|
45
|
+
const arrange = program.command("arrange").description("Analyze and regroup Tailwind classes in cn() / tv() calls (Tailwind v4)");
|
|
46
|
+
arrange.command("analyze").description("Report long strings, nested cn in tv(), and related findings").argument("[target]", "Directory or file (default: packages/ui/src/components)").action(async (target) => {
|
|
47
|
+
const fs = createNodeCliFs();
|
|
48
|
+
const logger = createNodeCliLogger();
|
|
49
|
+
const resolved = target ? path.resolve(target) : defaultTargetPath();
|
|
50
|
+
if (!checkTargetExists(resolved, fs, logger)) return;
|
|
51
|
+
if (!await loadArrangeCommandConfig(fs, logger, findRepoRoot(fs))) return;
|
|
52
|
+
printAnalyzeReport(resolved, analyzeDirectory(resolved, fs), logger);
|
|
53
|
+
});
|
|
54
|
+
arrange.command("preview").description("Dry-run: print suggested replacements without writing files").argument("[target]", "Directory or file (default: packages/ui/src/components)").addOption(createWithClassNameOption()).option("--cn-import <spec>", "Override module specifier when adding cn import").action(async (target, opts) => {
|
|
55
|
+
const fs = createNodeCliFs();
|
|
56
|
+
const logger = createNodeCliLogger();
|
|
57
|
+
const resolved = target ? path.resolve(target) : defaultTargetPath();
|
|
58
|
+
if (!checkTargetExists(resolved, fs, logger)) return;
|
|
59
|
+
const rootDir = findRepoRoot(fs);
|
|
60
|
+
const loaded = await loadArrangeCommandConfig(fs, logger, rootDir);
|
|
61
|
+
if (!loaded) return;
|
|
62
|
+
process.exitCode = await runArrangeSync({
|
|
63
|
+
rootDir,
|
|
64
|
+
config: loaded.arrangeConfig,
|
|
65
|
+
targetPath: resolved,
|
|
66
|
+
write: false,
|
|
67
|
+
withClassName: opts.withClassName,
|
|
68
|
+
cnImport: opts.cnImport,
|
|
69
|
+
fs,
|
|
70
|
+
logger
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
arrange.command("apply").description("Apply grouping and cn-in-tv unwrap edits to files").argument("[target]", "Directory or file (default: packages/ui/src/components)").addOption(createWithClassNameOption()).option("--cn-import <spec>", "Override module specifier when adding cn import").action(async (target, opts) => {
|
|
74
|
+
const fs = createNodeCliFs();
|
|
75
|
+
const logger = createNodeCliLogger();
|
|
76
|
+
const resolved = target ? path.resolve(target) : defaultTargetPath();
|
|
77
|
+
if (!checkTargetExists(resolved, fs, logger)) return;
|
|
78
|
+
const rootDir = findRepoRoot(fs);
|
|
79
|
+
const loaded = await loadArrangeCommandConfig(fs, logger, rootDir);
|
|
80
|
+
if (!loaded) return;
|
|
81
|
+
process.exitCode = await runArrangeSync({
|
|
82
|
+
rootDir,
|
|
83
|
+
config: loaded.arrangeConfig,
|
|
84
|
+
targetPath: resolved,
|
|
85
|
+
write: true,
|
|
86
|
+
withClassName: opts.withClassName,
|
|
87
|
+
cnImport: opts.cnImport,
|
|
88
|
+
fs,
|
|
89
|
+
logger
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
arrange.command("group").description("Try grouping on a pasted class string (stdout: cn(...) or tv array with --tv)").argument("[tokens...]", "Class tokens (quote a single string if it contains spaces)").option("--tv", "Emit tv()-style array instead of cn() call", false).addOption(createWithClassNameOption()).action((tokens, opts) => {
|
|
93
|
+
const inlineClasses = tokens.join(" ").trim();
|
|
94
|
+
if (!inlineClasses) {
|
|
95
|
+
process.stderr.write("Pass a class string. Example: codefast arrange group \"flex gap-2 text-sm rounded-md\"\n");
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const groups = suggestCnGroups(inlineClasses);
|
|
100
|
+
const result = opts.tv ? formatArray(groups) : formatCnCall(groups, { trailingClassName: !!opts.withClassName });
|
|
101
|
+
process.stdout.write(`${result}\n`);
|
|
102
|
+
const bucketSummary = summarizeGroupBucketLabels(groups);
|
|
103
|
+
process.stdout.write(`\n// Buckets: ${JSON.stringify(bucketSummary)}\n`);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { registerArrangeCommand as t };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./collectors-cn-BS8TTQ8O.mjs";
|
|
2
|
+
import "./ast-helpers-MDRYuIAk.mjs";
|
|
3
|
+
import "./analyze-DvtJ3j9m.mjs";
|
|
4
|
+
import "./formatters-DGE8jJ53.mjs";
|
|
5
|
+
import "./imports-DSVq1xhB.mjs";
|
|
6
|
+
import "./group-file-ClW7XdD6.mjs";
|
|
7
|
+
import "./run-target-CJWrqNT4.mjs";
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { a as EMPTY_CN_TV_BINDINGS } from "./constants-AtPhF-Ct.mjs";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
//#region src/lib/arrange/domain/ast/ast-helpers.ts
|
|
4
|
+
/** Known module specifiers that export `cn` / `tv`. */
|
|
5
|
+
const KNOWN_CN_TV_MODULES = new Set([
|
|
6
|
+
"@codefast/tailwind-variants",
|
|
7
|
+
"clsx",
|
|
8
|
+
"class-variance-authority",
|
|
9
|
+
"tailwind-variants",
|
|
10
|
+
"#lib/utils",
|
|
11
|
+
"~/lib/utils",
|
|
12
|
+
"@/lib/utils"
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* True for typical shadcn-style re-exports: `./utils`, `@/lib/utils`, `…/utils/…`,
|
|
16
|
+
* or a dedicated `cn.ts` / `cn.tsx` module.
|
|
17
|
+
*/
|
|
18
|
+
function moduleLooksLikeCnTvReexport(moduleSpecifier) {
|
|
19
|
+
const norm = moduleSpecifier.replace(/\\/g, "/");
|
|
20
|
+
if (/(?:^|[./])utils(?:\/|$)/.test(norm)) return true;
|
|
21
|
+
if (/\/utils\//.test(norm) || /\/utils$/.test(norm)) return true;
|
|
22
|
+
if (/(?:^|\/)cn\.tsx?$/.test(norm)) return true;
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build a set of local binding names that are imported from a known cn/tv
|
|
27
|
+
* module in `sf`.
|
|
28
|
+
*/
|
|
29
|
+
function buildKnownCnTvBindings(sf) {
|
|
30
|
+
const bindings = /* @__PURE__ */ new Set();
|
|
31
|
+
for (const stmt of sf.statements) {
|
|
32
|
+
if (!ts.isImportDeclaration(stmt) || !stmt.importClause) continue;
|
|
33
|
+
if (stmt.importClause.isTypeOnly) continue;
|
|
34
|
+
const spec = stmt.moduleSpecifier;
|
|
35
|
+
if (!ts.isStringLiteral(spec)) continue;
|
|
36
|
+
const moduleSpecifier = spec.text;
|
|
37
|
+
if (!KNOWN_CN_TV_MODULES.has(moduleSpecifier) && !moduleLooksLikeCnTvReexport(moduleSpecifier)) continue;
|
|
38
|
+
const clause = stmt.importClause;
|
|
39
|
+
if (clause.name) bindings.add(clause.name.text);
|
|
40
|
+
if (!clause.namedBindings) continue;
|
|
41
|
+
if (ts.isNamedImports(clause.namedBindings)) {
|
|
42
|
+
for (const namedImport of clause.namedBindings.elements) bindings.add(namedImport.name.text);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
bindings.add(clause.namedBindings.name.text);
|
|
46
|
+
}
|
|
47
|
+
return bindings;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns true when `expr` is an identifier whose name matches `name` AND
|
|
51
|
+
* that name is listed in `knownBindings` from a recognized import.
|
|
52
|
+
*/
|
|
53
|
+
function isCnOrTvIdentifier(expr, name, knownBindings = EMPTY_CN_TV_BINDINGS) {
|
|
54
|
+
if (knownBindings.size === 0) return false;
|
|
55
|
+
if (ts.isIdentifier(expr) && expr.text === name) return knownBindings.has(expr.text);
|
|
56
|
+
if (ts.isPropertyAccessExpression(expr) && expr.name.text === name) return ts.isIdentifier(expr.expression) && knownBindings.has(expr.expression.text);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
function propertyAssignmentNameText(prop) {
|
|
60
|
+
if (ts.isIdentifier(prop.name)) return prop.name.text;
|
|
61
|
+
if (ts.isStringLiteral(prop.name)) return prop.name.text;
|
|
62
|
+
}
|
|
63
|
+
function lineOf(sf, tsNode) {
|
|
64
|
+
return sf.getLineAndCharacterOfPosition(tsNode.getStart(sf)).line + 1;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Returns only the leading whitespace (tabs / spaces) of the line containing
|
|
68
|
+
* `pos`.
|
|
69
|
+
*/
|
|
70
|
+
function indentOfLineContaining(source, pos) {
|
|
71
|
+
const searchPos = Math.max(0, Math.min(pos, source.length));
|
|
72
|
+
const prevLineBreak = Math.max(source.lastIndexOf("\n", searchPos - 1), source.lastIndexOf("\r", searchPos - 1));
|
|
73
|
+
const lineStart = prevLineBreak === -1 ? 0 : prevLineBreak + 1;
|
|
74
|
+
let lineEnd = source.length;
|
|
75
|
+
for (let i = lineStart; i < source.length; i++) {
|
|
76
|
+
const ch = source[i];
|
|
77
|
+
if (ch === "\n" || ch === "\r") {
|
|
78
|
+
lineEnd = i;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const line = source.slice(lineStart, lineEnd);
|
|
83
|
+
return /^[\t ]*/.exec(line)?.[0] ?? "";
|
|
84
|
+
}
|
|
85
|
+
function applyEditsDescending(sourceText, edits) {
|
|
86
|
+
const sorted = [...edits].sort((editA, editB) => editB.start - editA.start);
|
|
87
|
+
let out = sourceText;
|
|
88
|
+
for (const edit of sorted) out = out.slice(0, edit.start) + edit.replacement + out.slice(edit.end);
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Replace `cn(...)` nested in `tv({...})` with a plain string (one arg) or a string
|
|
93
|
+
* array (multiple args). Returns undefined when there are zero arguments.
|
|
94
|
+
*/
|
|
95
|
+
function unwrapCnInsideTvCallReplacement(call, sourceText, sf) {
|
|
96
|
+
const args = call.arguments;
|
|
97
|
+
if (args.length === 0) return void 0;
|
|
98
|
+
const baseIndent = indentOfLineContaining(sourceText, call.getStart(sf));
|
|
99
|
+
const innerIndent = `${baseIndent} `;
|
|
100
|
+
if (args.length === 1) {
|
|
101
|
+
const firstArg = args[0];
|
|
102
|
+
return sourceText.slice(firstArg.getStart(sf), firstArg.getEnd());
|
|
103
|
+
}
|
|
104
|
+
const lines = ["["];
|
|
105
|
+
for (let i = 0; i < args.length; i++) {
|
|
106
|
+
const arg = args[i];
|
|
107
|
+
const piece = sourceText.slice(arg.getStart(sf), arg.getEnd());
|
|
108
|
+
const comma = i < args.length - 1 || args.length > 1 ? "," : "";
|
|
109
|
+
lines.push(`${innerIndent}${piece}${comma}`);
|
|
110
|
+
}
|
|
111
|
+
lines.push(`${baseIndent}]`);
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
export { isCnOrTvIdentifier as a, propertyAssignmentNameText as c, indentOfLineContaining as i, unwrapCnInsideTvCallReplacement as l, applyEditsDescending as n, lineOf as o, buildKnownCnTvBindings as r, moduleLooksLikeCnTvReexport as s, KNOWN_CN_TV_MODULES as t };
|
package/dist/{bin.js → bin.mjs}
RENAMED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { n as runCli } from "./program-DR-H4tH3.mjs";
|
|
2
3
|
import process from "node:process";
|
|
3
|
-
|
|
4
|
+
//#region src/bin.ts
|
|
4
5
|
const code = await runCli(process.argv);
|
|
5
6
|
process.exit(code);
|
|
7
|
+
//#endregion
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/lib/infra/caught-unknown-message.ts
|
|
2
|
+
/**
|
|
3
|
+
* Stable, user-facing text for values caught as `unknown`.
|
|
4
|
+
* Avoids `String(unknown)` / `[object Object]` and satisfies strict `no-base-to-string`.
|
|
5
|
+
*/
|
|
6
|
+
function messageFromCaughtUnknown(value) {
|
|
7
|
+
if (value instanceof Error) return value.message;
|
|
8
|
+
if (typeof value === "string") return value;
|
|
9
|
+
if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") return String(value);
|
|
10
|
+
if (typeof value === "symbol") return String(value);
|
|
11
|
+
if (value === null) return "null";
|
|
12
|
+
if (value === void 0) return "undefined";
|
|
13
|
+
try {
|
|
14
|
+
const serialized = JSON.stringify(value);
|
|
15
|
+
if (typeof serialized === "string") return serialized;
|
|
16
|
+
} catch {}
|
|
17
|
+
return "[unserializable non-Error value]";
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { messageFromCaughtUnknown as t };
|