@chrisluyi/daas-cli 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,8 +11,8 @@ MFE apps have **no build config files** — no `rsbuild.config.ts`, no `vitest.c
11
11
  | Package | Version | Description |
12
12
  |---|---|---|
13
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) |
14
+ | [`@chrisluyi/daas-rsbuild-config`](https://www.npmjs.com/package/@chrisluyi/daas-rsbuild-config) | 1.1.0 | rsbuild config factory + vitest config — arrives transitively |
15
+ | [`@chrisluyi/daas-template`](https://www.npmjs.com/package/@chrisluyi/daas-template) | 1.0.0 | MFE scaffold templates (services / containers / components structure) |
16
16
 
17
17
  ---
18
18
 
package/dist/index.js CHANGED
@@ -912,6 +912,9 @@ var require_picocolors = __commonJS((exports, module) => {
912
912
  module.exports.createColors = createColors2;
913
913
  });
914
914
 
915
+ // src/index.ts
916
+ import { createLogger as createLogger5, setLogger as setLogger5 } from "@chrisluyi/daas-logger";
917
+
915
918
  // ../../node_modules/.bun/consola@3.4.2/node_modules/consola/dist/core.mjs
916
919
  var LogLevels = {
917
920
  silent: Number.NEGATIVE_INFINITY,
@@ -2312,9 +2315,18 @@ async function runMain(cmd, opts = {}) {
2312
2315
  }
2313
2316
 
2314
2317
  // src/commands/build.ts
2315
- import { createConfig } from "@chrisluyi/rsbuild-config";
2318
+ import { apiTransport, createLogger, setLogger } from "@chrisluyi/daas-logger";
2319
+ import { createConfig } from "@chrisluyi/daas-rsbuild-config";
2316
2320
  import { createRsbuild } from "@rsbuild/core";
2317
2321
 
2322
+ // src/bridge.ts
2323
+ import { mkdirSync, writeFileSync } from "fs";
2324
+ import { dirname, relative, resolve as resolve2, sep as sep2 } from "path";
2325
+ import {
2326
+ BRIDGE_EXPOSE_KEY,
2327
+ bridgeWrapperModule
2328
+ } from "@chrisluyi/daas-rsbuild-config";
2329
+
2318
2330
  // src/daas-config.ts
2319
2331
  import { existsSync, readFileSync } from "fs";
2320
2332
  import { resolve } from "path";
@@ -2332,20 +2344,64 @@ function readDaasConfig(cwd = process.cwd()) {
2332
2344
  }
2333
2345
  const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
2334
2346
  const daas = pkg.daas;
2335
- if (!daas?.configUrl) {
2336
- throw new ConfigError("No daas config found in package.json. Run daas init first.");
2337
- }
2338
2347
  return {
2339
- configUrl: daas.configUrl,
2340
- port: daas.port ?? 3000,
2341
- coverage: daas.coverage,
2342
- setupFiles: daas.setupFiles
2348
+ configUrl: daas?.configUrl,
2349
+ port: daas?.port ?? 3000,
2350
+ coverage: daas?.coverage,
2351
+ setupFiles: daas?.setupFiles,
2352
+ logUrl: daas?.logUrl,
2353
+ bridge: daas?.bridge ?? false
2354
+ };
2355
+ }
2356
+
2357
+ // src/bridge.ts
2358
+ function prepareBridgeBuild(cwd, manifest) {
2359
+ const exposeValues = Object.values(manifest.exposes);
2360
+ if (exposeValues.length !== 1) {
2361
+ throw new ConfigError(`Bridge MFEs must expose exactly one root component (found ${exposeValues.length}). ` + "A bridge remote is a single full sub-app; expose only its root in mf.manifest.json.");
2362
+ }
2363
+ const wrapperAbsPath = resolve2(cwd, ".daas", "export-app.tsx");
2364
+ const rootAbs = resolve2(cwd, exposeValues[0]);
2365
+ let rootImport = relative(dirname(wrapperAbsPath), rootAbs).split(sep2).join("/");
2366
+ if (!rootImport.startsWith("."))
2367
+ rootImport = `./${rootImport}`;
2368
+ return {
2369
+ manifest: {
2370
+ name: manifest.name,
2371
+ exposes: { [BRIDGE_EXPOSE_KEY]: "./.daas/export-app" }
2372
+ },
2373
+ wrapperAbsPath,
2374
+ wrapperContent: bridgeWrapperModule(rootImport)
2375
+ };
2376
+ }
2377
+ function writeBridgeWrapper(prep) {
2378
+ mkdirSync(dirname(prep.wrapperAbsPath), { recursive: true });
2379
+ writeFileSync(prep.wrapperAbsPath, prep.wrapperContent);
2380
+ }
2381
+
2382
+ // src/logger/consolaTransport.ts
2383
+ function consolaTransport() {
2384
+ return (entry) => {
2385
+ switch (entry.level) {
2386
+ case "debug":
2387
+ consola.debug(entry.message, entry.context ?? "");
2388
+ break;
2389
+ case "info":
2390
+ consola.info(entry.message);
2391
+ break;
2392
+ case "warn":
2393
+ consola.warn(entry.message);
2394
+ break;
2395
+ case "error":
2396
+ consola.fatal(entry.message);
2397
+ break;
2398
+ }
2343
2399
  };
2344
2400
  }
2345
2401
 
2346
2402
  // src/manifest.ts
2347
2403
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
2348
- import { resolve as resolve2 } from "path";
2404
+ import { resolve as resolve3 } from "path";
2349
2405
 
2350
2406
  class ManifestError extends Error {
2351
2407
  constructor(message) {
@@ -2354,7 +2410,7 @@ class ManifestError extends Error {
2354
2410
  }
2355
2411
  }
2356
2412
  function readManifest(cwd = process.cwd()) {
2357
- const manifestPath = resolve2(cwd, "mf.manifest.json");
2413
+ const manifestPath = resolve3(cwd, "mf.manifest.json");
2358
2414
  if (existsSync2(manifestPath)) {
2359
2415
  try {
2360
2416
  const raw = JSON.parse(readFileSync2(manifestPath, "utf8"));
@@ -2368,7 +2424,7 @@ function readManifest(cwd = process.cwd()) {
2368
2424
  return deriveFromPackageJson(cwd);
2369
2425
  }
2370
2426
  function deriveFromPackageJson(cwd) {
2371
- const pkgPath = resolve2(cwd, "package.json");
2427
+ const pkgPath = resolve3(cwd, "package.json");
2372
2428
  if (!existsSync2(pkgPath)) {
2373
2429
  throw new ManifestError("No package.json found. Are you in an MFE directory?");
2374
2430
  }
@@ -2377,14 +2433,16 @@ function deriveFromPackageJson(cwd) {
2377
2433
  return { name, exposes: { "./App": "./src/App" } };
2378
2434
  }
2379
2435
  function normalizeManifest(raw, cwd) {
2380
- const base = deriveFromPackageJson(cwd);
2436
+ const hasName = typeof raw.name === "string";
2437
+ const hasExposes = raw.exposes != null;
2438
+ const base = hasName && hasExposes ? null : deriveFromPackageJson(cwd);
2381
2439
  return {
2382
- name: typeof raw.name === "string" ? raw.name : base.name,
2383
- exposes: raw.exposes != null ? raw.exposes : base.exposes
2440
+ name: hasName ? raw.name.replace(/-/g, "_") : base.name,
2441
+ exposes: hasExposes ? raw.exposes : base.exposes
2384
2442
  };
2385
2443
  }
2386
2444
  function stripScope(name) {
2387
- return name.replace(/^@[^/]+\//, "");
2445
+ return name.replace(/^@[^/]+\//, "").replace(/-/g, "_");
2388
2446
  }
2389
2447
 
2390
2448
  // src/output.ts
@@ -2415,15 +2473,15 @@ function getCliVersion() {
2415
2473
  }
2416
2474
 
2417
2475
  // src/remote-config.ts
2418
- import { SUPPORTED_CONFIG_VERSION } from "@chrisluyi/rsbuild-config";
2476
+ import { SUPPORTED_CONFIG_VERSION } from "@chrisluyi/daas-rsbuild-config";
2419
2477
  // src/fallback-config.json
2420
2478
  var fallback_config_default = {
2421
2479
  version: "1.0.0",
2422
2480
  minCliVersion: "1.0.0",
2423
2481
  releaseNotesUrl: "https://github.com/your-org/daas-cli/releases",
2424
2482
  shared: {
2425
- react: "18.3.1",
2426
- "react-dom": "18.3.1"
2483
+ react: "^19.0.0",
2484
+ "react-dom": "^19.0.0"
2427
2485
  },
2428
2486
  rsbuildConfigVersion: 1
2429
2487
  };
@@ -2500,8 +2558,8 @@ function meetsMinVersion(current, min) {
2500
2558
  }
2501
2559
 
2502
2560
  // src/target.ts
2503
- import { writeFileSync } from "fs";
2504
- import { resolve as resolve3 } from "path";
2561
+ import { writeFileSync as writeFileSync2 } from "fs";
2562
+ import { resolve as resolve4 } from "path";
2505
2563
 
2506
2564
  class TargetParseError extends Error {
2507
2565
  constructor(message) {
@@ -2532,7 +2590,7 @@ function generateTsconfigDaas(cwd, target) {
2532
2590
  }
2533
2591
  }
2534
2592
  };
2535
- writeFileSync(resolve3(cwd, "tsconfig.daas.json"), `${JSON.stringify(content, null, 2)}
2593
+ writeFileSync2(resolve4(cwd, "tsconfig.daas.json"), `${JSON.stringify(content, null, 2)}
2536
2594
  `);
2537
2595
  }
2538
2596
 
@@ -2565,18 +2623,33 @@ var buildCommand = defineCommand({
2565
2623
  const json = args.json;
2566
2624
  try {
2567
2625
  const daasConfig = readDaasConfig();
2568
- const manifest = readManifest();
2626
+ setLogger(createLogger([consolaTransport(), apiTransport(daasConfig.logUrl)]));
2627
+ let manifest = readManifest();
2628
+ if (daasConfig.bridge) {
2629
+ const prep = prepareBridgeBuild(process.cwd(), manifest);
2630
+ writeBridgeWrapper(prep);
2631
+ manifest = prep.manifest;
2632
+ if (!json)
2633
+ consola.info('Bridge mode: isolated React, exposing ./export-app (register host entry with loader: "bridge")');
2634
+ }
2569
2635
  if (!json)
2570
2636
  consola.start(`Building ${manifest.name}...`);
2571
- const remoteMeta = await fetchRemoteConfig(daasConfig.configUrl, true);
2572
- checkCliVersion(remoteMeta);
2573
- checkConfigVersion(remoteMeta, true);
2637
+ let remoteMeta;
2638
+ if (daasConfig.configUrl) {
2639
+ remoteMeta = await fetchRemoteConfig(daasConfig.configUrl, true);
2640
+ checkCliVersion(remoteMeta);
2641
+ checkConfigVersion(remoteMeta, true);
2642
+ } else {
2643
+ remoteMeta = loadFallback();
2644
+ consola.info("No configUrl \u2014 using built-in fallback config");
2645
+ }
2574
2646
  const target = args.target ? parseTarget(args.target) : undefined;
2575
2647
  if (target)
2576
2648
  generateTsconfigDaas(process.cwd(), target);
2577
2649
  const config = createConfig(manifest, remoteMeta, {
2578
2650
  mode: "production",
2579
- target
2651
+ target,
2652
+ bridge: daasConfig.bridge
2580
2653
  });
2581
2654
  const rsbuild = await createRsbuild({ rsbuildConfig: config });
2582
2655
  await rsbuild.build();
@@ -2621,7 +2694,8 @@ var buildCommand = defineCommand({
2621
2694
  });
2622
2695
 
2623
2696
  // src/commands/dev.ts
2624
- import { createConfig as createConfig2 } from "@chrisluyi/rsbuild-config";
2697
+ import { apiTransport as apiTransport2, createLogger as createLogger2, setLogger as setLogger2 } from "@chrisluyi/daas-logger";
2698
+ import { createConfig as createConfig2 } from "@chrisluyi/daas-rsbuild-config";
2625
2699
  import { createRsbuild as createRsbuild2 } from "@rsbuild/core";
2626
2700
  var devCommand = defineCommand({
2627
2701
  meta: { name: "dev", description: "Start MFE dev server" },
@@ -2641,12 +2715,25 @@ var devCommand = defineCommand({
2641
2715
  async run({ args }) {
2642
2716
  try {
2643
2717
  const daasConfig = readDaasConfig();
2644
- const manifest = readManifest();
2645
- const remoteMeta = await fetchRemoteConfig(daasConfig.configUrl, false);
2646
- checkCliVersion(remoteMeta);
2647
- const versionOk = checkConfigVersion(remoteMeta, false);
2648
- if (!versionOk) {
2649
- consola.warn("Config version mismatch \u2014 update daas-cli for full compatibility");
2718
+ setLogger2(createLogger2([consolaTransport(), apiTransport2(daasConfig.logUrl)]));
2719
+ let manifest = readManifest();
2720
+ if (daasConfig.bridge) {
2721
+ const prep = prepareBridgeBuild(process.cwd(), manifest);
2722
+ writeBridgeWrapper(prep);
2723
+ manifest = prep.manifest;
2724
+ consola.info('Bridge mode: isolated React, exposing ./export-app (register host entry with loader: "bridge")');
2725
+ }
2726
+ let remoteMeta;
2727
+ if (daasConfig.configUrl) {
2728
+ remoteMeta = await fetchRemoteConfig(daasConfig.configUrl, false);
2729
+ checkCliVersion(remoteMeta);
2730
+ const versionOk = checkConfigVersion(remoteMeta, false);
2731
+ if (!versionOk) {
2732
+ consola.warn("Config version mismatch \u2014 update daas-cli for full compatibility");
2733
+ }
2734
+ } else {
2735
+ remoteMeta = loadFallback();
2736
+ consola.info("No configUrl \u2014 using built-in fallback config");
2650
2737
  }
2651
2738
  const port = args.port ? Number(args.port) : daasConfig.port;
2652
2739
  const target = args.target ? parseTarget(args.target) : undefined;
@@ -2657,7 +2744,8 @@ var devCommand = defineCommand({
2657
2744
  const config = createConfig2(manifest, remoteMeta, {
2658
2745
  mode: "development",
2659
2746
  port,
2660
- target
2747
+ target,
2748
+ bridge: daasConfig.bridge
2661
2749
  });
2662
2750
  consola.start(`Starting ${manifest.name} dev server...`);
2663
2751
  const rsbuild = await createRsbuild2({ rsbuildConfig: config });
@@ -2698,22 +2786,86 @@ function handleError(command, e2, json) {
2698
2786
  process.exit(EXIT_CODES.GENERAL_ERROR);
2699
2787
  }
2700
2788
 
2701
- // src/init/monorepo.ts
2789
+ // src/commands/host.ts
2702
2790
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
2703
- import { dirname, resolve as resolve4 } from "path";
2791
+ import { resolve as resolve5 } from "path";
2792
+ import { createHostConfig } from "@chrisluyi/daas-rsbuild-config";
2793
+ import { createRsbuild as createRsbuild3 } from "@rsbuild/core";
2794
+ function sanitizeName(name) {
2795
+ return name.replace(/^@[^/]+\//, "").replace(/[^a-zA-Z0-9]/g, "_");
2796
+ }
2797
+ function readHostPackage(cwd = process.cwd()) {
2798
+ const pkgPath = resolve5(cwd, "package.json");
2799
+ if (!existsSync3(pkgPath)) {
2800
+ throw new Error("No package.json found. Are you in a host app directory?");
2801
+ }
2802
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
2803
+ const daas = pkg.daas ?? {};
2804
+ return {
2805
+ name: sanitizeName(pkg.name ?? "host"),
2806
+ port: daas.port ?? 3000
2807
+ };
2808
+ }
2809
+ var hostDevCommand = defineCommand({
2810
+ meta: { name: "dev", description: "Start host shell dev server" },
2811
+ async run() {
2812
+ try {
2813
+ const { name, port } = readHostPackage();
2814
+ const config = createHostConfig(name, { mode: "development", port });
2815
+ consola.start(`Starting ${name} dev server...`);
2816
+ const rsbuild = await createRsbuild3({ rsbuildConfig: config });
2817
+ const server = await rsbuild.createDevServer();
2818
+ await server.listen();
2819
+ consola.success(`Ready on http://localhost:${port}`);
2820
+ } catch (e2) {
2821
+ consola.fatal(e2);
2822
+ process.exit(1);
2823
+ }
2824
+ }
2825
+ });
2826
+ var hostBuildCommand = defineCommand({
2827
+ meta: { name: "build", description: "Build host shell for production" },
2828
+ async run() {
2829
+ try {
2830
+ const { name } = readHostPackage();
2831
+ const config = createHostConfig(name, { mode: "production" });
2832
+ consola.start(`Building ${name}...`);
2833
+ const rsbuild = await createRsbuild3({ rsbuildConfig: config });
2834
+ await rsbuild.build();
2835
+ consola.success("Build complete");
2836
+ } catch (e2) {
2837
+ consola.fatal(e2);
2838
+ process.exit(1);
2839
+ }
2840
+ }
2841
+ });
2842
+ var hostCommand = defineCommand({
2843
+ meta: { name: "host", description: "Commands for host shell apps" },
2844
+ subCommands: {
2845
+ dev: hostDevCommand,
2846
+ build: hostBuildCommand
2847
+ }
2848
+ });
2849
+
2850
+ // src/commands/info.ts
2851
+ import { apiTransport as apiTransport3, createLogger as createLogger3, setLogger as setLogger3 } from "@chrisluyi/daas-logger";
2852
+
2853
+ // src/init/monorepo.ts
2854
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
2855
+ import { dirname as dirname2, resolve as resolve6 } from "path";
2704
2856
  function detectMonorepo(startDir) {
2705
2857
  let dir = startDir;
2706
2858
  for (let i2 = 0;i2 < 10; i2++) {
2707
- const parent = dirname(dir);
2859
+ const parent = dirname2(dir);
2708
2860
  if (parent === dir)
2709
2861
  break;
2710
2862
  dir = parent;
2711
- if (existsSync3(resolve4(dir, "bun.lockb")))
2863
+ if (existsSync4(resolve6(dir, "bun.lockb")))
2712
2864
  return true;
2713
- const pkgPath = resolve4(dir, "package.json");
2714
- if (existsSync3(pkgPath)) {
2865
+ const pkgPath = resolve6(dir, "package.json");
2866
+ if (existsSync4(pkgPath)) {
2715
2867
  try {
2716
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
2868
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
2717
2869
  if (pkg.workspaces)
2718
2870
  return true;
2719
2871
  } catch {}
@@ -2738,6 +2890,7 @@ var infoCommand = defineCommand({
2738
2890
  let manifest;
2739
2891
  try {
2740
2892
  daasConfig = readDaasConfig();
2893
+ setLogger3(createLogger3([consolaTransport(), apiTransport3(daasConfig.logUrl)]));
2741
2894
  manifest = readManifest();
2742
2895
  } catch (e2) {
2743
2896
  if (json)
@@ -2747,9 +2900,13 @@ var infoCommand = defineCommand({
2747
2900
  }
2748
2901
  const isMonorepo = detectMonorepo(process.cwd());
2749
2902
  let remoteJson;
2750
- try {
2751
- remoteJson = await fetchRemoteConfig(daasConfig.configUrl, true);
2752
- } catch {
2903
+ if (daasConfig.configUrl) {
2904
+ try {
2905
+ remoteJson = await fetchRemoteConfig(daasConfig.configUrl, true);
2906
+ } catch {
2907
+ remoteJson = { ...loadFallback(), offline: true };
2908
+ }
2909
+ } else {
2753
2910
  remoteJson = { ...loadFallback(), offline: true };
2754
2911
  }
2755
2912
  const payload = {
@@ -2769,10 +2926,10 @@ var infoCommand = defineCommand({
2769
2926
  });
2770
2927
 
2771
2928
  // src/commands/init.ts
2772
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
2929
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
2773
2930
  import { createRequire } from "module";
2774
- import { resolve as resolve6 } from "path";
2775
- import { scaffold } from "@chrisluyi/template";
2931
+ import { resolve as resolve8 } from "path";
2932
+ import { scaffold } from "@chrisluyi/daas-template";
2776
2933
 
2777
2934
  // ../../node_modules/.bun/@clack+core@0.3.5/node_modules/@clack/core/dist/index.mjs
2778
2935
  var import_sisteransi = __toESM(require_src(), 1);
@@ -3265,10 +3422,10 @@ var de = () => {
3265
3422
  };
3266
3423
 
3267
3424
  // src/init/copy-template.ts
3268
- import { copyFileSync, existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
3269
- import { dirname as dirname2, join } from "path";
3425
+ import { copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
3426
+ import { dirname as dirname3, join } from "path";
3270
3427
  import { fileURLToPath } from "url";
3271
- var __dirname2 = dirname2(fileURLToPath(import.meta.url));
3428
+ var __dirname2 = dirname3(fileURLToPath(import.meta.url));
3272
3429
  var TEMPLATES_DIR = join(__dirname2, "../../templates/mfe");
3273
3430
  async function copyTemplate(destDir) {
3274
3431
  const files = readdirSync(TEMPLATES_DIR);
@@ -3279,9 +3436,9 @@ async function copyTemplate(destDir) {
3279
3436
  let dest = join(destDir, destFile);
3280
3437
  if (destFile === "App.tsx" || destFile === "App.test.tsx") {
3281
3438
  dest = join(destDir, "src", destFile);
3282
- mkdirSync(join(destDir, "src"), { recursive: true });
3439
+ mkdirSync2(join(destDir, "src"), { recursive: true });
3283
3440
  }
3284
- if (existsSync4(dest)) {
3441
+ if (existsSync5(dest)) {
3285
3442
  const overwrite = await se({
3286
3443
  message: `${destFile} already exists. Overwrite?`
3287
3444
  });
@@ -3297,11 +3454,11 @@ async function copyTemplate(destDir) {
3297
3454
  }
3298
3455
 
3299
3456
  // src/init/package-json.ts
3300
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
3301
- import { resolve as resolve5 } from "path";
3457
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
3458
+ import { resolve as resolve7 } from "path";
3302
3459
  function updatePackageJson(cwd, opts) {
3303
- const pkgPath = resolve5(cwd, "package.json");
3304
- const pkg = existsSync5(pkgPath) ? JSON.parse(readFileSync4(pkgPath, "utf8")) : {};
3460
+ const pkgPath = resolve7(cwd, "package.json");
3461
+ const pkg = existsSync6(pkgPath) ? JSON.parse(readFileSync5(pkgPath, "utf8")) : {};
3305
3462
  pkg.scripts = {
3306
3463
  ...pkg.scripts ?? {},
3307
3464
  dev: "daas dev",
@@ -3310,9 +3467,10 @@ function updatePackageJson(cwd, opts) {
3310
3467
  };
3311
3468
  pkg.dependencies = {
3312
3469
  ...pkg.dependencies ?? {},
3470
+ "@module-federation/bridge-react": "*",
3313
3471
  "@radix-ui/react-tabs": "^1.1.0",
3314
3472
  ...opts.isFullTemplate ? {
3315
- "@chrisluyi/template": `^${opts.templateVersion ?? opts.cliVersion ?? "1.0.0"}`,
3473
+ "@chrisluyi/daas-template": `^${opts.templateVersion ?? opts.cliVersion ?? "1.0.0"}`,
3316
3474
  "@tanstack/react-query": "^5.0.0",
3317
3475
  zustand: "^5.0.0",
3318
3476
  immer: "^10.0.0",
@@ -3326,7 +3484,7 @@ function updatePackageJson(cwd, opts) {
3326
3484
  };
3327
3485
  }
3328
3486
  pkg.daas = { configUrl: opts.configUrl, port: opts.port };
3329
- writeFileSync2(pkgPath, `${JSON.stringify(pkg, null, 2)}
3487
+ writeFileSync3(pkgPath, `${JSON.stringify(pkg, null, 2)}
3330
3488
  `);
3331
3489
  }
3332
3490
 
@@ -3410,7 +3568,7 @@ var initCommand = defineCommand({
3410
3568
  s2.message("\u2713 Updated package.json");
3411
3569
  if (hasExtraExposes) {
3412
3570
  const manifest = { name: appName, exposes: { "./App": "./src/App" } };
3413
- writeFileSync3(resolve6(cwd, "mf.manifest.json"), `${JSON.stringify(manifest, null, 2)}
3571
+ writeFileSync4(resolve8(cwd, "mf.manifest.json"), `${JSON.stringify(manifest, null, 2)}
3414
3572
  `);
3415
3573
  s2.message("\u2713 Created mf.manifest.json (add extra exposes as needed)");
3416
3574
  }
@@ -3421,26 +3579,225 @@ var initCommand = defineCommand({
3421
3579
  function getTemplateVersion() {
3422
3580
  try {
3423
3581
  const req = createRequire(import.meta.url);
3424
- const pkg = req("@chrisluyi/template/package.json");
3582
+ const pkg = req("@chrisluyi/daas-template/package.json");
3425
3583
  return pkg.version ?? "1.0.0";
3426
3584
  } catch {
3427
3585
  return "1.0.0";
3428
3586
  }
3429
3587
  }
3430
3588
  function getPackageName(cwd) {
3431
- const pkgPath = resolve6(cwd, "package.json");
3432
- if (!existsSync6(pkgPath))
3589
+ const pkgPath = resolve8(cwd, "package.json");
3590
+ if (!existsSync7(pkgPath))
3433
3591
  return "mfe-app";
3434
3592
  try {
3435
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
3593
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
3436
3594
  return (pkg.name ?? "mfe-app").replace(/^@[^/]+\//, "");
3437
3595
  } catch {
3438
3596
  return "mfe-app";
3439
3597
  }
3440
3598
  }
3441
3599
 
3600
+ // src/region.ts
3601
+ import { spawnSync } from "child_process";
3602
+ import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
3603
+ import { relative as relative2, resolve as resolve9 } from "path";
3604
+
3605
+ class RegionError extends Error {
3606
+ constructor(message) {
3607
+ super(message);
3608
+ this.name = "RegionError";
3609
+ }
3610
+ }
3611
+ var DEFAULT_ALIASES = {
3612
+ "@region-config": "./src/config/regions/{region}/config",
3613
+ "@regional": "./src/config/regions/{region}/index"
3614
+ };
3615
+ function readRegionalConfig(cwd = process.cwd()) {
3616
+ const pkgPath = resolve9(cwd, "package.json");
3617
+ if (!existsSync8(pkgPath)) {
3618
+ throw new RegionError("No package.json found. Run from the project root (the daas.regional block lives there).");
3619
+ }
3620
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
3621
+ const r4 = pkg.daas?.regional ?? {};
3622
+ return {
3623
+ ...r4,
3624
+ regionsDir: r4.regionsDir ?? "src/config/regions",
3625
+ tsconfig: r4.tsconfig ?? "tsconfig.daas.json",
3626
+ extends: r4.extends ?? "./tsconfig.json",
3627
+ bunPaths: r4.bunPaths ?? false
3628
+ };
3629
+ }
3630
+ function discoverRegions(cwd, cfg) {
3631
+ if (cfg.regions && cfg.regions.length > 0)
3632
+ return cfg.regions;
3633
+ const dir = resolve9(cwd, cfg.regionsDir ?? "src/config/regions");
3634
+ if (!existsSync8(dir)) {
3635
+ throw new RegionError(`regionsDir "${cfg.regionsDir}" does not exist \u2014 set daas.regional.regionsDir or daas.regional.regions in package.json`);
3636
+ }
3637
+ const regions = readdirSync2(dir, { withFileTypes: true }).filter((e3) => e3.isDirectory()).map((e3) => e3.name).sort();
3638
+ if (regions.length === 0) {
3639
+ throw new RegionError(`regionsDir "${cfg.regionsDir}" has no region folders`);
3640
+ }
3641
+ return regions;
3642
+ }
3643
+ function renderRegionTsconfig(cwd, cfg, region) {
3644
+ const regions = discoverRegions(cwd, cfg);
3645
+ if (!regions.includes(region)) {
3646
+ throw new RegionError(`Unknown region "${region}". Known: ${regions.join(", ")}`);
3647
+ }
3648
+ const aliases = cfg.aliases ?? DEFAULT_ALIASES;
3649
+ const paths = Object.fromEntries(Object.entries(aliases).map(([k3, v3]) => [
3650
+ k3,
3651
+ [v3.replaceAll("{region}", region)]
3652
+ ]));
3653
+ const tsconfigDir = resolve9(cwd, cfg.tsconfig, "..");
3654
+ const regionsDirAbs = resolve9(cwd, cfg.regionsDir);
3655
+ const others = regions.filter((r4) => r4 !== region).map((r4) => relative2(tsconfigDir, resolve9(regionsDirAbs, r4)));
3656
+ const exclude = [...others, ...cfg.multiRegionFiles ?? []];
3657
+ const compilerOptions = {
3658
+ ...cfg.bunPaths ? { baseUrl: ".", ignoreDeprecations: "6.0" } : {},
3659
+ paths
3660
+ };
3661
+ const body = JSON.stringify({ extends: cfg.extends, compilerOptions, exclude }, null, 2);
3662
+ return `// GENERATED by \`daas region ${region}\` \u2014 do not edit; regenerate or \`daas region --reset\`.
3663
+ ${body}
3664
+ `;
3665
+ }
3666
+ function writeRegionTsconfig(cwd, cfg, region, applyTo) {
3667
+ const content = renderRegionTsconfig(cwd, cfg, region);
3668
+ const out = resolve9(cwd, cfg.tsconfig);
3669
+ writeFileSync5(out, content);
3670
+ if (applyTo) {
3671
+ writeFileSync5(resolve9(cwd, applyTo), content);
3672
+ return { generated: out, applied: resolve9(cwd, applyTo) };
3673
+ }
3674
+ return { generated: out };
3675
+ }
3676
+ function resetTsconfig(cwd, path) {
3677
+ const rel = relative2(cwd, resolve9(cwd, path));
3678
+ const show = spawnSync("git", ["show", `HEAD:${rel}`], {
3679
+ cwd,
3680
+ encoding: "utf8"
3681
+ });
3682
+ if (show.status !== 0) {
3683
+ throw new RegionError(`Cannot restore "${rel}" from git HEAD: ${show.stderr.trim() || "not a tracked file?"}`);
3684
+ }
3685
+ writeFileSync5(resolve9(cwd, path), show.stdout);
3686
+ }
3687
+ function runRegionCheck(cwd, cfg, runner = (p) => {
3688
+ const r4 = spawnSync("bunx", ["tsc", "--noEmit", "-p", p], {
3689
+ cwd,
3690
+ encoding: "utf8"
3691
+ });
3692
+ return { ok: r4.status === 0, output: `${r4.stdout ?? ""}${r4.stderr ?? ""}` };
3693
+ }) {
3694
+ const results = [];
3695
+ for (const region of discoverRegions(cwd, cfg)) {
3696
+ writeRegionTsconfig(cwd, cfg, region);
3697
+ const { ok, output } = runner(resolve9(cwd, cfg.tsconfig));
3698
+ results.push({ region, ok, output });
3699
+ }
3700
+ return results;
3701
+ }
3702
+
3703
+ // src/commands/region.ts
3704
+ var regionCommand = defineCommand({
3705
+ meta: {
3706
+ name: "region",
3707
+ description: "Region-scoped type programs: generate a tsconfig containing ONLY the selected region (mirror of the build-time region alias swap)"
3708
+ },
3709
+ args: {
3710
+ region: {
3711
+ type: "positional",
3712
+ description: "Region to select (see --list)",
3713
+ required: false
3714
+ },
3715
+ list: { type: "boolean", default: false, description: "List regions" },
3716
+ check: {
3717
+ type: "boolean",
3718
+ default: false,
3719
+ description: "Matrix gate: generate + tsc --noEmit for EVERY region; fails if any region's program fails"
3720
+ },
3721
+ apply: {
3722
+ type: "string",
3723
+ description: "Also overwrite this tsconfig with the generated content (editor + Bun follow the selection); undo with --reset"
3724
+ },
3725
+ reset: {
3726
+ type: "string",
3727
+ description: "Restore this tsconfig from git HEAD (undo --apply)"
3728
+ },
3729
+ json: {
3730
+ type: "boolean",
3731
+ default: false,
3732
+ description: "Machine-readable JSON output"
3733
+ }
3734
+ },
3735
+ async run({ args }) {
3736
+ try {
3737
+ const cwd = process.cwd();
3738
+ const cfg = readRegionalConfig(cwd);
3739
+ if (args.reset) {
3740
+ resetTsconfig(cwd, args.reset);
3741
+ if (args.json)
3742
+ writeJsonSuccess("region", { reset: args.reset });
3743
+ else
3744
+ consola.success(`Restored ${args.reset} from git HEAD`);
3745
+ return;
3746
+ }
3747
+ if (args.list) {
3748
+ const regions = discoverRegions(cwd, cfg);
3749
+ if (args.json)
3750
+ writeJsonSuccess("region", { regions });
3751
+ else
3752
+ consola.info(`Regions: ${regions.join(", ")}`);
3753
+ return;
3754
+ }
3755
+ if (args.check) {
3756
+ const results = runRegionCheck(cwd, cfg);
3757
+ for (const r4 of results) {
3758
+ if (r4.ok)
3759
+ consola.success(`region ${r4.region}`);
3760
+ else {
3761
+ consola.error(`region ${r4.region}`);
3762
+ if (!args.json && r4.output.trim())
3763
+ consola.log(r4.output.trim());
3764
+ }
3765
+ }
3766
+ const failed = results.filter((r4) => !r4.ok);
3767
+ if (args.json)
3768
+ writeJsonSuccess("region", {
3769
+ results: results.map(({ region, ok }) => ({ region, ok }))
3770
+ });
3771
+ if (failed.length > 0)
3772
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3773
+ return;
3774
+ }
3775
+ if (!args.region) {
3776
+ throw new RegionError("Pass a region (see --list), or use --check / --reset");
3777
+ }
3778
+ const { generated, applied } = writeRegionTsconfig(cwd, cfg, args.region, args.apply);
3779
+ if (args.json)
3780
+ writeJsonSuccess("region", { region: args.region, generated, applied });
3781
+ else {
3782
+ consola.success(`Region-scoped tsconfig for "${args.region}" \u2192 ${generated}`);
3783
+ if (applied)
3784
+ consola.info(`Applied to ${applied} (undo: daas region --reset ${args.apply})`);
3785
+ }
3786
+ } catch (err) {
3787
+ if (err instanceof RegionError) {
3788
+ if (args.json)
3789
+ writeJsonError("region", EXIT_CODES.GENERAL_ERROR, err.message, "Check the daas.regional block in package.json (regionsDir/regions/aliases) and run with --list");
3790
+ consola.error(err.message);
3791
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3792
+ }
3793
+ throw err;
3794
+ }
3795
+ }
3796
+ });
3797
+
3442
3798
  // src/commands/test.ts
3443
- import { createVitestConfig } from "@chrisluyi/rsbuild-config/vitest";
3799
+ import { apiTransport as apiTransport4, createLogger as createLogger4, setLogger as setLogger4 } from "@chrisluyi/daas-logger";
3800
+ import { createVitestConfig } from "@chrisluyi/daas-rsbuild-config/vitest";
3444
3801
  import { startVitest } from "vitest/node";
3445
3802
  var testCommand = defineCommand({
3446
3803
  meta: { name: "test", description: "Run MFE tests" },
@@ -3456,6 +3813,7 @@ var testCommand = defineCommand({
3456
3813
  try {
3457
3814
  const manifest = readManifest();
3458
3815
  const daasConfig = readDaasConfig();
3816
+ setLogger4(createLogger4([consolaTransport(), apiTransport4(daasConfig.logUrl)]));
3459
3817
  const config = createVitestConfig(manifest, {
3460
3818
  coverage: daasConfig.coverage,
3461
3819
  setupFiles: daasConfig.setupFiles
@@ -3479,6 +3837,7 @@ var testCommand = defineCommand({
3479
3837
  });
3480
3838
 
3481
3839
  // src/index.ts
3840
+ setLogger5(createLogger5([consolaTransport()]));
3482
3841
  var main = defineCommand({
3483
3842
  meta: {
3484
3843
  name: "daas",
@@ -3490,7 +3849,9 @@ var main = defineCommand({
3490
3849
  dev: devCommand,
3491
3850
  build: buildCommand,
3492
3851
  test: testCommand,
3493
- info: infoCommand
3852
+ info: infoCommand,
3853
+ host: hostCommand,
3854
+ region: regionCommand
3494
3855
  }
3495
3856
  });
3496
3857
  runMain(main);
package/dist/test.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // @bun
2
2
  // src/test.ts
3
- export * from "@chrisluyi/rsbuild-config/test";
3
+ export * from "@chrisluyi/daas-rsbuild-config/test";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrisluyi/daas-cli",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "daas": "./dist/index.js"
@@ -16,27 +16,25 @@
16
16
  "skill.md"
17
17
  ],
18
18
  "scripts": {
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",
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/daas-template --external @chrisluyi/daas-logger --external @chrisluyi/daas-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/daas-rsbuild-config",
20
20
  "test": "bun test --ignore 'templates/**'"
21
21
  },
22
22
  "dependencies": {
23
23
  "@clack/prompts": "^0.7.0",
24
- "@chrisluyi/rsbuild-config": "workspace:*",
25
- "@chrisluyi/template": "workspace:*",
26
- "@rsbuild/core": "^1.0.0",
24
+ "@chrisluyi/daas-logger": "workspace:*",
25
+ "@chrisluyi/daas-rsbuild-config": "workspace:*",
26
+ "@chrisluyi/daas-template": "workspace:*",
27
+ "@rsbuild/core": "*",
27
28
  "citty": "^0.1.6",
28
29
  "consola": "^3.2.3",
29
- "vitest": "^2.0.0"
30
+ "vitest": "*"
30
31
  },
31
- "devDependencies": {
32
- "typescript": "^5.5.0"
32
+ "engines": {
33
+ "node": ">=20.19.0"
33
34
  },
34
35
  "repository": {
35
36
  "type": "git",
36
37
  "url": "https://github.com/Chris-LuYi/daas-cli",
37
38
  "directory": "packages/cli"
38
- },
39
- "publishConfig": {
40
- "access": "public"
41
39
  }
42
40
  }
package/skill.md CHANGED
@@ -96,13 +96,60 @@ src/
96
96
  components/App.tsx ← presentational component
97
97
  containers/AppContainer.tsx ← data/business logic wrapper
98
98
  services/index.ts ← data fetching / API calls
99
+ flows/ ← multi-step flows (optional — add when needed)
100
+ <name>/
101
+ types.ts ← TData interface
102
+ index.ts ← createFlow<TData>(...)
103
+ steps/ ← step components (useFlowData / useFlowActions)
99
104
  config/
100
105
  regions/default.ts ← region config layer
101
106
  platforms/default.ts ← platform config layer
102
107
  products/default.ts ← product config layer
103
108
  ```
104
109
 
110
+ ## Template Runtime (`@chrisluyi/daas-template/runtime`)
111
+
112
+ Key utilities available in MFE components:
113
+
114
+ | Export | Purpose |
115
+ |---|---|
116
+ | `createApiClient` | Region-aware HTTP client (auto-injects X-Region-Id, X-Locale headers) |
117
+ | `createService` | React Query data fetching with Zod validation |
118
+ | `createStore` | Zustand UI state (per-module singleton) |
119
+ | `createForm` | Zod-validated form with regional widget injection |
120
+ | `createFlow` | Router-agnostic multi-step flow engine |
121
+ | `FlowEngine` | Component wrapper — renders current step automatically |
122
+ | `useFlow` | Hook — use when you need a custom layout (progress bar etc.) |
123
+ | `useFlowData` | Step hook — read/write the shared typed data bag |
124
+ | `useFlowActions` | Step hook — `next()`, `back()`, `isFirst`, `isLast` |
125
+ | `useRegional` | Access the full `RegionalRegistry` from any component |
126
+
127
+ ### Flow SDK quick reference
128
+
129
+ ```ts
130
+ // Define
131
+ const myFlow = createFlow<MyData>({
132
+ id: "MyFlow",
133
+ steps: [{ id: "step1", component: Step1 }, { id: "step2", component: Step2 }],
134
+ initialData: {},
135
+ })
136
+
137
+ // Mount
138
+ const registry = useRegional()
139
+ return <FlowEngine flow={myFlow} registry={registry} />
140
+
141
+ // Inside a step — no props needed
142
+ const { data, setData } = useFlowData<MyData>()
143
+ const { next, back, isLast } = useFlowActions()
144
+ ```
145
+
146
+ Regional step injection via `RegionalRegistry.flows?`:
147
+ ```ts
148
+ createRegistry(defaultRegistry, {
149
+ flows: { MyFlow: { extraSteps: [{ after: "step1", id: "extra", component: ExtraStep }] } }
150
+ })
151
+ ```
152
+
105
153
  ## Roadmap (do not suggest these — not yet implemented)
106
154
 
107
- - v1.5: `daas dev sg-foo-mb-dev` target strings for multi-region builds
108
155
  - v2+: `daas lint` — validates project structure against template conventions