@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 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 = createConfig(manifest, remoteMeta, { mode: "production", target });
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 resolve5, dirname as dirname2 } from "path";
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 = dirname2(dir);
2708
+ const parent = dirname(dir);
2790
2709
  if (parent === dir)
2791
2710
  break;
2792
2711
  dir = parent;
2793
- if (existsSync3(resolve5(dir, "bun.lockb")))
2712
+ if (existsSync3(resolve4(dir, "bun.lockb")))
2794
2713
  return true;
2795
- const pkgPath = resolve5(dir, "package.json");
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 { writeFileSync as writeFileSync3, existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
3341
- import { resolve as resolve7 } from "path";
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 dirname3, join } from "path";
3346
- import { fileURLToPath as fileURLToPath2 } from "url";
3347
- var __dirname3 = dirname3(fileURLToPath2(import.meta.url));
3348
- var TEMPLATES_DIR = join(__dirname3, "../../templates/mfe");
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 resolve6 } from "path";
3297
+ import { resolve as resolve5 } from "path";
3376
3298
  function updatePackageJson(cwd, opts) {
3377
- const pkgPath = resolve6(cwd, "package.json");
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 (reserved for v2)" }
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
- if (args.template) {
3408
- consola.warn("Template support is not yet available. Scaffolding with default template.");
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
- const { copied, skipped } = await copyTemplate(cwd);
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(resolve7(cwd, "mf.manifest.json"), JSON.stringify(manifest, null, 2) + `
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 = resolve7(cwd, "package.json");
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
@@ -0,0 +1,3 @@
1
+ // @bun
2
+ // src/test.ts
3
+ export * from "@chrisluyi/rsbuild-config/test";
package/package.json CHANGED
@@ -1,23 +1,28 @@
1
1
  {
2
2
  "name": "@chrisluyi/daas-cli",
3
- "version": "1.1.0",
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 [--template <name>]` | Interactive scaffold: creates App.tsx, App.test.tsx, tsconfig.json, updates package.json |
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: `@daas/template` with src/services, src/containers, src/components conventions
108
+ - v2+: `daas lint` validates project structure against template conventions
package/dist/daas-bin DELETED
Binary file