@chrisluyi/daas-cli 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +405 -0
- package/dist/index.js +52 -109
- package/dist/test.js +3 -0
- package/package.json +7 -2
- package/skill.md +21 -2
- package/dist/daas-bin +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# daas-cli
|
|
2
|
+
|
|
3
|
+
Zero-config build/dev/test CLI for React micro-frontend (MFE) apps using [rsbuild](https://rsbuild.dev) + [Module Federation 2](https://module-federation.io).
|
|
4
|
+
|
|
5
|
+
MFE apps have **no build config files** — no `rsbuild.config.ts`, no `vitest.config.ts`. The CLI owns all of it.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Packages
|
|
10
|
+
|
|
11
|
+
| Package | Version | Description |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| [`@chrisluyi/daas-cli`](https://www.npmjs.com/package/@chrisluyi/daas-cli) | 1.1.0 | CLI binary — install this as your only devDependency |
|
|
14
|
+
| [`@chrisluyi/rsbuild-config`](https://www.npmjs.com/package/@chrisluyi/rsbuild-config) | 1.1.0 | rsbuild config factory + vitest config — arrives transitively |
|
|
15
|
+
| [`@chrisluyi/template`](https://www.npmjs.com/package/@chrisluyi/template) | 1.0.0 | MFE scaffold templates (services / containers / components structure) |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quickstart (3 commands)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
mkdir my-mfe && cd my-mfe
|
|
23
|
+
echo '{"name":"my-mfe"}' > package.json
|
|
24
|
+
npx @chrisluyi/daas-cli init
|
|
25
|
+
bun install
|
|
26
|
+
daas dev
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Your MFE is now running at `http://localhost:3000` with Module Federation 2, React, hot-reload, and Vitest — zero config required.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# In your MFE project
|
|
37
|
+
bun add -D @chrisluyi/daas-cli
|
|
38
|
+
|
|
39
|
+
# or npm
|
|
40
|
+
npm install -D @chrisluyi/daas-cli
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`daas-cli` is the **only** devDependency you need. It ships rsbuild, vitest, @testing-library/react, happy-dom, and all tooling as its own dependencies. Do not install these separately.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
daas init [--template default]
|
|
51
|
+
daas dev [target] [--port <n>] [--json]
|
|
52
|
+
daas build [target] [--json]
|
|
53
|
+
daas test [--json]
|
|
54
|
+
daas info [--json]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### `daas init`
|
|
58
|
+
|
|
59
|
+
Interactive scaffold powered by `@clack/prompts`. Prompts for remote config URL, port, and app name, then writes starter files.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
┌ daas-cli — scaffold new MFE
|
|
63
|
+
│
|
|
64
|
+
◇ Remote config URL?
|
|
65
|
+
│ https://your-org.example.com/daas-config.json
|
|
66
|
+
│
|
|
67
|
+
◇ Dev server port?
|
|
68
|
+
│ 3001
|
|
69
|
+
│
|
|
70
|
+
◇ App name? (leave blank to use package.json name)
|
|
71
|
+
│ mfe-auth
|
|
72
|
+
│
|
|
73
|
+
◇ Does this app expose additional components beyond ./App?
|
|
74
|
+
│ No
|
|
75
|
+
│
|
|
76
|
+
◆ Scaffolding mfe-auth...
|
|
77
|
+
│ ✓ Created src/App.tsx
|
|
78
|
+
│ ✓ Created src/App.test.tsx
|
|
79
|
+
│ ✓ Created tsconfig.json
|
|
80
|
+
│ ✓ Updated package.json
|
|
81
|
+
│
|
|
82
|
+
└ Done! Run: bun install && daas dev
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**What gets created (minimal scaffold):**
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
src/
|
|
89
|
+
├── App.tsx # minimal React component
|
|
90
|
+
└── App.test.tsx # smoke test
|
|
91
|
+
tsconfig.json # jsx: react-jsx, strict mode
|
|
92
|
+
package.json # scripts + daas config injected
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**`--template default`** scaffolds the full opinionated structure:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
src/
|
|
99
|
+
├── App.tsx # entry — renders AppContainer
|
|
100
|
+
├── containers/
|
|
101
|
+
│ └── AppContainer.tsx # data-fetching component
|
|
102
|
+
├── components/
|
|
103
|
+
│ └── App.tsx # presentational component
|
|
104
|
+
├── services/
|
|
105
|
+
│ └── index.ts # service layer stub
|
|
106
|
+
└── config/
|
|
107
|
+
├── regions/ # per-region config (for v1.5 targets)
|
|
108
|
+
├── platforms/ # per-platform config
|
|
109
|
+
└── products/ # per-product config
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `daas dev`
|
|
113
|
+
|
|
114
|
+
Starts the rsbuild dev server with HMR and Module Federation 2 in remote mode.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
daas dev # uses port from package.json#daas
|
|
118
|
+
daas dev --port 3002 # override port at runtime
|
|
119
|
+
daas dev sg-foo-mb-dev # v1.5: activate region/product/platform target
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `daas build`
|
|
123
|
+
|
|
124
|
+
Production build to `./dist`. Always fetches fresh remote config — fatal if network unavailable.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
daas build
|
|
128
|
+
daas build sg-foo-mb-prod # v1.5: build for a specific target
|
|
129
|
+
daas build --json # machine-readable output (for CI / AI agents)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `daas test`
|
|
133
|
+
|
|
134
|
+
Runs Vitest with `happy-dom`, `@testing-library/react`, and `@testing-library/jest-dom` pre-configured. The following browser APIs are mocked automatically so MFE tests work without any extra setup:
|
|
135
|
+
|
|
136
|
+
- `window.matchMedia`
|
|
137
|
+
- `ResizeObserver`
|
|
138
|
+
- `IntersectionObserver`
|
|
139
|
+
- `window.scrollTo` / `scrollBy`
|
|
140
|
+
- `URL.createObjectURL` / `revokeObjectURL`
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
daas test
|
|
144
|
+
daas test --json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Writing tests** — import everything from `@chrisluyi/daas-cli/test` (no direct vitest or `@testing-library` imports needed):
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
import { render, screen, describe, test, expect, userEvent } from "@chrisluyi/daas-cli/test"
|
|
151
|
+
import MyComponent from "./MyComponent"
|
|
152
|
+
|
|
153
|
+
describe("MyComponent", () => {
|
|
154
|
+
test("renders a button", async () => {
|
|
155
|
+
render(<MyComponent />)
|
|
156
|
+
await userEvent.click(screen.getByRole("button"))
|
|
157
|
+
expect(screen.getByText("Clicked!")).toBeInTheDocument()
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Adding extra setup** — append your own setup files after the built-in mocks via `package.json#daas.setupFiles`:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"daas": {
|
|
167
|
+
"configUrl": "...",
|
|
168
|
+
"port": 3001,
|
|
169
|
+
"setupFiles": ["./src/test-setup.ts"]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Your setup file can override any of the default mocks or add new ones.
|
|
175
|
+
|
|
176
|
+
### `daas info`
|
|
177
|
+
|
|
178
|
+
Prints the fully resolved project state — useful for debugging and AI agent diagnosis.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
daas info --json
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Example output:
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"ok": true,
|
|
189
|
+
"command": "info",
|
|
190
|
+
"cliVersion": "1.1.0",
|
|
191
|
+
"appName": "mfe-auth",
|
|
192
|
+
"port": 3001,
|
|
193
|
+
"configUrl": "https://your-org.example.com/daas-config.json",
|
|
194
|
+
"manifest": {
|
|
195
|
+
"name": "mfe-auth",
|
|
196
|
+
"exposes": { "./App": "./src/App" }
|
|
197
|
+
},
|
|
198
|
+
"remoteJson": {
|
|
199
|
+
"version": "1.0.0",
|
|
200
|
+
"minCliVersion": "1.0.0",
|
|
201
|
+
"shared": { "react": "18.3.1", "react-dom": "18.3.1" },
|
|
202
|
+
"rsbuildConfigVersion": 1
|
|
203
|
+
},
|
|
204
|
+
"isMonorepo": false
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Per-App Config (`package.json#daas`)
|
|
211
|
+
|
|
212
|
+
All MFE-specific configuration lives in `package.json`:
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"name": "mfe-auth",
|
|
217
|
+
"scripts": {
|
|
218
|
+
"dev": "daas dev",
|
|
219
|
+
"build": "daas build",
|
|
220
|
+
"test": "daas test"
|
|
221
|
+
},
|
|
222
|
+
"devDependencies": {
|
|
223
|
+
"@chrisluyi/daas-cli": "^1.1.0"
|
|
224
|
+
},
|
|
225
|
+
"daas": {
|
|
226
|
+
"configUrl": "https://your-org.example.com/daas-config.json",
|
|
227
|
+
"port": 3001,
|
|
228
|
+
"coverage": {
|
|
229
|
+
"lines": 80,
|
|
230
|
+
"branches": 80
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
| Field | Required | Description |
|
|
237
|
+
|---|---|---|
|
|
238
|
+
| `configUrl` | Yes | URL of the remote JSON config owned by your platform team |
|
|
239
|
+
| `port` | Yes | Dev server port — must be unique per MFE in a monorepo |
|
|
240
|
+
| `coverage` | No | Vitest coverage thresholds |
|
|
241
|
+
| `setupFiles` | No | Extra Vitest setup files appended after the built-in browser mocks |
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Optional Manifest (`mf.manifest.json`)
|
|
246
|
+
|
|
247
|
+
Only needed when exposing more than just `./App`:
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"name": "mfe-auth",
|
|
252
|
+
"exposes": {
|
|
253
|
+
"./App": "./src/App",
|
|
254
|
+
"./LoginButton": "./src/components/LoginButton"
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
If absent, `name` derives from `package.json#name` (scope stripped), and `exposes` defaults to `{ "./App": "./src/App" }`.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Remote JSON (served by your platform team)
|
|
264
|
+
|
|
265
|
+
The URL in `configUrl` must serve a JSON file like this:
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"version": "1.0.0",
|
|
270
|
+
"minCliVersion": "1.0.0",
|
|
271
|
+
"releaseNotesUrl": "https://your-org.example.com/daas-cli/releases",
|
|
272
|
+
"shared": {
|
|
273
|
+
"react": "18.3.1",
|
|
274
|
+
"react-dom": "18.3.1"
|
|
275
|
+
},
|
|
276
|
+
"rsbuildConfigVersion": 1
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
This controls which version of shared dependencies all your MFEs use via Module Federation 2's singleton negotiation. The CLI fetches this at dev startup (5-min cache) and always fresh at build time.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Monorepo Support
|
|
285
|
+
|
|
286
|
+
`daas init` detects monorepos by walking parent directories for `bun.lockb` or `package.json#workspaces`. In a monorepo:
|
|
287
|
+
|
|
288
|
+
- `daas-cli` is **not** added to the app's `devDependencies` (it's resolved from the workspace root)
|
|
289
|
+
- Each MFE has its own `port` in `package.json#daas`
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
my-monorepo/
|
|
293
|
+
├── package.json # workspaces: ["packages/*"]
|
|
294
|
+
├── bun.lockb
|
|
295
|
+
└── packages/
|
|
296
|
+
├── mfe-auth/ # daas dev → port 3001
|
|
297
|
+
│ └── package.json # { "daas": { "port": 3001, ... } }
|
|
298
|
+
└── mfe-dashboard/ # daas dev → port 3002
|
|
299
|
+
└── package.json # { "daas": { "port": 3002, ... } }
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## v1.5 — Multi-Region Target System
|
|
305
|
+
|
|
306
|
+
Target strings let you select region/product/platform/environment at dev and build time:
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
daas dev sg-foo-mb-dev
|
|
310
|
+
│ │ │ └─ environment (dev / staging / prod)
|
|
311
|
+
│ │ └──── platform (mb = mobile, wb = web, etc.)
|
|
312
|
+
│ └──────── product (may contain hyphens)
|
|
313
|
+
└──────────── region (sg, us, eu, …)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
When a target is provided:
|
|
317
|
+
1. The CLI resolves a `TargetContext` from the string
|
|
318
|
+
2. `tsconfig.daas.json` is generated (gitignored) with path aliases:
|
|
319
|
+
- `@region-config` → `./src/config/regions/<region>`
|
|
320
|
+
- `@platform-config` → `./src/config/platforms/<platform>`
|
|
321
|
+
- `@product-config` → `./src/config/products/<product>`
|
|
322
|
+
3. Environment variables are injected at build time: `DAAS_REGION`, `DAAS_PRODUCT`, `DAAS_PLATFORM`, `DAAS_ENV`
|
|
323
|
+
|
|
324
|
+
Use `daas init --template default` to scaffold the config layer folder structure.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Exit Codes
|
|
329
|
+
|
|
330
|
+
| Code | Meaning | What to do |
|
|
331
|
+
|---|---|---|
|
|
332
|
+
| `0` | Success | — |
|
|
333
|
+
| `1` | General error (build/test failure) | Check output above the JSON |
|
|
334
|
+
| `2` | Config error (missing `configUrl`, bad manifest) | Run `daas init` or fix `package.json#daas` |
|
|
335
|
+
| `3` | CLI too old (`minCliVersion` not met) | Upgrade `@chrisluyi/daas-cli` |
|
|
336
|
+
| `4` | Remote JSON fetch failed | Check network / VPN (only fatal in `build`) |
|
|
337
|
+
| `5` | Config version mismatch | Upgrade `@chrisluyi/daas-cli` |
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## `--json` Output Contract
|
|
342
|
+
|
|
343
|
+
All commands except `init` support `--json` — always use this in CI and AI agent workflows.
|
|
344
|
+
|
|
345
|
+
**Success:**
|
|
346
|
+
```json
|
|
347
|
+
{
|
|
348
|
+
"ok": true,
|
|
349
|
+
"command": "build",
|
|
350
|
+
"appName": "mfe-auth",
|
|
351
|
+
"output": "./dist",
|
|
352
|
+
"cliVersion": "1.1.0"
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Error:**
|
|
357
|
+
```json
|
|
358
|
+
{
|
|
359
|
+
"ok": false,
|
|
360
|
+
"command": "build",
|
|
361
|
+
"exitCode": 4,
|
|
362
|
+
"error": "Could not fetch remote config: HTTP 503",
|
|
363
|
+
"suggestion": "Check network connectivity or VPN.",
|
|
364
|
+
"cliVersion": "1.1.0"
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## AI Agent Support (Claude Code)
|
|
371
|
+
|
|
372
|
+
Register the bundled skill so Claude Code understands `daas-cli` without hallucinating commands:
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
# Per-project
|
|
376
|
+
mkdir -p .claude/skills
|
|
377
|
+
cp node_modules/@chrisluyi/daas-cli/skill.md .claude/skills/daas-cli.md
|
|
378
|
+
|
|
379
|
+
# Or globally
|
|
380
|
+
cp node_modules/@chrisluyi/daas-cli/skill.md ~/.claude/skills/daas-cli.md
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**AI agent workflow:**
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
# 1. Always start here to understand current state
|
|
387
|
+
daas info --json
|
|
388
|
+
|
|
389
|
+
# 2. Run commands and parse stdout JSON
|
|
390
|
+
daas build --json
|
|
391
|
+
# { "ok": true, ... } or { "ok": false, "exitCode": N, "error": "...", "suggestion": "..." }
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Development (this repo)
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
bun install
|
|
400
|
+
bun test # run all tests
|
|
401
|
+
bun run build # build both packages to dist/
|
|
402
|
+
bun run lint # biome check
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
See [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) for the full release workflow.
|
package/dist/index.js
CHANGED
|
@@ -2313,73 +2313,7 @@ async function runMain(cmd, opts = {}) {
|
|
|
2313
2313
|
|
|
2314
2314
|
// src/commands/dev.ts
|
|
2315
2315
|
import { createRsbuild } from "@rsbuild/core";
|
|
2316
|
-
|
|
2317
|
-
// ../rsbuild-config/dist/index.js
|
|
2318
|
-
import { mergeRsbuildConfig } from "@rsbuild/core";
|
|
2319
|
-
|
|
2320
|
-
// ../rsbuild-config/dist/defaults.js
|
|
2321
|
-
var SUPPORTED_CONFIG_VERSION = 1;
|
|
2322
|
-
function getDefaults(mode) {
|
|
2323
|
-
return {
|
|
2324
|
-
output: {
|
|
2325
|
-
distPath: { root: "dist" },
|
|
2326
|
-
cleanDistPath: mode === "production"
|
|
2327
|
-
},
|
|
2328
|
-
performance: mode === "production" ? { chunkSplit: { strategy: "split-by-experience" } } : undefined
|
|
2329
|
-
};
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
|
-
// ../rsbuild-config/dist/plugins/react.js
|
|
2333
|
-
import { pluginReact } from "@rsbuild/plugin-react";
|
|
2334
|
-
function getReactPlugin() {
|
|
2335
|
-
return pluginReact();
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
// ../rsbuild-config/dist/plugins/mf2.js
|
|
2339
|
-
import { pluginModuleFederation } from "@module-federation/rsbuild-plugin";
|
|
2340
|
-
function getMF2Plugin(manifest, remoteMeta) {
|
|
2341
|
-
return pluginModuleFederation({
|
|
2342
|
-
name: manifest.name,
|
|
2343
|
-
filename: "remoteEntry.js",
|
|
2344
|
-
exposes: manifest.exposes,
|
|
2345
|
-
shared: buildShared(remoteMeta.shared)
|
|
2346
|
-
});
|
|
2347
|
-
}
|
|
2348
|
-
function buildShared(shared) {
|
|
2349
|
-
return Object.fromEntries(Object.entries(shared).map(([pkg, version]) => [
|
|
2350
|
-
pkg,
|
|
2351
|
-
{
|
|
2352
|
-
singleton: true,
|
|
2353
|
-
requiredVersion: version,
|
|
2354
|
-
eager: pkg === "react" || pkg === "react-dom"
|
|
2355
|
-
}
|
|
2356
|
-
]));
|
|
2357
|
-
}
|
|
2358
|
-
|
|
2359
|
-
// ../rsbuild-config/dist/index.js
|
|
2360
|
-
function createConfig(manifest, remoteMeta, options) {
|
|
2361
|
-
const { mode, port, target } = options;
|
|
2362
|
-
return mergeRsbuildConfig(getDefaults(mode), {
|
|
2363
|
-
server: port ? { port } : undefined,
|
|
2364
|
-
plugins: [getReactPlugin(), getMF2Plugin(manifest, remoteMeta)],
|
|
2365
|
-
source: target ? { alias: buildTargetAliases(target), define: buildTargetDefines(target) } : undefined
|
|
2366
|
-
});
|
|
2367
|
-
}
|
|
2368
|
-
function buildTargetAliases(target) {
|
|
2369
|
-
return {
|
|
2370
|
-
"@region-config": `./src/config/regions/${target.region}`,
|
|
2371
|
-
"@platform-config": `./src/config/platforms/${target.platform}`,
|
|
2372
|
-
"@product-config": `./src/config/products/${target.product}`
|
|
2373
|
-
};
|
|
2374
|
-
}
|
|
2375
|
-
function buildTargetDefines(target) {
|
|
2376
|
-
return {
|
|
2377
|
-
"process.env.DAAS_REGION": JSON.stringify(target.region),
|
|
2378
|
-
"process.env.DAAS_ENVIRONMENT": JSON.stringify(target.environment),
|
|
2379
|
-
"process.env.DAAS_PLATFORM": JSON.stringify(target.platform),
|
|
2380
|
-
"process.env.DAAS_PRODUCT": JSON.stringify(target.product)
|
|
2381
|
-
};
|
|
2382
|
-
}
|
|
2316
|
+
import { createConfig } from "@chrisluyi/rsbuild-config";
|
|
2383
2317
|
|
|
2384
2318
|
// src/daas-config.ts
|
|
2385
2319
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -2404,7 +2338,8 @@ function readDaasConfig(cwd = process.cwd()) {
|
|
|
2404
2338
|
return {
|
|
2405
2339
|
configUrl: daas.configUrl,
|
|
2406
2340
|
port: daas.port ?? 3000,
|
|
2407
|
-
coverage: daas.coverage
|
|
2341
|
+
coverage: daas.coverage,
|
|
2342
|
+
setupFiles: daas.setupFiles
|
|
2408
2343
|
};
|
|
2409
2344
|
}
|
|
2410
2345
|
|
|
@@ -2451,6 +2386,9 @@ function normalizeManifest(raw, cwd) {
|
|
|
2451
2386
|
function stripScope(name) {
|
|
2452
2387
|
return name.replace(/^@[^/]+\//, "");
|
|
2453
2388
|
}
|
|
2389
|
+
|
|
2390
|
+
// src/remote-config.ts
|
|
2391
|
+
import { SUPPORTED_CONFIG_VERSION } from "@chrisluyi/rsbuild-config";
|
|
2454
2392
|
// src/fallback-config.json
|
|
2455
2393
|
var fallback_config_default = {
|
|
2456
2394
|
version: "1.0.0",
|
|
@@ -2666,6 +2604,7 @@ function handleError(command, e2, json) {
|
|
|
2666
2604
|
|
|
2667
2605
|
// src/commands/build.ts
|
|
2668
2606
|
import { createRsbuild as createRsbuild2 } from "@rsbuild/core";
|
|
2607
|
+
import { createConfig as createConfig2 } from "@chrisluyi/rsbuild-config";
|
|
2669
2608
|
var buildCommand = defineCommand({
|
|
2670
2609
|
meta: { name: "build", description: "Build MFE for production" },
|
|
2671
2610
|
args: {
|
|
@@ -2685,7 +2624,7 @@ var buildCommand = defineCommand({
|
|
|
2685
2624
|
const target = args.target ? parseTarget(args.target) : undefined;
|
|
2686
2625
|
if (target)
|
|
2687
2626
|
generateTsconfigDaas(process.cwd(), target);
|
|
2688
|
-
const config =
|
|
2627
|
+
const config = createConfig2(manifest, remoteMeta, { mode: "production", target });
|
|
2689
2628
|
const rsbuild = await createRsbuild2({ rsbuildConfig: config });
|
|
2690
2629
|
await rsbuild.build();
|
|
2691
2630
|
if (json)
|
|
@@ -2730,27 +2669,7 @@ var buildCommand = defineCommand({
|
|
|
2730
2669
|
|
|
2731
2670
|
// src/commands/test.ts
|
|
2732
2671
|
import { startVitest } from "vitest/node";
|
|
2733
|
-
|
|
2734
|
-
// ../rsbuild-config/dist/vitest.js
|
|
2735
|
-
import { resolve as resolve4, dirname } from "path";
|
|
2736
|
-
import { fileURLToPath } from "url";
|
|
2737
|
-
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
2738
|
-
function createVitestConfig(manifest, options = {}) {
|
|
2739
|
-
return {
|
|
2740
|
-
test: {
|
|
2741
|
-
name: manifest.name,
|
|
2742
|
-
environment: "happy-dom",
|
|
2743
|
-
setupFiles: [resolve4(__dirname2, "test-setup.js")],
|
|
2744
|
-
coverage: {
|
|
2745
|
-
provider: "v8",
|
|
2746
|
-
reporter: ["text", "lcov"],
|
|
2747
|
-
thresholds: options.coverage ?? undefined
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
};
|
|
2751
|
-
}
|
|
2752
|
-
|
|
2753
|
-
// src/commands/test.ts
|
|
2672
|
+
import { createVitestConfig } from "@chrisluyi/rsbuild-config/vitest";
|
|
2754
2673
|
var testCommand = defineCommand({
|
|
2755
2674
|
meta: { name: "test", description: "Run MFE tests" },
|
|
2756
2675
|
args: {
|
|
@@ -2761,7 +2680,7 @@ var testCommand = defineCommand({
|
|
|
2761
2680
|
try {
|
|
2762
2681
|
const manifest = readManifest();
|
|
2763
2682
|
const daasConfig = readDaasConfig();
|
|
2764
|
-
const config = createVitestConfig(manifest, { coverage: daasConfig.coverage });
|
|
2683
|
+
const config = createVitestConfig(manifest, { coverage: daasConfig.coverage, setupFiles: daasConfig.setupFiles });
|
|
2765
2684
|
const vitest = await startVitest("test", [], config);
|
|
2766
2685
|
const failed = vitest?.state.getFiles().some((f3) => f3.result?.state === "fail") ?? false;
|
|
2767
2686
|
if (json) {
|
|
@@ -2782,17 +2701,17 @@ var testCommand = defineCommand({
|
|
|
2782
2701
|
|
|
2783
2702
|
// src/init/monorepo.ts
|
|
2784
2703
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
2785
|
-
import { resolve as
|
|
2704
|
+
import { resolve as resolve4, dirname } from "path";
|
|
2786
2705
|
function detectMonorepo(startDir) {
|
|
2787
2706
|
let dir = startDir;
|
|
2788
2707
|
for (let i2 = 0;i2 < 10; i2++) {
|
|
2789
|
-
const parent =
|
|
2708
|
+
const parent = dirname(dir);
|
|
2790
2709
|
if (parent === dir)
|
|
2791
2710
|
break;
|
|
2792
2711
|
dir = parent;
|
|
2793
|
-
if (existsSync3(
|
|
2712
|
+
if (existsSync3(resolve4(dir, "bun.lockb")))
|
|
2794
2713
|
return true;
|
|
2795
|
-
const pkgPath =
|
|
2714
|
+
const pkgPath = resolve4(dir, "package.json");
|
|
2796
2715
|
if (existsSync3(pkgPath)) {
|
|
2797
2716
|
try {
|
|
2798
2717
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
@@ -2846,6 +2765,9 @@ var infoCommand = defineCommand({
|
|
|
2846
2765
|
}
|
|
2847
2766
|
});
|
|
2848
2767
|
|
|
2768
|
+
// src/commands/init.ts
|
|
2769
|
+
import { scaffold } from "@chrisluyi/template";
|
|
2770
|
+
|
|
2849
2771
|
// ../../node_modules/.bun/@clack+core@0.3.5/node_modules/@clack/core/dist/index.mjs
|
|
2850
2772
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
2851
2773
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
@@ -3337,15 +3259,15 @@ var de = () => {
|
|
|
3337
3259
|
};
|
|
3338
3260
|
|
|
3339
3261
|
// src/commands/init.ts
|
|
3340
|
-
import {
|
|
3341
|
-
import { resolve as
|
|
3262
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
3263
|
+
import { resolve as resolve6 } from "path";
|
|
3342
3264
|
|
|
3343
3265
|
// src/init/copy-template.ts
|
|
3344
3266
|
import { existsSync as existsSync4, copyFileSync, mkdirSync, readdirSync } from "fs";
|
|
3345
|
-
import { dirname as
|
|
3346
|
-
import { fileURLToPath
|
|
3347
|
-
var
|
|
3348
|
-
var TEMPLATES_DIR = join(
|
|
3267
|
+
import { dirname as dirname2, join } from "path";
|
|
3268
|
+
import { fileURLToPath } from "url";
|
|
3269
|
+
var __dirname2 = dirname2(fileURLToPath(import.meta.url));
|
|
3270
|
+
var TEMPLATES_DIR = join(__dirname2, "../../templates/mfe");
|
|
3349
3271
|
async function copyTemplate(destDir) {
|
|
3350
3272
|
const files = readdirSync(TEMPLATES_DIR);
|
|
3351
3273
|
const result = { copied: [], skipped: [] };
|
|
@@ -3372,9 +3294,9 @@ async function copyTemplate(destDir) {
|
|
|
3372
3294
|
|
|
3373
3295
|
// src/init/package-json.ts
|
|
3374
3296
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
|
|
3375
|
-
import { resolve as
|
|
3297
|
+
import { resolve as resolve5 } from "path";
|
|
3376
3298
|
function updatePackageJson(cwd, opts) {
|
|
3377
|
-
const pkgPath =
|
|
3299
|
+
const pkgPath = resolve5(cwd, "package.json");
|
|
3378
3300
|
const pkg = existsSync5(pkgPath) ? JSON.parse(readFileSync4(pkgPath, "utf8")) : {};
|
|
3379
3301
|
pkg.scripts = {
|
|
3380
3302
|
...pkg.scripts ?? {},
|
|
@@ -3394,19 +3316,22 @@ function updatePackageJson(cwd, opts) {
|
|
|
3394
3316
|
}
|
|
3395
3317
|
|
|
3396
3318
|
// src/commands/init.ts
|
|
3319
|
+
var KNOWN_TEMPLATES = ["default"];
|
|
3397
3320
|
var initCommand = defineCommand({
|
|
3398
3321
|
meta: { name: "init", description: "Scaffold a new MFE app" },
|
|
3399
3322
|
args: {
|
|
3400
|
-
template: { type: "string", description: "Template name
|
|
3323
|
+
template: { type: "string", description: "Template name. Use 'default' for the full services/containers/components structure." }
|
|
3401
3324
|
},
|
|
3402
3325
|
async run({ args }) {
|
|
3403
3326
|
if (!process.stdin.isTTY) {
|
|
3404
3327
|
consola.fatal("daas init requires an interactive terminal. Cannot run in non-TTY mode.");
|
|
3405
3328
|
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
3406
3329
|
}
|
|
3407
|
-
|
|
3408
|
-
|
|
3330
|
+
const templateName = args.template;
|
|
3331
|
+
if (templateName && !KNOWN_TEMPLATES.includes(templateName)) {
|
|
3332
|
+
consola.warn(`Unknown template "${templateName}". Available: ${KNOWN_TEMPLATES.join(", ")}. Falling back to minimal scaffold.`);
|
|
3409
3333
|
}
|
|
3334
|
+
const useFullTemplate = templateName === "default";
|
|
3410
3335
|
oe("daas-cli \u2014 scaffold new MFE");
|
|
3411
3336
|
const configUrl = await te({
|
|
3412
3337
|
message: "Remote config URL?",
|
|
@@ -3432,7 +3357,25 @@ var initCommand = defineCommand({
|
|
|
3432
3357
|
s2.start(`Scaffolding ${appName}...`);
|
|
3433
3358
|
const cwd = process.cwd();
|
|
3434
3359
|
const isMonorepo = detectMonorepo(cwd);
|
|
3435
|
-
|
|
3360
|
+
let copied;
|
|
3361
|
+
let skipped;
|
|
3362
|
+
if (useFullTemplate) {
|
|
3363
|
+
const result = await scaffold(cwd, {
|
|
3364
|
+
appName,
|
|
3365
|
+
onConflict: async (relPath) => {
|
|
3366
|
+
s2.stop("");
|
|
3367
|
+
const overwrite = await se({ message: `${relPath} already exists. Overwrite?` });
|
|
3368
|
+
s2.start(`Scaffolding ${appName}...`);
|
|
3369
|
+
return overwrite;
|
|
3370
|
+
}
|
|
3371
|
+
});
|
|
3372
|
+
copied = result.copied;
|
|
3373
|
+
skipped = result.skipped;
|
|
3374
|
+
} else {
|
|
3375
|
+
const result = await copyTemplate(cwd);
|
|
3376
|
+
copied = result.copied;
|
|
3377
|
+
skipped = result.skipped;
|
|
3378
|
+
}
|
|
3436
3379
|
for (const f4 of copied)
|
|
3437
3380
|
s2.message(`\u2713 Created ${f4}`);
|
|
3438
3381
|
for (const f4 of skipped)
|
|
@@ -3441,7 +3384,7 @@ var initCommand = defineCommand({
|
|
|
3441
3384
|
s2.message("\u2713 Updated package.json");
|
|
3442
3385
|
if (hasExtraExposes) {
|
|
3443
3386
|
const manifest = { name: appName, exposes: { "./App": "./src/App" } };
|
|
3444
|
-
writeFileSync3(
|
|
3387
|
+
writeFileSync3(resolve6(cwd, "mf.manifest.json"), JSON.stringify(manifest, null, 2) + `
|
|
3445
3388
|
`);
|
|
3446
3389
|
s2.message("\u2713 Created mf.manifest.json (add extra exposes as needed)");
|
|
3447
3390
|
}
|
|
@@ -3450,7 +3393,7 @@ var initCommand = defineCommand({
|
|
|
3450
3393
|
}
|
|
3451
3394
|
});
|
|
3452
3395
|
function getPackageName(cwd) {
|
|
3453
|
-
const pkgPath =
|
|
3396
|
+
const pkgPath = resolve6(cwd, "package.json");
|
|
3454
3397
|
if (!existsSync6(pkgPath))
|
|
3455
3398
|
return "mfe-app";
|
|
3456
3399
|
try {
|
package/dist/test.js
ADDED
package/package.json
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrisluyi/daas-cli",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"daas": "./dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./dist/index.js",
|
|
11
|
+
"./test": "./dist/test.js"
|
|
12
|
+
},
|
|
9
13
|
"files": [
|
|
10
14
|
"dist",
|
|
11
15
|
"templates",
|
|
12
16
|
"skill.md"
|
|
13
17
|
],
|
|
14
18
|
"scripts": {
|
|
15
|
-
"build": "bun build src/index.ts --outfile dist/index.js.tmp --target bun --external @rspack/core --external @rspack/lite-tapable --external @rsbuild/core --external @rsbuild/plugin-react --external @module-federation/rsbuild-plugin --external vitest --external @vitest/browser --external @vitest/ui --external tapable --external react-router-dom --external lightningcss && printf '%s\\n' '#!/usr/bin/env bun' | cat - dist/index.js.tmp > dist/index.js && rm dist/index.js.tmp && chmod +x dist/index.js",
|
|
19
|
+
"build": "bun build src/index.ts --outfile dist/index.js.tmp --target bun --external @rspack/core --external @rspack/lite-tapable --external @rsbuild/core --external @rsbuild/plugin-react --external @module-federation/rsbuild-plugin --external vitest --external @vitest/browser --external @vitest/ui --external tapable --external react-router-dom --external lightningcss --external @chrisluyi/template --external @chrisluyi/rsbuild-config && printf '%s\\n' '#!/usr/bin/env bun' | cat - dist/index.js.tmp > dist/index.js && rm dist/index.js.tmp && chmod +x dist/index.js && bun build src/test.ts --outfile dist/test.js --target bun --external vitest --external @testing-library/react --external @testing-library/user-event --external @chrisluyi/rsbuild-config",
|
|
16
20
|
"test": "bun test --ignore 'templates/**'"
|
|
17
21
|
},
|
|
18
22
|
"dependencies": {
|
|
19
23
|
"@clack/prompts": "^0.7.0",
|
|
20
24
|
"@chrisluyi/rsbuild-config": "workspace:*",
|
|
25
|
+
"@chrisluyi/template": "workspace:*",
|
|
21
26
|
"@rsbuild/core": "^1.0.0",
|
|
22
27
|
"citty": "^0.1.6",
|
|
23
28
|
"consola": "^3.2.3",
|
package/skill.md
CHANGED
|
@@ -15,7 +15,8 @@ MFE apps intentionally have NO `rsbuild.config.ts`, NO `vitest.config.ts`. The C
|
|
|
15
15
|
|
|
16
16
|
| Command | Purpose |
|
|
17
17
|
|---|---|
|
|
18
|
-
| `daas init
|
|
18
|
+
| `daas init` | Interactive scaffold (minimal): creates App.tsx, App.test.tsx, tsconfig.json, updates package.json |
|
|
19
|
+
| `daas init --template default` | Full structure scaffold: creates services/containers/components/config layer folders |
|
|
19
20
|
| `daas dev [--port <n>] [--json]` | Start rsbuild dev server |
|
|
20
21
|
| `daas build [--json]` | Production build to ./dist |
|
|
21
22
|
| `daas test [--json]` | Run Vitest tests |
|
|
@@ -83,7 +84,25 @@ Remote URLs are **not configured here** — they are resolved dynamically at run
|
|
|
83
84
|
|
|
84
85
|
`daas-cli` is the only `devDependency` needed. It brings rsbuild, vitest, @testing-library/react, happy-dom, and all tooling as its own dependencies. Do not install these separately.
|
|
85
86
|
|
|
87
|
+
## `--template default` Folder Structure
|
|
88
|
+
|
|
89
|
+
When using `daas init --template default`, the scaffold creates:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
App.tsx ← MF2 entry point (exposes ./App)
|
|
93
|
+
App.test.tsx
|
|
94
|
+
tsconfig.json
|
|
95
|
+
src/
|
|
96
|
+
components/App.tsx ← presentational component
|
|
97
|
+
containers/AppContainer.tsx ← data/business logic wrapper
|
|
98
|
+
services/index.ts ← data fetching / API calls
|
|
99
|
+
config/
|
|
100
|
+
regions/default.ts ← region config layer
|
|
101
|
+
platforms/default.ts ← platform config layer
|
|
102
|
+
products/default.ts ← product config layer
|
|
103
|
+
```
|
|
104
|
+
|
|
86
105
|
## Roadmap (do not suggest these — not yet implemented)
|
|
87
106
|
|
|
88
107
|
- v1.5: `daas dev sg-foo-mb-dev` target strings for multi-region builds
|
|
89
|
-
- v2
|
|
108
|
+
- v2+: `daas lint` — validates project structure against template conventions
|
package/dist/daas-bin
DELETED
|
Binary file
|