@codefast/cli 0.3.7-canary.0 → 0.3.12
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 +60 -299
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +5 -0
- package/dist/commands/arrange.d.ts +3 -0
- package/dist/commands/arrange.d.ts.map +1 -0
- package/dist/commands/arrange.js +114 -0
- package/dist/commands/mirror.d.ts +4 -0
- package/dist/commands/mirror.d.ts.map +1 -0
- package/dist/commands/mirror.js +63 -0
- package/dist/lib/arrange/analyze.d.ts +4 -0
- package/dist/lib/arrange/analyze.d.ts.map +1 -0
- package/dist/lib/arrange/analyze.js +105 -0
- package/dist/lib/arrange/ast/collectors-cn.d.ts +10 -0
- package/dist/lib/arrange/ast/collectors-cn.d.ts.map +1 -0
- package/dist/lib/arrange/ast/collectors-cn.js +84 -0
- package/dist/lib/arrange/ast/collectors-jsx.d.ts +4 -0
- package/dist/lib/arrange/ast/collectors-jsx.d.ts.map +1 -0
- package/dist/lib/arrange/ast/collectors-jsx.js +18 -0
- package/dist/lib/arrange/ast/collectors-tv.d.ts +13 -0
- package/dist/lib/arrange/ast/collectors-tv.d.ts.map +1 -0
- package/dist/lib/arrange/ast/collectors-tv.js +273 -0
- package/dist/lib/arrange/ast/targets.d.ts +8 -0
- package/dist/lib/arrange/ast/targets.d.ts.map +1 -0
- package/dist/lib/arrange/ast/targets.js +143 -0
- package/dist/lib/arrange/ast/utils.d.ts +36 -0
- package/dist/lib/arrange/ast/utils.d.ts.map +1 -0
- package/dist/lib/arrange/ast/utils.js +138 -0
- package/dist/lib/arrange/constants.d.ts +68 -0
- package/dist/lib/arrange/constants.d.ts.map +1 -0
- package/dist/lib/arrange/constants.js +179 -0
- package/dist/lib/arrange/errors.d.ts +14 -0
- package/dist/lib/arrange/errors.d.ts.map +1 -0
- package/dist/lib/arrange/errors.js +16 -0
- package/dist/lib/arrange/formatters.d.ts +16 -0
- package/dist/lib/arrange/formatters.d.ts.map +1 -0
- package/dist/lib/arrange/formatters.js +57 -0
- package/dist/lib/arrange/group-file.d.ts +4 -0
- package/dist/lib/arrange/group-file.d.ts.map +1 -0
- package/dist/lib/arrange/group-file.js +115 -0
- package/dist/lib/arrange/grouping.d.ts +41 -0
- package/dist/lib/arrange/grouping.d.ts.map +1 -0
- package/dist/lib/arrange/grouping.js +280 -0
- package/dist/lib/arrange/imports.d.ts +6 -0
- package/dist/lib/arrange/imports.d.ts.map +1 -0
- package/dist/lib/arrange/imports.js +74 -0
- package/dist/lib/arrange/report.d.ts +4 -0
- package/dist/lib/arrange/report.d.ts.map +1 -0
- package/dist/lib/arrange/report.js +37 -0
- package/dist/lib/arrange/run-target.d.ts +4 -0
- package/dist/lib/arrange/run-target.d.ts.map +1 -0
- package/dist/lib/arrange/run-target.js +28 -0
- package/dist/lib/arrange/tokenizer.d.ts +37 -0
- package/dist/lib/arrange/tokenizer.d.ts.map +1 -0
- package/dist/lib/arrange/tokenizer.js +390 -0
- package/dist/lib/arrange/types.d.ts +104 -0
- package/dist/lib/arrange/types.d.ts.map +1 -0
- package/dist/lib/arrange/types.js +7 -0
- package/dist/lib/arrange/walk.d.ts +4 -0
- package/dist/lib/arrange/walk.d.ts.map +1 -0
- package/dist/lib/arrange/walk.js +34 -0
- package/dist/lib/arrange.d.ts +41 -0
- package/dist/lib/arrange.d.ts.map +1 -0
- package/dist/lib/arrange.js +38 -0
- package/dist/lib/infra/fs-contract.d.ts +28 -0
- package/dist/lib/infra/fs-contract.d.ts.map +1 -0
- package/dist/lib/infra/fs-contract.js +1 -0
- package/dist/lib/infra/node-io.d.ts +4 -0
- package/dist/lib/infra/node-io.d.ts.map +1 -0
- package/dist/lib/infra/node-io.js +27 -0
- package/dist/lib/mirror/config.d.ts +7 -0
- package/dist/lib/mirror/config.d.ts.map +1 -0
- package/dist/lib/mirror/config.js +146 -0
- package/dist/lib/mirror/constants.d.ts +10 -0
- package/dist/lib/mirror/constants.d.ts.map +1 -0
- package/dist/lib/mirror/constants.js +13 -0
- package/dist/lib/mirror/engine.d.ts +12 -0
- package/dist/lib/mirror/engine.d.ts.map +1 -0
- package/dist/lib/mirror/engine.js +248 -0
- package/dist/lib/mirror/errors.d.ts +10 -0
- package/dist/lib/mirror/errors.d.ts.map +1 -0
- package/dist/lib/mirror/errors.js +12 -0
- package/dist/lib/mirror/package-filter.d.ts +8 -0
- package/dist/lib/mirror/package-filter.d.ts.map +1 -0
- package/dist/lib/mirror/package-filter.js +33 -0
- package/dist/lib/mirror/reporter.d.ts +24 -0
- package/dist/lib/mirror/reporter.d.ts.map +1 -0
- package/dist/lib/mirror/reporter.js +126 -0
- package/dist/lib/mirror/sync.d.ts +4 -0
- package/dist/lib/mirror/sync.d.ts.map +1 -0
- package/dist/lib/mirror/sync.js +135 -0
- package/dist/lib/mirror/types.d.ts +81 -0
- package/dist/lib/mirror/types.d.ts.map +1 -0
- package/dist/lib/mirror/types.js +1 -0
- package/dist/lib/mirror/update-pkg.d.ts +13 -0
- package/dist/lib/mirror/update-pkg.d.ts.map +1 -0
- package/dist/lib/mirror/update-pkg.js +39 -0
- package/dist/lib/mirror/workspace-packages.d.ts +26 -0
- package/dist/lib/mirror/workspace-packages.d.ts.map +1 -0
- package/dist/lib/mirror/workspace-packages.js +160 -0
- package/dist/lib/mirror.d.ts +7 -0
- package/dist/lib/mirror.d.ts.map +1 -0
- package/dist/lib/mirror.js +5 -0
- package/dist/lib/repo-root.d.ts +7 -0
- package/dist/lib/repo-root.d.ts.map +1 -0
- package/dist/lib/repo-root.js +23 -0
- package/dist/lib/shared/utils.d.ts +9 -0
- package/dist/lib/shared/utils.d.ts.map +1 -0
- package/dist/lib/shared/utils.js +12 -0
- package/dist/program.d.ts +4 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +38 -0
- package/package.json +42 -53
- package/CHANGELOG.md +0 -15
- package/dist/cjs/index.cjs +0 -2
- package/dist/cjs/src/index.d.ts +0 -3
- package/dist/cjs/src/index.d.ts.map +0 -1
- package/dist/esm/index.js +0 -2
- package/dist/esm/src/index.d.ts +0 -3
- package/dist/esm/src/index.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,339 +1,100 @@
|
|
|
1
1
|
# @codefast/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Two tools bundled in one CLI:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
| Command | Purpose |
|
|
6
|
+
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| `mirror sync` | Regenerate `package.json` `exports` from each package's built `dist/` tree (pnpm workspace packages) |
|
|
8
|
+
| `arrange` | Analyze, dry-run, or apply suggested grouping for long Tailwind class strings inside `cn()` / `tv()` call sites (Tailwind v4–oriented heuristics), or `group` a pasted class string |
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
---
|
|
8
11
|
|
|
9
|
-
##
|
|
12
|
+
## Requirements & Install
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
- **React Component Type Checking**: Validate React component type correspondence across packages
|
|
13
|
-
- **Explicit Architecture**: Clean, maintainable codebase following DDD and SOLID principles
|
|
14
|
-
- **Dependency Injection**: Fully testable with InversifyJS container
|
|
15
|
-
- **Rich CLI Experience**: Colorful output with chalk and comprehensive help system
|
|
16
|
-
|
|
17
|
-
## Installation
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
# Install globally
|
|
21
|
-
npm install -g @codefast/cli
|
|
22
|
-
|
|
23
|
-
# Or use with npx
|
|
24
|
-
npx @codefast/cli --help
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Usage
|
|
28
|
-
|
|
29
|
-
### Available Commands
|
|
30
|
-
|
|
31
|
-
#### Hello Command
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
codefast hello [options]
|
|
35
|
-
codefast hello --name "Developer"
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Simple greeting command for testing CLI functionality.
|
|
39
|
-
|
|
40
|
-
**Options:**
|
|
41
|
-
|
|
42
|
-
- `-n, --name <name>`: Name to greet (default: "World")
|
|
43
|
-
|
|
44
|
-
#### Analyze Command
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
codefast analyze [options]
|
|
48
|
-
codefast analyze --pattern "src/**/*.ts" --config "./tsconfig.json"
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Analyze TypeScript project and generate statistics about classes, functions, and interfaces.
|
|
52
|
-
|
|
53
|
-
**Options:**
|
|
54
|
-
|
|
55
|
-
- `-p, --pattern <pattern>`: File pattern to analyze (default: "src/**/*.ts")
|
|
56
|
-
- `-c, --config <path>`: Path to tsconfig.json file
|
|
57
|
-
|
|
58
|
-
**Example Output:**
|
|
59
|
-
|
|
60
|
-
```text
|
|
61
|
-
🔍 Analyzing TypeScript project...
|
|
62
|
-
✅ Found 45 TypeScript files
|
|
63
|
-
⚠️ Loaded 45 source files for analysis
|
|
64
|
-
📊 Project Statistics:
|
|
65
|
-
Classes: 12
|
|
66
|
-
Functions: 89
|
|
67
|
-
Interfaces: 23
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
#### Check Component Types Command
|
|
14
|
+
**Node.js ≥ 24** is required.
|
|
71
15
|
|
|
72
16
|
```bash
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Check React component type correspondence across packages in a monorepo.
|
|
17
|
+
# Global
|
|
18
|
+
pnpm add -g @codefast/cli
|
|
78
19
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- `-d, --packages-dir <dir>`: Packages directory to analyze (default: "packages")
|
|
82
|
-
|
|
83
|
-
## Architecture
|
|
84
|
-
|
|
85
|
-
This CLI follows **Explicit Architecture** principles, ensuring clear separation of concerns, testability, and maintainability.
|
|
86
|
-
|
|
87
|
-
### Directory Structure
|
|
88
|
-
|
|
89
|
-
```text
|
|
90
|
-
src/
|
|
91
|
-
├── core/ # Core business logic
|
|
92
|
-
│ └── application/ # Application layer
|
|
93
|
-
│ ├── ports/ # Interface definitions
|
|
94
|
-
│ │ ├── analysis/ # Analysis service interfaces
|
|
95
|
-
│ │ ├── services/ # Service interfaces
|
|
96
|
-
│ │ └── system/ # System service interfaces
|
|
97
|
-
│ └── use-cases/ # Business workflows
|
|
98
|
-
├── infrastructure/ # Technical implementations
|
|
99
|
-
│ └── adapters/ # Port implementations
|
|
100
|
-
│ ├── analysis/ # Analysis service adapters
|
|
101
|
-
│ ├── services/ # Service adapters
|
|
102
|
-
│ └── system/ # System service adapters
|
|
103
|
-
├── commands/ # CLI command handling
|
|
104
|
-
├── di/ # Dependency injection
|
|
105
|
-
│ └── modules/ # DI module configurations
|
|
106
|
-
└── index.ts # CLI entry point
|
|
20
|
+
# Without a global install
|
|
21
|
+
pnpm dlx @codefast/cli -- --help
|
|
107
22
|
```
|
|
108
23
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
#### 1. Core/Application Layer
|
|
112
|
-
|
|
113
|
-
Contains business logic and defines interfaces (ports) for external dependencies.
|
|
114
|
-
|
|
115
|
-
**Use Cases:**
|
|
116
|
-
|
|
117
|
-
- `AnalyzeProjectUseCase`: Orchestrates TypeScript project analysis
|
|
118
|
-
- `CheckComponentTypesUseCase`: Handles React component type validation
|
|
119
|
-
- `GreetUserUseCase`: Simple greeting functionality
|
|
24
|
+
---
|
|
120
25
|
|
|
121
|
-
|
|
26
|
+
## `arrange`
|
|
122
27
|
|
|
123
|
-
|
|
124
|
-
- **Service Ports**: `LoggingServicePort`
|
|
125
|
-
- **System Ports**: `FileSystemSystemPort`, `PathSystemPort`, `UrlSystemPort`
|
|
28
|
+
### Recommended workflow
|
|
126
29
|
|
|
127
|
-
|
|
30
|
+
1. **`analyze [target]`** — Prints a report (long strings, nested `cn` in `tv`, related notes). No files changed.
|
|
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.
|
|
128
33
|
|
|
129
|
-
|
|
34
|
+
**Default target** (when path is omitted): `packages/ui/src/components` resolved from `process.cwd()`.
|
|
130
35
|
|
|
131
|
-
|
|
36
|
+
### Useful options
|
|
132
37
|
|
|
133
|
-
- `
|
|
134
|
-
-
|
|
135
|
-
- `ChalkLoggingServiceAdapter`: Colored console logging with chalk
|
|
136
|
-
- `FastGlobFileSystemSystemAdapter`: File system operations with fast-glob
|
|
137
|
-
- `NodePathSystemAdapter`: Path operations using Node.js path module
|
|
138
|
-
- `NodeUrlSystemAdapter`: URL operations using Node.js url module
|
|
38
|
+
- **`--with-class-name`** — Append `className` as the last argument to the suggested `cn(...)`.
|
|
39
|
+
- **`--cn-import <spec>`** — Override the module specifier when the tool adds a `cn` import.
|
|
139
40
|
|
|
140
|
-
|
|
41
|
+
### `group [tokens...]`
|
|
141
42
|
|
|
142
|
-
|
|
43
|
+
No filesystem involved — paste a class string, get back a suggested `cn(...)` (or a `tv()`-style array with `--tv`) plus a short buckets summary. Use this to tune your mental model before running `analyze` on a large tree.
|
|
143
44
|
|
|
144
|
-
|
|
45
|
+
---
|
|
145
46
|
|
|
146
|
-
|
|
47
|
+
## Grouping philosophy — Render Pipeline Order
|
|
147
48
|
|
|
148
|
-
|
|
49
|
+
`arrange` does **not** sort alphabetically. It groups utilities in roughly the same order the browser reasons about them:
|
|
149
50
|
|
|
150
|
-
**
|
|
51
|
+
**Existence → Position → Layout → Sizing → Spacing → Shape → Background → Shadow → Typography → Composite → Motion → Starting → Behavior → Conditions (State)**
|
|
151
52
|
|
|
152
|
-
|
|
153
|
-
- `applicationModule`: Binds use cases and application services
|
|
154
|
-
- `commandsModule`: Binds command handlers
|
|
53
|
+
Bucket breakdown:
|
|
155
54
|
|
|
156
|
-
|
|
55
|
+
| Bucket | What it covers | Examples |
|
|
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-[…]:` |
|
|
157
71
|
|
|
158
|
-
|
|
159
|
-
2. **Interface Segregation**: Small, focused interfaces for each concern
|
|
160
|
-
3. **Single Responsibility**: Each class has one reason to change
|
|
161
|
-
4. **Testability**: All dependencies are injected and can be mocked
|
|
162
|
-
5. **Explicit Dependencies**: No hidden dependencies or global state
|
|
72
|
+
Some adjacent buckets may be merged into one string literal when declared _compatible_ (e.g. `layout` + `sizing`) — keeps `cn()` readable without flattening unrelated concerns.
|
|
163
73
|
|
|
164
|
-
|
|
74
|
+
To change a placement, edit `classifyBareUtility` in `src/lib/arrange/tokenizer.ts` and add a `classifyToken` test in `src/lib/arrange.test.ts`.
|
|
165
75
|
|
|
166
|
-
|
|
76
|
+
---
|
|
167
77
|
|
|
168
|
-
|
|
169
|
-
- pnpm 10.13.1+
|
|
78
|
+
## `mirror sync`
|
|
170
79
|
|
|
171
|
-
|
|
80
|
+
Run from anywhere under the monorepo — the CLI finds the root via `pnpm-workspace.yaml`.
|
|
172
81
|
|
|
173
82
|
```bash
|
|
174
|
-
#
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
# Build the package
|
|
178
|
-
pnpm build
|
|
179
|
-
|
|
180
|
-
# Run in development mode
|
|
181
|
-
pnpm dev
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Available Scripts
|
|
185
|
-
|
|
186
|
-
```bash
|
|
187
|
-
# Development
|
|
188
|
-
pnpm dev # Build and watch for changes
|
|
189
|
-
pnpm build # Build for production
|
|
190
|
-
pnpm clean # Clean build artifacts
|
|
191
|
-
|
|
192
|
-
# Code Quality
|
|
193
|
-
pnpm lint # Run ESLint
|
|
194
|
-
pnpm lint:fix # Fix ESLint issues
|
|
195
|
-
pnpm type-check # TypeScript type checking
|
|
196
|
-
pnpm format # Format code with Prettier
|
|
197
|
-
pnpm format:check # Check code formatting
|
|
198
|
-
|
|
199
|
-
# Testing
|
|
200
|
-
pnpm test # Run tests
|
|
201
|
-
pnpm test:watch # Run tests in watch mode
|
|
202
|
-
pnpm test:coverage # Run tests with coverage
|
|
203
|
-
pnpm test:coverage:ci # Run tests with CI coverage
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### Testing Strategy
|
|
207
|
-
|
|
208
|
-
The CLI follows a comprehensive testing approach aligned with the explicit architecture:
|
|
209
|
-
|
|
210
|
-
#### Unit Testing
|
|
211
|
-
|
|
212
|
-
- **Domain/Application Layer**: Test use cases with mocked ports
|
|
213
|
-
- **Infrastructure Layer**: Test adapters with real or mocked external dependencies
|
|
214
|
-
- **Commands Layer**: Test command handlers with mocked use cases
|
|
215
|
-
|
|
216
|
-
#### Test Structure
|
|
217
|
-
|
|
218
|
-
```text
|
|
219
|
-
src/
|
|
220
|
-
├── core/application/use-cases/
|
|
221
|
-
│ ├── analyze-project.use-case.ts
|
|
222
|
-
│ └── analyze-project.use-case.test.ts
|
|
223
|
-
├── infrastructure/adapters/
|
|
224
|
-
│ ├── ts-morph.typescript.analysis.adapter.ts
|
|
225
|
-
│ └── ts-morph.typescript.analysis.adapter.test.ts
|
|
226
|
-
└── commands/
|
|
227
|
-
├── command-handler.ts
|
|
228
|
-
└── command-handler.test.ts
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
#### Testing Best Practices
|
|
232
|
-
|
|
233
|
-
- Mock at the port boundaries (interfaces)
|
|
234
|
-
- Use dependency injection for test isolation
|
|
235
|
-
- Test business logic independently of technical details
|
|
236
|
-
- Maintain high coverage for critical paths
|
|
237
|
-
|
|
238
|
-
### Adding New Features
|
|
239
|
-
|
|
240
|
-
#### 1. Define the Port (Interface)
|
|
241
|
-
|
|
242
|
-
```typescript
|
|
243
|
-
// src/core/application/ports/new-feature.port.ts
|
|
244
|
-
export interface NewFeaturePort {
|
|
245
|
-
performAction(input: string): Promise<string>;
|
|
246
|
-
}
|
|
83
|
+
codefast mirror sync # All workspace packages
|
|
84
|
+
codefast mirror sync packages/ui # One package only
|
|
85
|
+
codefast mirror sync -v # Verbose
|
|
247
86
|
```
|
|
248
87
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
```typescript
|
|
252
|
-
// src/core/application/use-cases/new-feature.use-case.ts
|
|
253
|
-
@injectable()
|
|
254
|
-
export class NewFeatureUseCase {
|
|
255
|
-
constructor(
|
|
256
|
-
@inject(TYPES.NewFeaturePort)
|
|
257
|
-
private readonly newFeatureService: NewFeaturePort,
|
|
258
|
-
) {}
|
|
259
|
-
|
|
260
|
-
async execute(input: string): Promise<void> {
|
|
261
|
-
const result = await this.newFeatureService.performAction(input);
|
|
262
|
-
// Handle result...
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
```
|
|
88
|
+
**Config:** Place `codefast.config.js` (or `.mjs` / `.cjs` / `.json`) at repo root with a `mirror` object (`skipPackages`, `pathTransformations`, `customExports`, …).
|
|
266
89
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
```typescript
|
|
270
|
-
// src/infrastructure/adapters/new-feature.adapter.ts
|
|
271
|
-
@injectable()
|
|
272
|
-
export class NewFeatureAdapter implements NewFeaturePort {
|
|
273
|
-
async performAction(input: string): Promise<string> {
|
|
274
|
-
// Implementation using external library
|
|
275
|
-
return `Processed: ${ input }`;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
```
|
|
90
|
+
> ⚠️ `.js`/`.mjs`/`.cjs` config files are loaded via `import()` — only run `mirror sync` in repositories you trust.
|
|
279
91
|
|
|
280
|
-
|
|
92
|
+
---
|
|
281
93
|
|
|
282
|
-
|
|
283
|
-
// src/di/types.ts
|
|
284
|
-
export const TYPES = {
|
|
285
|
-
// ... existing types
|
|
286
|
-
NewFeaturePort: Symbol.for('NewFeaturePort'),
|
|
287
|
-
NewFeatureUseCase: Symbol.for('NewFeatureUseCase'),
|
|
288
|
-
};
|
|
94
|
+
## Developing inside this monorepo
|
|
289
95
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
// src/di/modules/application.module.ts
|
|
294
|
-
applicationModule.bind<NewFeatureUseCase>(TYPES.NewFeatureUseCase).to(NewFeatureUseCase);
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
#### 5. Add CLI Command
|
|
298
|
-
|
|
299
|
-
```typescript
|
|
300
|
-
// src/commands/command-handler.ts
|
|
301
|
-
this.program.command('new-feature').description('Description of new feature').action(async (options) => {
|
|
302
|
-
await this.newFeatureUseCase.execute(options.input);
|
|
303
|
-
});
|
|
96
|
+
```bash
|
|
97
|
+
pnpm exec codefast --help
|
|
304
98
|
```
|
|
305
99
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
### Core Dependencies
|
|
309
|
-
|
|
310
|
-
- **chalk**: Terminal styling and colors
|
|
311
|
-
- **commander**: CLI framework and argument parsing
|
|
312
|
-
- **fast-glob**: Fast file globbing for file system operations
|
|
313
|
-
- **inversify**: Dependency injection container
|
|
314
|
-
- **reflect-metadata**: Metadata reflection for decorators
|
|
315
|
-
- **ts-morph**: TypeScript compiler API wrapper
|
|
316
|
-
- **zod**: Schema validation and type safety
|
|
317
|
-
|
|
318
|
-
### Development Dependencies
|
|
319
|
-
|
|
320
|
-
- **@rslib/core**: Modern build tool for libraries
|
|
321
|
-
- **TypeScript**: Type checking and compilation
|
|
322
|
-
- **Jest**: Testing framework
|
|
323
|
-
- **ESLint**: Code linting
|
|
324
|
-
- **Prettier**: Code formatting
|
|
325
|
-
|
|
326
|
-
## Contributing
|
|
327
|
-
|
|
328
|
-
1. Follow the explicit architecture guidelines
|
|
329
|
-
2. Write tests for new features
|
|
330
|
-
3. Ensure all quality checks pass (`pnpm lint`, `pnpm type-check`, `pnpm test`)
|
|
331
|
-
4. Update documentation for new commands or features
|
|
332
|
-
|
|
333
|
-
## License
|
|
334
|
-
|
|
335
|
-
MIT License - see LICENSE file for details.
|
|
336
|
-
|
|
337
|
-
## Related
|
|
338
|
-
|
|
339
|
-
- [CodeFast UI Components](../ui/README.md)
|
|
100
|
+
Root `package.json` defines optional `cli:*` scripts (e.g. `cli:mirror-sync`, `cli:arrange-analyze`) as thin wrappers around `pnpm exec codefast …`.
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arrange.d.ts","sourceRoot":"","sources":["../../src/commands/arrange.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4EzC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsF7D"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { Option } from "commander";
|
|
4
|
+
import { ArrangeError, ArrangeErrorCode, analyzeDirectory, createNodeCliFs, createNodeCliLogger, DEFAULT_ARRANGE_TARGET, formatArray, formatCnCall, printAnalyzeReport, runOnTarget, suggestCnGroups, summarizeGroupBucketLabels, } from "#lib/arrange";
|
|
5
|
+
/** Commander attribute `withClassName` (second long flag `--with-class-name`). */
|
|
6
|
+
function createWithClassNameOption() {
|
|
7
|
+
return new Option("--with-classname, --with-class-name", "Append className as final cn() argument").default(false);
|
|
8
|
+
}
|
|
9
|
+
function defaultTargetPath() {
|
|
10
|
+
return path.resolve(process.cwd(), DEFAULT_ARRANGE_TARGET);
|
|
11
|
+
}
|
|
12
|
+
function checkTargetExists(resolved, fs, logger) {
|
|
13
|
+
if (!fs.existsSync(resolved)) {
|
|
14
|
+
logger.err(`Not found: ${resolved}`);
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
function handleArrangeLibError(e, logger) {
|
|
21
|
+
if (e instanceof ArrangeError && e.code === ArrangeErrorCode.TARGET_NOT_FOUND) {
|
|
22
|
+
logger.err(e.message);
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
function runArrangeAction(resolvedTarget, runOpts, fs, logger) {
|
|
29
|
+
try {
|
|
30
|
+
runOnTarget(resolvedTarget, {
|
|
31
|
+
write: runOpts.write,
|
|
32
|
+
withClassName: !!runOpts.withClassName,
|
|
33
|
+
cnImport: runOpts.cnImport,
|
|
34
|
+
}, fs, logger);
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
if (!handleArrangeLibError(e, logger))
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function registerArrangeCommand(program) {
|
|
42
|
+
const arrange = program
|
|
43
|
+
.command("arrange")
|
|
44
|
+
.description("Analyze and regroup Tailwind classes in cn() / tv() calls (Tailwind v4)");
|
|
45
|
+
arrange
|
|
46
|
+
.command("analyze")
|
|
47
|
+
.description("Report long strings, nested cn in tv(), and related findings")
|
|
48
|
+
.argument("[target]", "Directory or file (default: packages/ui/src/components)")
|
|
49
|
+
.action((target) => {
|
|
50
|
+
const fs = createNodeCliFs();
|
|
51
|
+
const logger = createNodeCliLogger();
|
|
52
|
+
const resolved = target ? path.resolve(target) : defaultTargetPath();
|
|
53
|
+
if (!checkTargetExists(resolved, fs, logger))
|
|
54
|
+
return;
|
|
55
|
+
printAnalyzeReport(resolved, analyzeDirectory(resolved, fs), logger);
|
|
56
|
+
});
|
|
57
|
+
arrange
|
|
58
|
+
.command("preview")
|
|
59
|
+
.description("Dry-run: print suggested replacements without writing files")
|
|
60
|
+
.argument("[target]", "Directory or file (default: packages/ui/src/components)")
|
|
61
|
+
.addOption(createWithClassNameOption())
|
|
62
|
+
.option("--cn-import <spec>", "Override module specifier when adding cn import")
|
|
63
|
+
.action((target, opts) => {
|
|
64
|
+
const fs = createNodeCliFs();
|
|
65
|
+
const logger = createNodeCliLogger();
|
|
66
|
+
const resolved = target ? path.resolve(target) : defaultTargetPath();
|
|
67
|
+
if (!checkTargetExists(resolved, fs, logger))
|
|
68
|
+
return;
|
|
69
|
+
runArrangeAction(resolved, {
|
|
70
|
+
write: false,
|
|
71
|
+
withClassName: opts.withClassName,
|
|
72
|
+
cnImport: opts.cnImport,
|
|
73
|
+
}, fs, logger);
|
|
74
|
+
});
|
|
75
|
+
arrange
|
|
76
|
+
.command("apply")
|
|
77
|
+
.description("Apply grouping and cn-in-tv unwrap edits to files")
|
|
78
|
+
.argument("[target]", "Directory or file (default: packages/ui/src/components)")
|
|
79
|
+
.addOption(createWithClassNameOption())
|
|
80
|
+
.option("--cn-import <spec>", "Override module specifier when adding cn import")
|
|
81
|
+
.action((target, opts) => {
|
|
82
|
+
const fs = createNodeCliFs();
|
|
83
|
+
const logger = createNodeCliLogger();
|
|
84
|
+
const resolved = target ? path.resolve(target) : defaultTargetPath();
|
|
85
|
+
if (!checkTargetExists(resolved, fs, logger))
|
|
86
|
+
return;
|
|
87
|
+
runArrangeAction(resolved, {
|
|
88
|
+
write: true,
|
|
89
|
+
withClassName: opts.withClassName,
|
|
90
|
+
cnImport: opts.cnImport,
|
|
91
|
+
}, fs, logger);
|
|
92
|
+
});
|
|
93
|
+
arrange
|
|
94
|
+
.command("group")
|
|
95
|
+
.description("Try grouping on a pasted class string (stdout: cn(...) or tv array with --tv)")
|
|
96
|
+
.argument("[tokens...]", "Class tokens (quote a single string if it contains spaces)")
|
|
97
|
+
.option("--tv", "Emit tv()-style array instead of cn() call", false)
|
|
98
|
+
.addOption(createWithClassNameOption())
|
|
99
|
+
.action((tokens, opts) => {
|
|
100
|
+
const inlineClasses = tokens.join(" ").trim();
|
|
101
|
+
if (!inlineClasses) {
|
|
102
|
+
process.stderr.write('Pass a class string. Example: codefast arrange group "flex gap-2 text-sm rounded-md"\n');
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const groups = suggestCnGroups(inlineClasses);
|
|
107
|
+
const result = opts.tv
|
|
108
|
+
? formatArray(groups)
|
|
109
|
+
: formatCnCall(groups, { trailingClassName: !!opts.withClassName });
|
|
110
|
+
process.stdout.write(`${result}\n`);
|
|
111
|
+
const bucketSummary = summarizeGroupBucketLabels(groups);
|
|
112
|
+
process.stdout.write(`\n// Buckets: ${JSON.stringify(bucketSummary)}\n`);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mirror.d.ts","sourceRoot":"","sources":["../../src/commands/mirror.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAiBjG;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmC5D"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { createNodeCliFs } from "#lib/infra/node-io";
|
|
5
|
+
import { normalizePath, runMirrorSync } from "#lib/mirror";
|
|
6
|
+
import { findRepoRoot } from "#lib/repo-root";
|
|
7
|
+
function tryRealpath(entryPath) {
|
|
8
|
+
try {
|
|
9
|
+
return realpathSync.native(entryPath);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return path.resolve(entryPath);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function packageArgToRelative(rootDir, arg) {
|
|
16
|
+
if (!arg)
|
|
17
|
+
return undefined;
|
|
18
|
+
const rootReal = tryRealpath(path.resolve(rootDir));
|
|
19
|
+
const cwdReal = tryRealpath(process.cwd());
|
|
20
|
+
const resolved = path.isAbsolute(arg) ? path.resolve(arg) : path.resolve(cwdReal, arg);
|
|
21
|
+
const targetReal = tryRealpath(resolved);
|
|
22
|
+
const rel = path.relative(rootReal, targetReal);
|
|
23
|
+
const normalized = normalizePath(rel);
|
|
24
|
+
if (normalized.startsWith("..") ||
|
|
25
|
+
path.isAbsolute(normalized) ||
|
|
26
|
+
normalized === "" ||
|
|
27
|
+
normalized === ".") {
|
|
28
|
+
throw new Error(`Package path must be a subdirectory under monorepo root: ${rootDir}`);
|
|
29
|
+
}
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
export function registerMirrorCommand(program) {
|
|
33
|
+
const mirror = program
|
|
34
|
+
.command("mirror")
|
|
35
|
+
.description("Keep package manifests aligned with what you ship");
|
|
36
|
+
mirror
|
|
37
|
+
.command("sync")
|
|
38
|
+
.description("Write package.json exports from dist/ for workspace packages")
|
|
39
|
+
.argument("[package]", "Optional package path relative to repo root (e.g. packages/ui)")
|
|
40
|
+
.option("-v, --verbose", "Print extra diagnostics", false)
|
|
41
|
+
.action(async function (pkg, options) {
|
|
42
|
+
const globals = this.optsWithGlobals();
|
|
43
|
+
const fs = createNodeCliFs();
|
|
44
|
+
const rootDir = findRepoRoot(fs);
|
|
45
|
+
let packageFilter;
|
|
46
|
+
try {
|
|
47
|
+
packageFilter = packageArgToRelative(rootDir, pkg);
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
this.error(e instanceof Error ? e.message : String(e));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const exitCode = await runMirrorSync({
|
|
54
|
+
rootDir,
|
|
55
|
+
verbose: options.verbose,
|
|
56
|
+
/** Commander sets `color: false` when `--no-color` is passed (default `color: true`). */
|
|
57
|
+
noColor: globals.color === false,
|
|
58
|
+
packageFilter,
|
|
59
|
+
fs,
|
|
60
|
+
});
|
|
61
|
+
process.exitCode = exitCode;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../../src/lib/arrange/analyze.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAEpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AA8FxD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,GAAG,aAAa,CAsCzE"}
|