@fulgurjs/federation 4.1.0 → 4.2.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/dist/cli.js CHANGED
@@ -156,8 +156,8 @@ var init_manifest = __esm({
156
156
 
157
157
  // src/config.ts
158
158
  async function loadRepoConfig(configPath) {
159
- const { pathToFileURL, fileURLToPath } = await import("node:url");
160
- const { createRequire } = await import("node:module");
159
+ const { pathToFileURL: pathToFileURL2, fileURLToPath } = await import("node:url");
160
+ const { createRequire: createRequire2 } = await import("node:module");
161
161
  const fs3 = await import("node:fs");
162
162
  const os = await import("node:os");
163
163
  const path3 = await import("node:path");
@@ -173,43 +173,44 @@ async function loadRepoConfig(configPath) {
173
173
  return "";
174
174
  })();
175
175
  const selfConfigJs = selfDir ? path3.join(selfDir, "config.js") : "";
176
- const SELF_SPEC = selfConfigJs && fs3.existsSync(selfConfigJs) ? pathToFileURL(selfConfigJs).href : "@fulgurjs-federation-config-unavailable";
176
+ const SELF_SPEC = selfConfigJs && fs3.existsSync(selfConfigJs) ? pathToFileURL2(selfConfigJs).href : "@fulgurjs-federation-config-unavailable";
177
177
  const rewriteSelfImports = (source) => source.replace(/(['"])@fulgurjs\/federation\/config\1/g, (_m, q) => `${q}${SELF_SPEC}${q}`);
178
+ let cacheBust = 0;
178
179
  const importConfigs = async (cacheKey) => {
179
- return await import(`${cacheKey}?t=${Date.now()}`);
180
+ return await import(`${cacheKey}?t=${Date.now()}-${++cacheBust}`);
180
181
  };
181
182
  let mod;
182
183
  if (configPath.endsWith(".json")) {
183
184
  mod = { default: JSON.parse(fs3.readFileSync(configPath, "utf8")) };
184
185
  } else {
185
186
  const source = rewriteSelfImports(fs3.readFileSync(configPath, "utf8"));
186
- const tmpBase = path3.join(os.tmpdir(), `fulgurjs-config-${Date.now()}`);
187
+ const tmpBase = path3.join(os.tmpdir(), `fulgurjs-config-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
187
188
  if (/\.(ts|mts|cts)$/.test(configPath)) {
188
189
  try {
189
190
  const tmpTs = `${tmpBase}.ts`;
190
191
  fs3.writeFileSync(tmpTs, source);
191
- mod = await importConfigs(pathToFileURL(tmpTs).href);
192
+ mod = await importConfigs(pathToFileURL2(tmpTs).href);
192
193
  } catch (e) {
193
194
  if (!/Unexpected|SyntaxError|ERR_UNSUPPORTED_NODE_MODULES|ERR_UNKNOWN_FILE_EXTENSION|ERR_IMPORT_ATTRIBUTE/.test(String(e)) && process.version.startsWith("v2")) {
194
195
  throw e;
195
196
  }
196
- const requireFromConfig = createRequire(configPath);
197
+ const requireFromConfig = createRequire2(configPath);
197
198
  let esbuild;
198
199
  try {
199
200
  esbuild = requireFromConfig("esbuild");
200
201
  } catch {
201
- const requireFromSelf = createRequire(import.meta.url);
202
+ const requireFromSelf = createRequire2(import.meta.url);
202
203
  esbuild = requireFromSelf("esbuild");
203
204
  }
204
205
  const out = esbuild.buildSync({ entryPoints: [configPath], bundle: false, write: false, format: "esm", target: "node18" });
205
206
  const jsFile = `${tmpBase}.mjs`;
206
207
  fs3.writeFileSync(jsFile, rewriteSelfImports(out.outputFiles[0].text));
207
- mod = await importConfigs(pathToFileURL(jsFile).href);
208
+ mod = await importConfigs(pathToFileURL2(jsFile).href);
208
209
  }
209
210
  } else {
210
211
  const jsFile = `${tmpBase}.mjs`;
211
212
  fs3.writeFileSync(jsFile, source);
212
- mod = await importConfigs(pathToFileURL(jsFile).href);
213
+ mod = await importConfigs(pathToFileURL2(jsFile).href);
213
214
  }
214
215
  }
215
216
  const cfg = mod.default;
@@ -287,6 +288,219 @@ var init_config = __esm({
287
288
  }
288
289
  });
289
290
 
291
+ // src/app-config.ts
292
+ import { createRequire } from "node:module";
293
+ import { pathToFileURL } from "node:url";
294
+ function appConfigError(what, cause, fix) {
295
+ throw new Error(`[fulgurjs:config] ${what}
296
+ \u6839\u56E0\uFF1A${cause}
297
+ \u4FEE\u6CD5\uFF1A${fix}`);
298
+ }
299
+ function isAggregateShape(value) {
300
+ return !!value && typeof value === "object" && Array.isArray(value.apps);
301
+ }
302
+ function fieldError(field, got, expect, example) {
303
+ appConfigError(
304
+ `fulgurjs.config.ts \u7684 ${field} \u5F62\u72B6\u4E0D\u5408\u6CD5\uFF08\u5B9E\u9645 ${JSON.stringify(got)}\uFF09`,
305
+ `CLI \u6309\u5355\u9879\u76EE\u5951\u7EA6\u8BFB\u53D6\uFF1A\u9ED8\u8BA4\u5BFC\u51FA\u76F4\u63A5\u662F federation() \u9009\u9879\u5BF9\u8C61`,
306
+ `\u671F\u671B ${expect}\u3002\u793A\u4F8B\uFF1A
307
+ ${example}`
308
+ );
309
+ }
310
+ function validateAppOptions(options) {
311
+ if (typeof options.name !== "string" || options.name.trim() === "") {
312
+ fieldError(
313
+ "\u9ED8\u8BA4\u5BFC\u51FA name",
314
+ options.name,
315
+ "\u975E\u7A7A\u5B57\u7B26\u4E32\uFF08\u8054\u90A6\u5BB9\u5668\u540D\uFF09",
316
+ `export default { name: 'my-app', exposes: { './pages/home': './src/views/Home.vue' } } satisfies FederationOptions`
317
+ );
318
+ }
319
+ for (const legacyField of ["root", "apps", "deploy", "host", "remote"]) {
320
+ if (options[legacyField] !== void 0) {
321
+ appConfigError(
322
+ `\u9ED8\u8BA4\u5BFC\u51FA\u5305\u542B\u65E7\u805A\u5408/\u89D2\u8272\u58F3\u5B57\u6BB5 "${legacyField}"`,
323
+ "\u5355\u9879\u76EE\u5951\u7EA6\u7684\u9ED8\u8BA4\u5BFC\u51FA\u76F4\u63A5\u662F federation() \u9009\u9879\uFF08name/exposes/setup/remotes/shared/\u2026\uFF09\uFF0C\u4E0D\u5305 host/remote \u89D2\u8272\u58F3\uFF0C\u4E5F\u6CA1\u6709 root/apps[]",
324
+ legacyField === "host" || legacyField === "remote" ? "\u628A host/remote \u5185\u7684\u5B57\u6BB5\u62CD\u5E73\u5230\u9876\u5C42\uFF1Ahost.remotes \u2192 remotes\uFF1Bremote.exposes \u2192 exposes\uFF1Bremote.setup \u2192 setup\uFF1Bhost.pages/remotePrefixes/deriveSpec \u2192 \u5177\u540D\u5BFC\u51FA hostPages" : `${legacyField} \u5C5E\u4E8E\u65E7\u805A\u5408\u914D\u7F6E\uFF08root + apps[]\uFF0C\u517C\u5BB9\u671F\u4FDD\u7559\uFF09\u3002\u5355\u9879\u76EE\u5F62\u6001\u8BF7\u5220\u9664\u8BE5\u5B57\u6BB5\uFF1B\u805A\u5408\u7528\u6CD5\u89C1 README\u300C\u5386\u53F2\u517C\u5BB9\u300D\u7AE0\u8282`
325
+ );
326
+ }
327
+ }
328
+ }
329
+ function validateHostPages(value) {
330
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
331
+ fieldError("\u5177\u540D\u5BFC\u51FA hostPages", value, "\u5BF9\u8C61 { pages, remotePrefixes, deriveSpec? }", `export const hostPages = { pages, remotePrefixes, deriveSpec }`);
332
+ }
333
+ const hp = value;
334
+ if (!Array.isArray(hp.pages)) {
335
+ fieldError("hostPages.pages", hp.pages, "PageEntry[]\uFF08\u8DEF\u7531\u8868\u6570\u7EC4\uFF09", `export const hostPages = { pages: [{ route: '/remote-a/home', name: 'Home', title: '\u9996\u9875' }], remotePrefixes: { '/remote-a/': 'remote-a' } }`);
336
+ }
337
+ if (!hp.remotePrefixes || typeof hp.remotePrefixes !== "object") {
338
+ fieldError("hostPages.remotePrefixes", hp.remotePrefixes, "Record<\u8DEF\u7531\u524D\u7F00, \u8FDC\u7A0B\u5BB9\u5668\u540D>", `remotePrefixes: { '/remote-a/': 'remote-a' }`);
339
+ }
340
+ if (hp.deriveSpec !== void 0 && typeof hp.deriveSpec !== "function") {
341
+ fieldError("hostPages.deriveSpec", hp.deriveSpec, "\u51FD\u6570 (route) => spec \u6216\u7701\u7565", `deriveSpec: (route) => 'pages/' + route.split('/').filter((s) => !s.startsWith(':')).join('/')`);
342
+ }
343
+ return hp;
344
+ }
345
+ function validatePathsInsideApp(options, appRoot, fs3, path3) {
346
+ const checkModule = (field, value) => {
347
+ if (typeof value !== "string" || value.trim() === "") return;
348
+ if (/^(https?:)?\/\//.test(value) || path3.isAbsolute(value)) {
349
+ appConfigError(
350
+ `${field} "${value}" \u4E0D\u662F\u672C\u9879\u76EE\u5185\u7684\u6A21\u5757\u8DEF\u5F84`,
351
+ "exposes/setup \u7684\u503C\u662F\u76F8\u5BF9\u672C\u5E94\u7528\u6839\u76EE\u5F55\u7684\u6E90\u6587\u4EF6\u8DEF\u5F84\uFF08URL/\u7EDD\u5BF9\u8DEF\u5F84\u65E0\u6CD5\u5728\u72EC\u7ACB\u4ED3\u5E93\u95F4\u79FB\u690D\uFF09",
352
+ `\u6539\u4E3A\u76F8\u5BF9\u8DEF\u5F84\uFF0C\u5982 './src/views/Home.vue'\uFF08\u914D\u7F6E\u6587\u4EF6\u76EE\u5F55 = \u5E94\u7528\u6839 ${appRoot}\uFF09`
353
+ );
354
+ }
355
+ const abs = path3.resolve(appRoot, value);
356
+ if (!fs3.existsSync(abs)) {
357
+ appConfigError(
358
+ `${field} \u6307\u5411\u7684\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${value}`,
359
+ `\u6309\u5E94\u7528\u6839 ${appRoot} \u89E3\u6790\u540E\u672A\u627E\u5230\u8BE5\u6587\u4EF6`,
360
+ `\u6838\u5BF9\u76F8\u5BF9\u8DEF\u5F84\uFF08\u4EE5\u914D\u7F6E\u6587\u4EF6\u6240\u5728\u76EE\u5F55\u4E3A\u57FA\u51C6\uFF09\uFF1B\u6587\u4EF6\u88AB\u79FB\u52A8/\u5220\u9664\u65F6\u540C\u6B65\u66F4\u65B0 ${field}`
361
+ );
362
+ }
363
+ };
364
+ const exposes = options.exposes;
365
+ if (exposes !== void 0 && (typeof exposes !== "object" || exposes === null || Array.isArray(exposes))) {
366
+ fieldError("exposes", exposes, 'Record<"./\u952E", "./\u6E90\u6587\u4EF6">', `exposes: { './pages/home': './src/views/Home.vue' }`);
367
+ }
368
+ for (const target of Object.values(exposes ?? {})) {
369
+ checkModule("exposes \u6761\u76EE", typeof target === "string" ? target : target?.import);
370
+ }
371
+ checkModule("setup", options.setup);
372
+ }
373
+ function resolveSelfEntry(configPath, spec) {
374
+ const tries = [() => createRequire(configPath).resolve(spec), () => createRequire(import.meta.url).resolve(spec)];
375
+ for (const t of tries) {
376
+ try {
377
+ return t();
378
+ } catch {
379
+ }
380
+ }
381
+ return void 0;
382
+ }
383
+ function resolveSpec(spec, from) {
384
+ try {
385
+ return createRequire(from).resolve(spec);
386
+ } catch {
387
+ return void 0;
388
+ }
389
+ }
390
+ function resolveEsbuild(configPath) {
391
+ const esbuildOf = (base) => {
392
+ const direct = resolveSpec("esbuild", base);
393
+ if (direct) return createRequire(direct)("esbuild");
394
+ const viteEntry = resolveSpec("vite", base);
395
+ if (viteEntry) {
396
+ const viaVite = resolveSpec("esbuild", viteEntry);
397
+ if (viaVite) return createRequire(viaVite)("esbuild");
398
+ }
399
+ return void 0;
400
+ };
401
+ for (const base of [configPath, import.meta.url]) {
402
+ const got = esbuildOf(base);
403
+ if (got) return got;
404
+ }
405
+ appConfigError(
406
+ "\u65E0\u6CD5\u52A0\u8F7D esbuild\uFF08\u5355\u9879\u76EE\u914D\u7F6E\u52A0\u8F7D\u9700\u8981\u5B83\u8F6C\u8BD1 TS \u4E0E\u76F8\u5BF9\u5BFC\u5165\uFF09",
407
+ "\u914D\u7F6E\u5DE5\u7A0B\uFF08\u542B\u5176 vite \u4F9D\u8D56\u6811\uFF09\u4E0E CLI \u4F9D\u8D56\u6811\u4E2D\u90FD\u89E3\u6790\u4E0D\u5230 esbuild",
408
+ "\u9879\u76EE\u5185\u6709 vite \u4F9D\u8D56\u5373\u53EF\uFF08\u7ECF\u5176\u4F9D\u8D56\u6811\u89E3\u6790\uFF09\uFF1B\u786E\u5B9E\u7F3A\u5931\u65F6 npm i -D esbuild \u540E\u91CD\u8BD5"
409
+ );
410
+ }
411
+ async function loadAppConfig(configPath) {
412
+ const fs3 = await import("node:fs");
413
+ const os = await import("node:os");
414
+ const path3 = await import("node:path");
415
+ const abs = path3.resolve(configPath);
416
+ if (!fs3.existsSync(abs)) {
417
+ appConfigError(
418
+ `\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${abs}`,
419
+ "--config \u6307\u5411\u7684\u8DEF\u5F84\u5728\u672C\u673A\u4E0D\u5B58\u5728\uFF08\u76F8\u5BF9\u8DEF\u5F84\u4EE5 CLI \u8FDB\u7A0B cwd \u89E3\u6790\uFF09",
420
+ "\u6838\u5BF9\u8DEF\u5F84\uFF1B\u4ECE\u5E94\u7528\u6839\u76EE\u5F55\u8FD0\u884C\u65F6\u76F4\u63A5\u7528 fulgurjs.config.ts"
421
+ );
422
+ }
423
+ if (abs.endsWith(".json")) {
424
+ const parsed = JSON.parse(fs3.readFileSync(abs, "utf8"));
425
+ if (isAggregateShape(parsed)) {
426
+ const repo = await loadRepoConfig(abs);
427
+ return { kind: "repo", configPath: abs, appRoot: path3.dirname(abs), repo };
428
+ }
429
+ return finishAppShape(parsed, {}, abs, path3.dirname(abs), fs3, path3);
430
+ }
431
+ const esbuild = resolveEsbuild(abs);
432
+ const selfRedirect = {
433
+ name: "fulgurjs-self-redirect",
434
+ setup(build) {
435
+ build.onResolve({ filter: /^@fulgurjs\/federation(\/|$)/ }, (args) => {
436
+ const resolved = resolveSelfEntry(abs, args.path);
437
+ if (!resolved) {
438
+ return { errors: [{ text: `cannot resolve "${args.path}" from the config project or the CLI itself` }] };
439
+ }
440
+ return { path: resolved };
441
+ });
442
+ }
443
+ };
444
+ const out = await esbuild.build({
445
+ entryPoints: [abs],
446
+ bundle: true,
447
+ write: false,
448
+ format: "esm",
449
+ target: "node18",
450
+ platform: "node",
451
+ plugins: [selfRedirect],
452
+ logLevel: "silent"
453
+ });
454
+ const tmpDir = fs3.mkdtempSync(path3.join(os.tmpdir(), "fulgurjs-appcfg-"));
455
+ try {
456
+ const bundleFile = path3.join(tmpDir, "fulgurjs.config.bundle.mjs");
457
+ fs3.writeFileSync(bundleFile, out.outputFiles[0].text);
458
+ const mod = await import(`${pathToFileURL(bundleFile).href}?t=${Date.now()}`);
459
+ if (isAggregateShape(mod.default)) {
460
+ const repo = await loadRepoConfig(abs);
461
+ return { kind: "repo", configPath: abs, appRoot: path3.dirname(abs), repo };
462
+ }
463
+ return finishAppShape(mod.default, { hostPages: mod.hostPages }, abs, path3.dirname(abs), fs3, path3);
464
+ } finally {
465
+ fs3.rmSync(tmpDir, { recursive: true, force: true });
466
+ }
467
+ }
468
+ function finishAppShape(def, named, configPath, appRoot, fs3, path3) {
469
+ if (def === void 0 || def === null) {
470
+ appConfigError(
471
+ "fulgurjs.config.ts \u6CA1\u6709\u53EF\u7528\u7684\u9ED8\u8BA4\u5BFC\u51FA",
472
+ "\u5355\u9879\u76EE\u5951\u7EA6\uFF1A\u9ED8\u8BA4\u5BFC\u51FA\u76F4\u63A5\u662F federation() \u9009\u9879\u5BF9\u8C61\uFF08satisfies FederationOptions\uFF09",
473
+ `export default { name: 'my-app', ... } satisfies FederationOptions`
474
+ );
475
+ }
476
+ if (typeof def !== "object" || Array.isArray(def)) {
477
+ fieldError("\u9ED8\u8BA4\u5BFC\u51FA", def, "\u5BF9\u8C61\uFF08federation() \u9009\u9879\uFF09", `export default { name: 'my-app', exposes: { /* \u2026 */ } } satisfies FederationOptions`);
478
+ }
479
+ const options = def;
480
+ validateAppOptions(options);
481
+ validatePathsInsideApp(options, appRoot, fs3, path3);
482
+ const hostPages = named.hostPages !== void 0 ? validateHostPages(named.hostPages) : void 0;
483
+ return {
484
+ kind: "app",
485
+ configPath,
486
+ appRoot,
487
+ options,
488
+ hostPages
489
+ };
490
+ }
491
+ function roleOfOptions(options) {
492
+ const hasRemotes = Object.keys(options.remotes ?? {}).length > 0;
493
+ const hasProvides = Object.keys(options.exposes ?? {}).length > 0 || !!options.setup;
494
+ if (hasRemotes && hasProvides) return "dual";
495
+ return hasRemotes ? "host" : "remote";
496
+ }
497
+ var init_app_config = __esm({
498
+ "src/app-config.ts"() {
499
+ "use strict";
500
+ init_config();
501
+ }
502
+ });
503
+
290
504
  // src/init.ts
291
505
  var init_exports = {};
292
506
  __export(init_exports, {
@@ -329,12 +543,23 @@ ${Object.entries(app.shared).map(([k, v]) => {
329
543
  return ` '${k}': { ${opts.join(", ")} },`;
330
544
  }).join("\n")}
331
545
  },
332
- ` : ` ${DEFAULT_SHARED_BLOCK},
333
- `;
546
+ ` : "";
334
547
  return `federation({
335
548
  name: '${app.name}',
336
549
  ${remotesBlock}${exposesBlock}${setupBlock}${sharedBlock}})`;
337
550
  }
551
+ function viteSnippetForAppMode(appRoot) {
552
+ return `// ${appRoot}/vite.config.ts\uFF08\u8054\u90A6\u76F8\u5173\u884C\uFF1B\u5176\u4F59 Vite \u914D\u7F6E\u539F\u6837\u4FDD\u7559\uFF09
553
+ import federation from '@fulgurjs/federation'
554
+ import fulgurjsConfig from './fulgurjs.config'
555
+
556
+ export default defineConfig({
557
+ plugins: [
558
+ // ...\u539F\u6709\u63D2\u4EF6,
559
+ federation(fulgurjsConfig),
560
+ ],
561
+ })`;
562
+ }
338
563
  function nginxSnippetFor(cfg) {
339
564
  const listen = cfg.deploy?.listen ?? 8080;
340
565
  const webRoot = cfg.deploy?.webRoot ?? "/var/www/your-site";
@@ -360,7 +585,7 @@ function nginxSnippetFor(cfg) {
360
585
  }`;
361
586
  }
362
587
  ).join("\n\n");
363
- return `# \u8054\u90A6\u7AD9\u70B9\u6A21\u677F\uFF08\u8981\u70B9\uFF1AremoteEntry/manifest/index.html \u5FC5\u987B no-cache\u2014\u2014
588
+ return `# \u8054\u90A6\u7AD9\u70B9\u6837\u677F\uFF08\u6253\u5370\u6837\u677F\uFF0C\u4E0D\u5199\u6587\u4EF6\u3002\u8981\u70B9\uFF1AremoteEntry/manifest/index.html \u5FC5\u987B no-cache\u2014\u2014
364
589
  # \u6587\u4EF6\u540D\u56FA\u5B9A\u800C\u5185\u5BB9\u6BCF\u6B21\u6784\u5EFA\u53D8\u5316\uFF0Cimmutable \u957F\u7F13\u5B58\u4F1A\u8BA9\u6D4F\u89C8\u5668\u6301\u65E7\u5165\u53E3\u3001\u91CD\u90E8\u7F72\u540E\u5168 404\uFF1B
365
590
  # \u5E26 hash \u7684 assets \u624D\u914D\u957F\u7F13\u5B58\uFF1B\u8DE8\u6E90\u90E8\u7F72\u65F6\u4FDD\u7559 Access-Control-Allow-Origin\uFF09
366
591
  server {
@@ -393,99 +618,123 @@ function appSummary(app) {
393
618
  }
394
619
  return lines.join("\n");
395
620
  }
621
+ function appModeSummary(loaded) {
622
+ const options = loaded.options;
623
+ const roleText = { host: "\u5BBF\u4E3B", remote: "\u8FDC\u7A0B", dual: "\u5BBF\u4E3B+\u8FDC\u7A0B\uFF08\u53CC\u89D2\u8272\uFF09" }[Object.keys(options.remotes ?? {}).length > 0 && (Object.keys(options.exposes ?? {}).length > 0 || !!options.setup) ? "dual" : Object.keys(options.remotes ?? {}).length > 0 ? "host" : "remote"];
624
+ const lines = [];
625
+ lines.push(` ${options.name}\uFF08${roleText}\uFF0C\u5355\u9879\u76EE\u914D\u7F6E\uFF0C\u76EE\u5F55 ${loaded.appRoot}\uFF1Bbase/dev \u7AEF\u53E3\u7531 vite.config.ts \u7BA1\u7406\uFF09`);
626
+ for (const [k, v] of Object.entries(options.remotes ?? {})) {
627
+ const cfg = typeof v === "string" ? { dev: v, prod: v } : v;
628
+ lines.push(` \u6D88\u8D39\u8FDC\u7A0B ${k} \u2192 dev ${cfg.dev ?? ""} / prod ${cfg.prod ?? ""}`);
629
+ }
630
+ if (options.exposes) {
631
+ lines.push(` exposes\uFF08${Object.keys(options.exposes).length}\uFF09\uFF1A${Object.keys(options.exposes).join("\u3001")}`);
632
+ }
633
+ if (options.setup) {
634
+ lines.push(` setup\uFF08\u53EF\u9009\u8FDC\u7A0B\u521D\u59CB\u5316\uFF09\uFF1A${options.setup}`);
635
+ }
636
+ if (loaded.hostPages) {
637
+ lines.push(` \u9875\u9762\u8DEF\u7531\u8868\uFF08hostPages \u5177\u540D\u5BFC\u51FA\uFF09\uFF1A${loaded.hostPages.pages.length} \u6761`);
638
+ } else {
639
+ lines.push(" \u9875\u9762\u8DEF\u7531\u8868\uFF1A\uFF08\u65E0 hostPages \u5BFC\u51FA\u2014\u2014\u7EAF\u8FDC\u7A0B\u5E94\u7528\u65E0\u9700\uFF1B\u5BBF\u4E3B\u5EFA\u8BAE\u63D0\u4F9B\uFF09");
640
+ }
641
+ return lines;
642
+ }
396
643
  async function inspectConfig(configPath) {
397
- const cfg = await loadRepoConfig(configPath);
644
+ const loaded = await loadAppConfig(configPath);
398
645
  const out = [];
399
- out.push(`${MARK} \u914D\u7F6E\u6821\u9A8C\u901A\u8FC7\uFF1Aroot=${cfg.root}\uFF0Capps=${cfg.apps.length}`);
646
+ if (loaded.kind === "app") {
647
+ out.push(`${MARK} \u914D\u7F6E\u6821\u9A8C\u901A\u8FC7\uFF1A\u5355\u9879\u76EE\u5F62\u6001\uFF08\u9ED8\u8BA4\u5BFC\u51FA = federation() \u9009\u9879\uFF09`);
648
+ out.push("\u5E94\u7528\u6458\u8981\uFF1A");
649
+ out.push(...appModeSummary(loaded));
650
+ out.push("\n\u2500\u2500 vite.config.ts plugins \u63A5\u5165\u5757\uFF08\u8FD9\u5C31\u662F\u5168\u90E8\u8054\u90A6\u63A5\u5165\u4EE3\u7801\uFF09 \u2500\u2500");
651
+ out.push(viteSnippetForAppMode(loaded.appRoot));
652
+ out.push("\n\u2500\u2500 \u63A5\u5165\u6838\u5BF9\u6E05\u5355\uFF08\u8054\u90A6\u901A\u7528\u9879\uFF0C\u4E0E\u5177\u4F53\u9879\u76EE\u65E0\u5173\uFF09 \u2500\u2500");
653
+ out.push(...COMMON_CHECKLIST);
654
+ return out.join("\n");
655
+ }
656
+ const cfg = loaded.repo ?? await loadRepoConfig(loaded.configPath);
657
+ out.push(`${MARK} \u914D\u7F6E\u6821\u9A8C\u901A\u8FC7\uFF1Aroot=${cfg.root}\uFF0Capps=${cfg.apps.length}\u3010\u65E7\u805A\u5408\u914D\u7F6E\u5F62\u6001\uFF08root+apps[]\uFF09\uFF0C\u517C\u5BB9\u671F\u4FDD\u7559\uFF1B\u65B0\u9879\u76EE\u63A8\u8350\u6BCF\u9879\u76EE\u4E00\u4EFD\u5355\u9879\u76EE\u914D\u7F6E\uFF0C\u89C1 README\u3011`);
400
658
  out.push("\u5E94\u7528\u6458\u8981\uFF1A");
401
659
  for (const app of cfg.apps) out.push(appSummary(app));
402
- out.push("\n\u2500\u2500 \u5404\u5E94\u7528 vite.config.ts plugins \u7C98\u8D34\u5757 \u2500\u2500");
660
+ out.push("\n\u2500\u2500 \u5404\u5E94\u7528 vite.config.ts plugins \u7C98\u8D34\u5757\uFF08\u65E7\u805A\u5408\u5F62\u6001\u9700\u7ECF loadRepoConfig/federationOptionsForApp \u8F6C\u6362\uFF09 \u2500\u2500");
403
661
  for (const app of cfg.apps) {
404
662
  out.push(`
405
663
  # ${app.path}\uFF08${app.name}\uFF09`);
406
664
  out.push(viteSnippetFor(app));
407
665
  }
408
- out.push("\n\u2500\u2500 NGINX \u7AD9\u70B9\u6A21\u677F \u2500\u2500");
666
+ out.push("\n\u2500\u2500 NGINX \u7AD9\u70B9\u6837\u677F\uFF08\u6253\u5370\u6837\u677F\uFF0C\u4E0D\u5199\u6587\u4EF6\uFF09 \u2500\u2500");
409
667
  out.push(nginxSnippetFor(cfg));
410
668
  out.push("\n\u2500\u2500 \u63A5\u5165\u6838\u5BF9\u6E05\u5355\uFF08\u8054\u90A6\u901A\u7528\u9879\uFF0C\u4E0E\u5177\u4F53\u9879\u76EE\u65E0\u5173\uFF09 \u2500\u2500");
411
- out.push("1. \u5BBF\u4E3B\u4E0E\u8FDC\u7A0B\u90FD\u5B89\u88C5\u4F9D\u8D56\uFF1Apnpm add @fulgurjs/federation");
412
- out.push("2. expose \u4E00\u5F8B\u6307\u5411\u72EC\u7ACB\u9875\uFF08\u9875\u9762\u4ECE\u8DEF\u7531\u53D6\u53C2\uFF09\uFF1B\u7EC4\u4EF6\u9700\u8981\u5FC5\u586B props \u65F6\u7ED9\u9ED8\u8BA4\u503C\uFF08BLD-003\uFF09");
413
- out.push("3. shared \u91CC vue / vue-router / pinia \u5EFA\u8BAE singleton: true\u2014\u2014\u8DE8\u5E94\u7528\u5FC5\u987B\u540C\u5B9E\u4F8B\uFF08\u5168\u5C40\u54CD\u5E94\u6027\u3001getActivePinia\u3001\u8DEF\u7531\u6CE8\u5165\uFF09");
414
- out.push("4. \u8FDC\u7A0B\u9700\u8981\u542F\u52A8\u671F\u521D\u59CB\u5316\uFF08\u5168\u5C40\u7EC4\u4EF6/\u6837\u5F0F/locale \u7B49\uFF09\u65F6\uFF0C\u5728 federation({ setup }) \u58F0\u660E\u5165\u53E3\u6587\u4EF6\uFF1A");
415
- out.push(" \u9ED8\u8BA4\u5BFC\u51FA setup(context) \u5E94\u7528\u7EA7\u6267\u884C\u4E00\u6B21\uFF08\u5BB9\u5668\u9996\u6B21\u88AB\u52A0\u8F7D\u4E1A\u52A1\u6A21\u5757\u524D\uFF09\uFF1B\u53EF\u9009\u5177\u540D\u5BFC\u51FA onSession(context)");
416
- out.push(" \u6309\u5BBF\u4E3B sessionKey \u53BB\u91CD\u6267\u884C\uFF08\u6362\u8D26\u53F7/\u91CD\u767B\u81EA\u52A8\u91CD\u8DD1\uFF09\u3002\u5BBF\u4E3B\u65E0\u9700\u518D\u624B\u5199\u300CloadRemote \u542F\u52A8\u5668\u5E76\u8C03\u7528\u300D\uFF1B");
417
- out.push(" \u8DE8\u5E94\u7528\u4F20\u503C\u7EDF\u4E00\u8D70 @fulgurjs/federation/runtime \u7684 context \u51FD\u6570\uFF1A\u5BBF\u4E3B provideAppContext \u5199\u5165");
418
- out.push(" \uFF08\u9000\u51FA\u65F6 clearAppContext \u6E05\u7406\uFF09\uFF0C\u8FDC\u7A0B setup/onSession \u7528 getAppContext / requireAppContext \u6D88\u8D39");
419
- out.push("5. \u5E94\u7528\u4EE3\u7801\u552F\u4E00 API \u5165\u53E3\uFF1Aimport { loadRemote, provideAppContext, getAppContext, clearAppContext, definePages, createHostPages, remoteSchema, remoteComponent } from '@fulgurjs/federation/runtime'");
420
- out.push('6. dev \u51B7\u542F\u52A8\u9996\u8F6E 30~60s \u6709\u9884\u6784\u5EFA\u7A97\u53E3\uFF08\u77AC\u65F6 504/"ce"\uFF0CDEV-010\uFF09\uFF1A\u5148\u771F\u5B9E\u6253\u5F00\u9875\u9762\u9884\u70ED\u518D\u505A\u65AD\u8A00');
421
- out.push("7. \u90E8\u7F72\u540E\u4F53\u68C0\uFF1Afulgurjs doctor --base <URL> --apps <\u5E94\u7528...>\uFF08\u7F13\u5B58\u5934/\u8D44\u6E90\u5F62\u6001/CORS/chunk \u53EF\u8FBE/\u7248\u672C skew\uFF09\uFF1B");
422
- out.push(" \u914D\u7F6E\u89E3\u91CA\uFF1Afulgurjs explain --config fulgurjs.config.ts --app <\u5E94\u7528>\uFF1B\u9875\u9762\u5951\u7EA6\u6838\u5BF9\uFF1Afulgurjs check-pages --config <...> --app <\u5BBF\u4E3B>");
423
- out.push("8. \u90E8\u7F72\u8BED\u4E49\uFF1AremoteEntry/manifest/index.html \u5FC5\u987B no-cache\uFF08\u4E25\u7981 immutable\uFF09\uFF1B\u5E26 hash \u7684 assets \u957F\u7F13\u5B58");
669
+ out.push(...COMMON_CHECKLIST);
424
670
  return out.join("\n");
425
671
  }
426
- var MARK, STARTER_CONFIG, DEFAULT_SHARED_BLOCK;
672
+ var MARK, STARTER_CONFIG, COMMON_CHECKLIST;
427
673
  var init_init = __esm({
428
674
  "src/init.ts"() {
429
675
  "use strict";
676
+ init_app_config();
430
677
  init_config();
431
678
  MARK = "[fulgurjs:init]";
432
- STARTER_CONFIG = `// fulgurjs.config.ts \u2014\u2014 @fulgurjs/federation \u63A5\u5165\u914D\u7F6E\uFF08\u5355\u6587\u4EF6\u9A71\u52A8\uFF0C\u53EF\u5165\u5E93\u3001\u53EF\u590D\u8DD1\uFF09
433
- // \u7528\u6CD5\uFF1Anpx fulgurjs init --config fulgurjs.config.ts \u6821\u9A8C\u914D\u7F6E\u5E76\u8F93\u51FA\u53EF\u7C98\u8D34\u6837\u677F\u4E0E\u6838\u5BF9\u6E05\u5355
434
- // npx fulgurjs doctor --base http://<\u7AD9\u70B9> --apps <\u5E94\u7528\u76EE\u5F55\u540D...> \u90E8\u7F72\u4F53\u68C0
435
- import { defineRepoConfig } from '@fulgurjs/federation/config'
679
+ STARTER_CONFIG = `// fulgurjs.config.ts \u2014\u2014 @fulgurjs/federation \u5355\u9879\u76EE\u63A5\u5165\u914D\u7F6E\uFF08\u4E00\u9879\u76EE\u4E00\u4EFD\uFF0C\u53EF\u5165\u5E93\u3001\u53EF\u590D\u8DD1\uFF09
680
+ //
681
+ // Vite \u63A5\u5165\uFF08\u672C\u9879\u76EE vite.config.ts \u53EA\u9700\u4E24\u884C\u8054\u90A6\u76F8\u5173\u4EE3\u7801\uFF09\uFF1A
682
+ // import federation from '@fulgurjs/federation'
683
+ // import fulgurjsConfig from './fulgurjs.config'
684
+ // // plugins: [ ...\u539F\u6709\u63D2\u4EF6, federation(fulgurjsConfig) ]
685
+ //
686
+ // CLI\uFF08\u7EAF\u672C\u5730\uFF0C\u65E0\u7F51\u7EDC\uFF09\uFF1A
687
+ // npx fulgurjs explain # \u89E3\u91CA\u672C\u5E94\u7528\u6709\u6548\u5F62\u6001\u4E0E\u52A0\u8F7D\u94FE
688
+ // npx fulgurjs check-pages # \u5BBF\u4E3B\u9875\u9762\u8868 \u2194 \u8FDC\u7A0B manifest \u5951\u7EA6\u6838\u5BF9
689
+ // npx fulgurjs doctor --base http://<\u7AD9\u70B9> --apps <\u5BB9\u5668\u540D> # \u90E8\u7F72\u4F53\u68C0
690
+ import type { FederationOptions } from '@fulgurjs/federation'
436
691
 
437
- export default defineRepoConfig({
438
- // \u5DE5\u7A0B\u6839\u76EE\u5F55\uFF08monorepo \u6839\u6216\u5355\u5E94\u7528\u4ED3\u5E93\u6839\uFF09
439
- root: process.cwd(),
440
- apps: [
441
- // \u2500\u2500 \u5BBF\u4E3B\uFF1A\u6D88\u8D39\u8FDC\u7A0B\u9875\u9762/\u7EC4\u4EF6\uFF08\u4E5F\u53EF\u518D expose \u81EA\u5DF1\u7684\u7EC4\u4EF6\u7ED9\u66F4\u9AD8\u5C42\u6D88\u8D39\uFF09\u2500\u2500
442
- {
443
- path: 'apps/host', // \u76F8\u5BF9 root \u7684\u5E94\u7528\u76EE\u5F55
444
- name: 'host-app', // \u8054\u90A6\u5BB9\u5668\u540D\uFF08\u8FDC\u7A0B\u5F15\u7528\u5B83\u65F6\u7528\u8FD9\u4E2A\u540D\uFF09
445
- port: 5173, // dev \u7AEF\u53E3
446
- base: '/', // \u90E8\u7F72/dev \u7684 URL \u524D\u7F00
447
- host: {
448
- // \u8DEF\u7531\u524D\u7F00 \u2192 \u8FDC\u7A0B\u540D\uFF1A\u5BBF\u4E3B\u628A /remote-a/** \u7684\u9875\u9762\u89E3\u6790\u5230\u8FDC\u7A0B remote-a
449
- remotePrefixes: { '/remote-a/': 'remote-a' },
450
- // \u8FDC\u7A0B\u5730\u5740\uFF1A\u88F8 URL\uFF08\u5BF9\u8C61\u5F62\u5F0F\u4E0D\u652F\u6301 name@ \u524D\u7F00\uFF0C\u89C1\u9519\u8BEF\u7801 CFG-007\uFF09
451
- remotes: {
452
- 'remote-a': { dev: 'http://localhost:5174/remote-a', prod: '/remote-a' },
453
- },
454
- // \u9875\u9762\u8DEF\u7531\u8868\uFF1A\u5BBF\u4E3B\u8DEF\u5F84 \u2192 \u8FDC\u7A0B exposes \u952E\uFF08\u9ED8\u8BA4\u63A8\u5BFC pages/<\u53BB\u524D\u7F00\u53BB\u53C2\u6570\u6BB5>\uFF1B
455
- // \u5E26\u53C2\u8DEF\u7531\u6216\u975E\u9ED8\u8BA4\u952E\u7528 spec \u663E\u5F0F\u8986\u76D6\uFF09
456
- pages: [
457
- { route: '/remote-a/home', name: 'RemoteAHome', title: '\u9996\u9875' },
458
- { route: '/remote-a/detail/:id', name: 'RemoteADetail', spec: 'pages/remote-a/detail', title: '\u8BE6\u60C5' },
459
- ],
460
- },
461
- },
462
- // \u2500\u2500 \u8FDC\u7A0B\uFF1Aexpose \u72EC\u7ACB\u9875\uFF08\u9875\u9762\u81EA\u5DF1\u4ECE\u8DEF\u7531\u53D6\u53C2\uFF0C\u4E0D\u4F9D\u8D56\u7236\u7EC4\u4EF6\u4F20\u5FC5\u586B props\uFF0C\u89C1 BLD-003\uFF09\u2500\u2500
463
- {
464
- path: 'apps/remote-a',
465
- name: 'remote-a',
466
- port: 5174,
467
- base: '/remote-a',
468
- remote: {
469
- exposes: {
470
- './pages/remote-a/home': './src/views/Home.vue',
471
- './pages/remote-a/detail': './src/views/Detail.vue',
472
- },
473
- // \u53EF\u9009\uFF1A\u8FDC\u7A0B\u9700\u8981\u542F\u52A8\u671F\u521D\u59CB\u5316\uFF08\u5168\u5C40\u7EC4\u4EF6/\u6837\u5F0F/locale\uFF09\u65F6\u58F0\u660E\u5165\u53E3\u6587\u4EF6\u2014\u2014
474
- // \u9ED8\u8BA4\u5BFC\u51FA setup(context) \u5E94\u7528\u7EA7\u6267\u884C\u4E00\u6B21\uFF1B\u53EF\u9009\u5177\u540D\u5BFC\u51FA onSession(context)
475
- // \u6309\u5BBF\u4E3B sessionKey \u53BB\u91CD\u6267\u884C\u3002\u7F3A\u7701 = \u65E0\u521D\u59CB\u5316\u884C\u4E3A\uFF08\u666E\u901A expose \u8BED\u4E49\u4E0D\u53D8\uFF09
476
- // setup: './src/fulgurjs/setup.ts',
477
- },
478
- },
479
- ],
480
- // \u90E8\u7F72\u4FE1\u606F\uFF08\u53EF\u9009\uFF09\uFF1A\u4EC5\u4F9B init \u8F93\u51FA NGINX \u6837\u677F\u65F6\u4F7F\u7528
481
- deploy: { webRoot: '/var/www/your-site', listen: 8080 },
482
- })
692
+ export default {
693
+ // \u8054\u90A6\u5BB9\u5668\u540D\uFF08\u8FDC\u7A0B\u5F15\u7528\u5B83\u65F6\u7528\u8FD9\u4E2A\u540D\uFF1B\u540C\u4E00\u9875\u9762\u5185\u552F\u4E00\uFF09
694
+ name: 'my-app',
695
+ // \u672C\u5E94\u7528\u5BF9\u5916\u66B4\u9732\u7684\u6A21\u5757\uFF1A'./\u952E' \u2192 \u76F8\u5BF9\u672C\u9879\u76EE\u6839\u7684\u6E90\u6587\u4EF6\u8DEF\u5F84
696
+ exposes: {
697
+ './pages/home': './src/views/Home.vue',
698
+ },
699
+ // \u672C\u5E94\u7528\u6D88\u8D39\u7684\u8FDC\u7A0B\uFF08\u952E = import \u524D\u7F00\uFF1Bdev/prod \u5730\u5740\u4E8C\u6BB5\u5F0F\uFF0C\u5355\u5730\u5740\u5B57\u7B26\u4E32\u4E5F\u884C\uFF09
700
+ remotes: {
701
+ 'remote-a': { dev: 'http://localhost:5174/remote-a', prod: '/remote-a' },
702
+ },
703
+ // \u53EF\u9009\uFF1A\u8FDC\u7A0B\u9700\u8981\u542F\u52A8\u671F\u521D\u59CB\u5316\uFF08\u5168\u5C40\u6837\u5F0F/\u7EC4\u4EF6/locale\uFF09\u65F6\u58F0\u660E\u5165\u53E3\u6587\u4EF6\u2014\u2014
704
+ // \u9ED8\u8BA4\u5BFC\u51FA setup(context) \u5E94\u7528\u7EA7\u6267\u884C\u4E00\u6B21\uFF1B\u53EF\u9009\u5177\u540D\u5BFC\u51FA onSession(context)
705
+ // \u6309\u5BBF\u4E3B sessionKey \u53BB\u91CD\u6267\u884C\u3002\u7F3A\u7701 = \u65E0\u521D\u59CB\u5316\u884C\u4E3A\uFF08\u666E\u901A expose \u8BED\u4E49\u4E0D\u53D8\uFF09
706
+ // setup: './src/fulgurjs/setup.ts',
707
+ shared: {
708
+ vue: { singleton: true, requiredVersion: '^3.4.0' },
709
+ 'vue-router': { singleton: true, requiredVersion: '^4.4.5' },
710
+ pinia: { singleton: true, requiredVersion: '^2.1.7' },
711
+ },
712
+ } satisfies FederationOptions
713
+
714
+ // \u2500\u2500 \u4EE5\u4E0B\u4EC5\u4F9B CLI explain/check-pages \u8BFB\u53D6\uFF08\u4E0D\u662F federation() \u7684\u53C2\u6570\uFF09\u2500\u2500
715
+ // \u5BBF\u4E3B\u5E94\u7528\uFF1A\u628A\u9875\u9762\u8DEF\u7531\u8868\u4EE5\u5177\u540D\u5BFC\u51FA hostPages \u63D0\u4F9B\u7ED9 CLI\uFF0C\u4E0E\u8FD0\u884C\u65F6 createHostPages
716
+ // \u6D88\u8D39\u540C\u4E00\u4EFD\u6570\u636E\u6A21\u5757\uFF08\u552F\u4E00\u624B\u5DE5\u7EF4\u62A4\u4F4D\u7F6E\uFF1B\u7EAF\u8FDC\u7A0B\u5E94\u7528\u5220\u9664\u672C\u6BB5\u5373\u53EF\uFF09\u3002
717
+ // \u7EAF\u6570\u636E\u6A21\u5757\u793A\u4F8B src/fulgurjs/host/pages.data.ts\uFF1A
718
+ // export const remotePrefixes = { '/remote-a/': 'remote-a' }
719
+ // export const pages = [{ route: '/remote-a/home', name: 'Home', title: '\u9996\u9875' }]
720
+ // import { pages, remotePrefixes } from './src/fulgurjs/host/pages.data'
721
+ // export const hostPages = { pages, remotePrefixes }
483
722
  `;
484
- DEFAULT_SHARED_BLOCK = `shared: {
485
- vue: { singleton: true, requiredVersion: '^3.4.0' },
486
- 'vue-router': { singleton: true, requiredVersion: '^4.4.5' },
487
- pinia: { singleton: true, requiredVersion: '^2.1.7' },
488
- }`;
723
+ COMMON_CHECKLIST = [
724
+ "1. \u5B89\u88C5\u4F9D\u8D56\uFF1Anpm/pnpm add @fulgurjs/federation",
725
+ "2. expose \u4E00\u5F8B\u6307\u5411\u72EC\u7ACB\u9875\uFF08\u9875\u9762\u4ECE\u8DEF\u7531\u53D6\u53C2\uFF09\uFF1B\u7EC4\u4EF6\u9700\u8981\u5FC5\u586B props \u65F6\u7ED9\u9ED8\u8BA4\u503C\uFF08BLD-003\uFF09",
726
+ "3. shared \u91CC vue / vue-router / pinia \u5EFA\u8BAE singleton: true\u2014\u2014\u8DE8\u5E94\u7528\u5FC5\u987B\u540C\u5B9E\u4F8B\uFF08\u5168\u5C40\u54CD\u5E94\u6027\u3001getActivePinia\u3001\u8DEF\u7531\u6CE8\u5165\uFF09",
727
+ "4. \u8FDC\u7A0B\u9700\u8981\u542F\u52A8\u671F\u521D\u59CB\u5316\uFF08\u5168\u5C40\u7EC4\u4EF6/\u6837\u5F0F/locale \u7B49\uFF09\u65F6\uFF0C\u5728 fulgurjs.config.ts \u7684 setup \u58F0\u660E\u5165\u53E3\u6587\u4EF6\uFF1A",
728
+ " \u9ED8\u8BA4\u5BFC\u51FA setup(context) \u5E94\u7528\u7EA7\u6267\u884C\u4E00\u6B21\uFF08\u5BB9\u5668\u9996\u6B21\u88AB\u52A0\u8F7D\u4E1A\u52A1\u6A21\u5757\u524D\uFF09\uFF1B\u53EF\u9009\u5177\u540D\u5BFC\u51FA onSession(context)",
729
+ " \u6309\u5BBF\u4E3B sessionKey \u53BB\u91CD\u6267\u884C\uFF08\u6362\u8D26\u53F7/\u91CD\u767B\u81EA\u52A8\u91CD\u8DD1\uFF09\u3002\u5BBF\u4E3B\u65E0\u9700\u518D\u624B\u5199\u300CloadRemote \u542F\u52A8\u5668\u5E76\u8C03\u7528\u300D\uFF1B",
730
+ " \u8DE8\u5E94\u7528\u4F20\u503C\u7EDF\u4E00\u8D70 @fulgurjs/federation/runtime \u7684 context \u51FD\u6570\uFF1A\u5BBF\u4E3B provideAppContext \u5199\u5165",
731
+ " \uFF08\u9000\u51FA\u65F6 clearAppContext \u6E05\u7406\uFF09\uFF0C\u8FDC\u7A0B setup/onSession \u7528 getAppContext / requireAppContext \u6D88\u8D39",
732
+ "5. \u5E94\u7528\u4EE3\u7801\u552F\u4E00 API \u5165\u53E3\uFF1Aimport { loadRemote, provideAppContext, getAppContext, clearAppContext, definePages, createHostPages, remoteSchema, remoteComponent } from '@fulgurjs/federation/runtime'",
733
+ '6. dev \u51B7\u542F\u52A8\u9996\u8F6E 30~60s \u6709\u9884\u6784\u5EFA\u7A97\u53E3\uFF08\u77AC\u65F6 504/"ce"\uFF0CDEV-010\uFF09\uFF1A\u5148\u771F\u5B9E\u6253\u5F00\u9875\u9762\u9884\u70ED\u518D\u505A\u65AD\u8A00',
734
+ "7. \u914D\u7F6E\u89E3\u91CA\uFF1Afulgurjs explain\uFF08\u5355\u9879\u76EE\u5F62\u6001\u514D --app\uFF09\uFF1B\u9875\u9762\u5951\u7EA6\u6838\u5BF9\uFF1Afulgurjs check-pages\uFF08\u5BBF\u4E3B\u9879\u76EE\u8FD0\u884C\uFF0C--manifest/--site \u6307\u5B9A\u8FDC\u7A0B manifest \u6765\u6E90\uFF09\uFF1B",
735
+ " \u90E8\u7F72\u4F53\u68C0\uFF1Afulgurjs doctor --base <URL> --apps <\u5BB9\u5668\u540D...>",
736
+ "8. \u90E8\u7F72\u8BED\u4E49\uFF1AremoteEntry/manifest/index.html \u5FC5\u987B no-cache\uFF08\u4E25\u7981 immutable\uFF09\uFF1B\u5E26 hash \u7684 assets \u957F\u7F13\u5B58"
737
+ ];
489
738
  }
490
739
  });
491
740
 
@@ -633,6 +882,19 @@ function pageMappings(app) {
633
882
  return null;
634
883
  };
635
884
  const derive = app.host?.deriveSpec ?? ((route) => route.split("/").filter(Boolean).slice(1).filter((s) => !s.startsWith(":")).join("/"));
885
+ return mappingsOf({ pages, remotePrefixes: app.host?.remotePrefixes ?? {}, deriveSpec: app.host?.deriveSpec }, derive);
886
+ }
887
+ function mappingsOf(hp, deriveFallback) {
888
+ const pages = hp.pages ?? [];
889
+ const prefixes = Object.entries(hp.remotePrefixes ?? {}).sort((a, b) => b[0].length - a[0].length);
890
+ const remoteOf = (route) => {
891
+ for (const [prefix, name] of prefixes) {
892
+ const p = prefix.endsWith("/") ? prefix : `${prefix}/`;
893
+ if (route.startsWith(p) || `${route}/` === p) return name;
894
+ }
895
+ return null;
896
+ };
897
+ const derive = hp.deriveSpec ?? deriveFallback;
636
898
  const out = [];
637
899
  for (const page of pages) {
638
900
  const remote = remoteOf(page.route);
@@ -642,8 +904,65 @@ function pageMappings(app) {
642
904
  }
643
905
  return out;
644
906
  }
645
- async function explainApp(configPath, appPathOrName) {
646
- const cfg = await loadRepoConfig(configPath);
907
+ function remotesOfOptions(options) {
908
+ return Object.entries(options.remotes ?? {}).map(([key, v]) => {
909
+ const cfg = typeof v === "string" ? { external: v } : v;
910
+ return { key, dev: String(cfg.dev ?? cfg.external ?? ""), prod: String(cfg.prod ?? cfg.external ?? "") };
911
+ });
912
+ }
913
+ function sharedOfOptions(options) {
914
+ const shared = options.shared;
915
+ if (!shared) return [];
916
+ if (Array.isArray(shared)) {
917
+ return shared.map((name) => ({ name, singleton: false, requiredVersion: false }));
918
+ }
919
+ return Object.entries(shared).map(([name, v]) => {
920
+ const hint = typeof v === "string" ? { requiredVersion: v } : v ?? {};
921
+ return {
922
+ name,
923
+ singleton: !!hint.singleton,
924
+ requiredVersion: hint.requiredVersion ?? false
925
+ };
926
+ });
927
+ }
928
+ function chainLines(options, role) {
929
+ const chain = [];
930
+ chain.push(role === "remote" ? "\u88AB\u5BBF\u4E3B\u6D88\u8D39\uFF1AremoteEntry/\u5171\u4EAB\u4F9D\u8D56\u52A0\u8F7D \u2192 \u5BBF\u4E3B\u9996\u6B21 loadRemote \u672C\u5E94\u7528\u6A21\u5757\u65F6\u6267\u884C\u53EF\u9009 setup/onSession \u2192 \u9875\u9762\u6A21\u5757\u5728\u5BBF\u4E3B\u5E03\u5C40\u5185\u6E32\u67D3" : "\u5BBF\u4E3B\u63D0\u4F9B AppContext\uFF08provideAppContext\uFF0C\u542B sessionKey\uFF09\u2192 \u52A0\u8F7D remoteEntry/\u5171\u4EAB\u4F9D\u8D56 \u2192 \u9996\u6B21 loadRemote \u6267\u884C\u8BE5\u8FDC\u7A0B\u53EF\u9009 setup/onSession \u2192 \u53D6\u5F97\u9875\u9762\u6A21\u5757 \u2192 \u5BBF\u4E3B\u5E03\u5C40\u6E32\u67D3");
931
+ if (options.setup) {
932
+ chain.push(`setup \u5165\u53E3 ${options.setup}\uFF1A\u9ED8\u8BA4\u5BFC\u51FA\u5E94\u7528\u7EA7\u6267\u884C\u4E00\u6B21\uFF1BonSession \u6309\u5BBF\u4E3B sessionKey \u53BB\u91CD\uFF08\u7F3A sessionKey \u65F6\u62A5 MFU-013\uFF09`);
933
+ } else {
934
+ chain.push("\u672C\u5E94\u7528\u672A\u914D\u7F6E setup\uFF1A\u65E0\u521D\u59CB\u5316\u751F\u547D\u5468\u671F\uFF0CloadRemote \u76F4\u63A5\u8FD4\u56DE\u6A21\u5757\uFF08\u666E\u901A expose \u8BED\u4E49\uFF09");
935
+ }
936
+ return chain;
937
+ }
938
+ function explainAppMode(loaded, appName) {
939
+ const options = loaded.options;
940
+ if (appName && appName !== options.name) {
941
+ throw new Error(
942
+ `[fulgurjs:explain] \u5355\u9879\u76EE\u914D\u7F6E\u53EA\u63CF\u8FF0\u672C\u5E94\u7528\uFF08name: "${options.name}"\uFF09\uFF0C\u4E0E --app "${appName}" \u4E0D\u4E00\u81F4
943
+ \u4FEE\u6CD5: \u53BB\u6389 --app\uFF0C\u6216\u6539\u4E3A --app ${options.name}`
944
+ );
945
+ }
946
+ const role = roleOfOptions(options);
947
+ const devSharedSelfExplicit = options.devSharedSelf !== void 0;
948
+ const devSharedSelfValue = devSharedSelfExplicit ? options.devSharedSelf === true : role !== "host";
949
+ const hp = loaded.hostPages;
950
+ return {
951
+ app: options.name,
952
+ role,
953
+ mode: "app",
954
+ path: loaded.appRoot,
955
+ remotes: remotesOfOptions(options),
956
+ exposes: Object.keys(options.exposes ?? {}),
957
+ setup: options.setup,
958
+ shared: sharedOfOptions(options),
959
+ devSharedSelf: { value: devSharedSelfValue, source: devSharedSelfExplicit ? "explicit" : "inferred" },
960
+ pages: hp ? mappingsOf(hp, DEFAULT_DERIVE) : [],
961
+ pagesSource: hp ? `fulgurjs.config.ts \u7684 hostPages \u5177\u540D\u5BFC\u51FA\uFF08${hp.pages.length} \u6761\uFF1B\u8FD0\u884C\u65F6\u771F\u6E90\u4E3A\u5E94\u7528\u5185\u540C\u4E00\u6570\u636E\u6A21\u5757\uFF09` : "\u672A\u627E\u5230 hostPages \u5177\u540D\u5BFC\u51FA\uFF08\u7EAF\u8FDC\u7A0B\u5E94\u7528\u65E0\u9700\u9875\u9762\u8868\uFF1B\u5BBF\u4E3B\u82E5\u8981\u8DD1 check-pages \u8BF7\u5728 fulgurjs.config.ts \u8865\u5177\u540D\u5BFC\u51FA hostPages\uFF09",
962
+ chain: chainLines(options, role)
963
+ };
964
+ }
965
+ function explainRepoMode(cfg, appPathOrName) {
647
966
  const app = cfg.apps.find((a) => a.path === appPathOrName || a.name === appPathOrName);
648
967
  if (!app) {
649
968
  throw new Error(
@@ -667,6 +986,7 @@ async function explainApp(configPath, appPathOrName) {
667
986
  return {
668
987
  app: app.name,
669
988
  role: roleOf(app),
989
+ mode: "repo",
670
990
  path: app.path,
671
991
  base: app.base,
672
992
  port: app.port,
@@ -679,13 +999,28 @@ async function explainApp(configPath, appPathOrName) {
679
999
  source: app.devSharedSelf === void 0 ? "inferred" : "explicit"
680
1000
  },
681
1001
  pages: pageMappings(app),
1002
+ pagesSource: app.host?.pages ? "\u4ED3\u5E93\u914D\u7F6E host.pages \u526F\u672C\uFF08\u517C\u5BB9\u5F62\u6001\uFF1B\u5355\u9879\u76EE\u5F62\u6001\u8BF7\u6539\u7528 hostPages \u5177\u540D\u5BFC\u51FA\uFF09" : "\u4ED3\u5E93\u914D\u7F6E\u672A\u63D0\u4F9B\u9875\u9762\u8868\u2014\u2014\u5E94\u7528\u4EE3\u7801\u9875\u9762\u8868\u4E3A\u8FD0\u884C\u65F6\u771F\u6E90",
682
1003
  chain
683
1004
  };
684
1005
  }
1006
+ async function explainApp(configPath, appName) {
1007
+ const loaded = await loadAppConfig(configPath);
1008
+ if (loaded.kind === "repo") {
1009
+ if (!appName) {
1010
+ throw new Error("[fulgurjs:explain] \u805A\u5408\u914D\u7F6E\u9700 --app <\u5E94\u7528\u76EE\u5F55\u540D\u6216\u5BB9\u5668\u540D>\uFF08\u5355\u9879\u76EE\u914D\u7F6E\u53EF\u7701\u7565\uFF09");
1011
+ }
1012
+ return explainRepoMode(loaded.repo, appName);
1013
+ }
1014
+ return explainAppMode(loaded, appName);
1015
+ }
685
1016
  function formatExplain(r) {
686
1017
  const roleText = r.role === "dual" ? "\u5BBF\u4E3B+\u8FDC\u7A0B\uFF08\u53CC\u89D2\u8272\uFF09" : r.role === "host" ? "\u5BBF\u4E3B" : "\u8FDC\u7A0B";
687
1018
  const L = [];
688
- L.push(`\u5E94\u7528 ${r.app}\uFF08${roleText}\uFF0C\u76EE\u5F55 ${r.path}\uFF0Cdev \u7AEF\u53E3 ${r.port}\uFF0Cbase ${r.base}\uFF09`);
1019
+ if (r.mode === "app") {
1020
+ L.push(`\u5E94\u7528 ${r.app}\uFF08${roleText}\uFF0C\u5355\u9879\u76EE\u914D\u7F6E\uFF0C\u76EE\u5F55 ${r.path}\uFF1Bbase/dev \u7AEF\u53E3\u7531 vite.config.ts \u7BA1\u7406\uFF09`);
1021
+ } else {
1022
+ L.push(`\u5E94\u7528 ${r.app}\uFF08${roleText}\uFF0C\u76EE\u5F55 ${r.path}\uFF0Cdev \u7AEF\u53E3 ${r.port}\uFF0Cbase ${r.base}\uFF09\u3010\u65E7\u805A\u5408\u914D\u7F6E\u5F62\u6001\uFF0C\u517C\u5BB9\u671F\u3011`);
1023
+ }
689
1024
  if (r.remotes.length) {
690
1025
  L.push("\u6D88\u8D39\u8FDC\u7A0B\uFF1A");
691
1026
  for (const x of r.remotes) L.push(` ${x.key} \u2192 dev ${x.dev} / prod ${x.prod}`);
@@ -702,41 +1037,147 @@ function formatExplain(r) {
702
1037
  }
703
1038
  L.push(`devSharedSelf\uFF1A${r.devSharedSelf.value}\uFF08\u6765\u6E90\uFF1A${r.devSharedSelf.source === "explicit" ? "\u663E\u5F0F\u914D\u7F6E" : "\u6309\u89D2\u8272\u63A8\u65AD\u2014\u2014\u63D0\u4F9B exposes/setup \u7684\u5E94\u7528\u4E3A true\uFF0C\u7EAF\u5BBF\u4E3B\u4E3A false"}\uFF09`);
704
1039
  if (r.pages.length) {
705
- L.push(`\u9875\u9762 spec \u6620\u5C04\uFF08${r.pages.length}\uFF09\uFF1A`);
1040
+ L.push(`\u9875\u9762 spec \u6620\u5C04\uFF08${r.pages.length}\uFF09\u2014\u2014\u6765\u6E90\uFF1A${r.pagesSource}\uFF1A`);
706
1041
  for (const p of r.pages) L.push(` ${p.route} \u2192 ${p.spec}${p.title ? `\uFF08${p.title}\uFF09` : ""}`);
707
1042
  } else {
708
- L.push("\u9875\u9762 spec \u6620\u5C04\uFF1A\uFF08\u4ED3\u5E93\u914D\u7F6E\u672A\u63D0\u4F9B\u9875\u9762\u8868\u2014\u2014\u5E94\u7528\u4EE3\u7801\u9875\u9762\u8868\u4E3A\u8FD0\u884C\u65F6\u771F\u6E90\uFF09");
1043
+ L.push(`\u9875\u9762 spec \u6620\u5C04\uFF1A\uFF08\u65E0\uFF09\u2014\u2014\u6765\u6E90\uFF1A${r.pagesSource}`);
709
1044
  }
710
1045
  L.push("\u52A0\u8F7D\u94FE\uFF1A");
711
1046
  for (const c of r.chain) L.push(` \xB7 ${c}`);
712
1047
  return L.join("\n");
713
1048
  }
714
- async function fetchRemoteManifest(cfg, app, opts) {
1049
+ function manifestFromFile(p) {
1050
+ try {
1051
+ const parsed = parseManifest(JSON.parse(fs2.readFileSync(p, "utf8")));
1052
+ if (parsed.manifest && !parsed.unsupportedVersion) return parsed.manifest;
1053
+ } catch {
1054
+ }
1055
+ return void 0;
1056
+ }
1057
+ async function manifestFromUrl(url) {
1058
+ try {
1059
+ const res = await fetch(url, { signal: AbortSignal.timeout(5e3) });
1060
+ if (res.ok) {
1061
+ const parsed = parseManifest(await res.json());
1062
+ if (parsed.manifest && !parsed.unsupportedVersion) return parsed.manifest;
1063
+ }
1064
+ } catch {
1065
+ }
1066
+ return void 0;
1067
+ }
1068
+ async function fetchRemoteManifestRepo(cfg, app, opts) {
1069
+ const explicit = opts.manifests?.[app.name] ?? opts.manifests?.[app.path];
1070
+ if (explicit) {
1071
+ const fromUrl = /^https?:\/\//.test(explicit);
1072
+ const got = fromUrl ? await manifestFromUrl(explicit) : manifestFromFile(explicit);
1073
+ if (got) return { manifest: got, from: explicit };
1074
+ return void 0;
1075
+ }
715
1076
  const candidates = [app.deployDir || app.base.replace(/^\/|\/$/g, ""), "dist"];
716
1077
  for (const dir of candidates) {
717
1078
  const p = path2.join(cfg.root, app.path, dir, "fulgurjs-manifest.json");
718
- try {
719
- const parsed = parseManifest(JSON.parse(fs2.readFileSync(p, "utf8")));
720
- if (parsed.manifest && !parsed.unsupportedVersion) return { manifest: parsed.manifest, from: p };
721
- } catch {
722
- }
1079
+ const got = manifestFromFile(p);
1080
+ if (got) return { manifest: got, from: p };
723
1081
  }
724
1082
  if (opts.site) {
725
1083
  const base = app.base === "/" ? "" : app.base.replace(/\/$/, "");
726
1084
  const url = `${opts.site.replace(/\/$/, "")}${base}/fulgurjs-manifest.json`;
727
- try {
728
- const res = await fetch(url, { signal: AbortSignal.timeout(5e3) });
729
- if (res.ok) {
730
- const parsed = parseManifest(await res.json());
731
- if (parsed.manifest && !parsed.unsupportedVersion) return { manifest: parsed.manifest, from: url };
732
- }
733
- } catch {
734
- }
1085
+ const got = await manifestFromUrl(url);
1086
+ if (got) return { manifest: got, from: url };
735
1087
  }
736
1088
  return void 0;
737
1089
  }
738
- async function checkPages(configPath, appPathOrName, opts = {}) {
739
- const cfg = await loadRepoConfig(configPath);
1090
+ async function fetchRemoteManifestApp(loaded, options, remoteName, opts) {
1091
+ const explicit = opts.manifests?.[remoteName];
1092
+ if (explicit) {
1093
+ const fromUrl = /^https?:\/\//.test(explicit);
1094
+ const got = fromUrl ? await manifestFromUrl(explicit) : manifestFromFile(explicit);
1095
+ if (got) return { manifest: got, from: explicit };
1096
+ return void 0;
1097
+ }
1098
+ const remoteCfg = (options.remotes ?? {})[remoteName];
1099
+ if (!remoteCfg) return void 0;
1100
+ const cfg = typeof remoteCfg === "string" ? { external: remoteCfg } : remoteCfg;
1101
+ const prodBase = cfg.prod ?? cfg.external ?? "";
1102
+ if (!prodBase) return void 0;
1103
+ if (/^https?:\/\//.test(prodBase)) {
1104
+ const url = `${prodBase.replace(/\/$/, "")}/fulgurjs-manifest.json`;
1105
+ const got = await manifestFromUrl(url);
1106
+ if (got) return { manifest: got, from: url };
1107
+ return void 0;
1108
+ }
1109
+ if (opts.site) {
1110
+ const url = `${opts.site.replace(/\/$/, "")}${prodBase.startsWith("/") ? prodBase : `/${prodBase}`}/fulgurjs-manifest.json`;
1111
+ const got = await manifestFromUrl(url);
1112
+ if (got) return { manifest: got, from: url };
1113
+ }
1114
+ return void 0;
1115
+ }
1116
+ async function checkPagesApp(loaded, opts) {
1117
+ const options = loaded.options;
1118
+ const issues = [];
1119
+ const hp = loaded.hostPages;
1120
+ const pages = hp?.pages ?? [];
1121
+ const prefixes = hp?.remotePrefixes ?? {};
1122
+ if (pages.length === 0) {
1123
+ issues.push({
1124
+ level: "unverified",
1125
+ message: "fulgurjs.config.ts \u672A\u63D0\u4F9B hostPages \u5177\u540D\u5BFC\u51FA\u2014\u2014\u65E0\u4ECE\u6838\u5BF9\u9875\u9762\u5951\u7EA6\uFF08\u7EAF\u8FDC\u7A0B\u5E94\u7528\u5C5E\u6B63\u5E38\uFF0C\u65E0\u9700\u9875\u9762\u8868\uFF09\u3002\n \u4FEE\u6CD5: \u5BBF\u4E3B\u5E94\u7528\u5728 fulgurjs.config.ts \u52A0\u5177\u540D\u5BFC\u51FA hostPages = { pages, remotePrefixes, deriveSpec? }\uFF08\u4E0E\u8FD0\u884C\u65F6 createHostPages \u6D88\u8D39\u540C\u4E00\u4EFD\u6570\u636E\u6A21\u5757\uFF09"
1126
+ });
1127
+ return { app: options.name, checked: 0, issues, failed: false, unverifiedFailed: !!opts.requireVerified, manifestSources: [] };
1128
+ }
1129
+ const violations = validatePages(pages, { remotes: prefixes });
1130
+ for (const v of violations) {
1131
+ issues.push({ level: v.level === "error" ? "error" : "warn", message: v.message });
1132
+ }
1133
+ const remoteNames = [...new Set(Object.values(prefixes))];
1134
+ const manifests = /* @__PURE__ */ new Map();
1135
+ const sources = [];
1136
+ for (const name of remoteNames) {
1137
+ if (!options.remotes || !(name in options.remotes)) {
1138
+ issues.push({ level: "error", message: `\u8DEF\u7531\u524D\u7F00\u6620\u5C04\u5230\u8FDC\u7A0B "${name}"\uFF0C\u4F46\u672C\u5E94\u7528 federation({ remotes }) \u672A\u6D88\u8D39\u5B83\u2014\u2014\u5BBF\u4E3B\u53EA\u6838\u5BF9\u5B83\u5B9E\u9645\u6D88\u8D39\u7684\u8FDC\u7A0B` });
1139
+ continue;
1140
+ }
1141
+ const got = await fetchRemoteManifestApp(loaded, options, name, opts);
1142
+ if (!got) {
1143
+ const how = opts.manifests?.[name] ? `--manifest ${name}=${opts.manifests[name]} \u4E0D\u53EF\u8FBE\u6216\u975E\u6709\u6548 manifest` : opts.site ? `\u6309 remotes prod \u5730\u5740\u4E0E --site ${opts.site} \u63A8\u5BFC\u7684 manifest URL \u4E0D\u53EF\u8FBE` : "\u672A\u63D0\u4F9B manifest \u6765\u6E90";
1144
+ issues.push({
1145
+ level: "unverified",
1146
+ message: `\u8FDC\u7A0B "${name}" \u7684 manifest \u4E0D\u53EF\u5F97\uFF08${how}\uFF09\u2014\u2014\u8BE5\u8FDC\u7A0B\u7684 spec \u5B58\u5728\u6027\u65E0\u6CD5\u6838\u5BF9\u3002
1147
+ \u4FEE\u6CD5: --manifest ${name}=<\u5DF2\u6784\u5EFA\u4EA7\u7269\u8DEF\u5F84\u6216 https://\u2026/fulgurjs-manifest.json>\uFF0C\u6216 --site <\u90E8\u7F72\u7AD9\u70B9>\uFF08\u6309 prod \u5730\u5740\u63A8\u5BFC\uFF09`
1148
+ });
1149
+ continue;
1150
+ }
1151
+ manifests.set(name, { exposes: new Set([...normalizeExposes(got.manifest).keys()].map(normSpecKey)), from: got.from });
1152
+ sources.push({ remote: name, from: got.from });
1153
+ }
1154
+ const derive = hp?.deriveSpec ?? DEFAULT_DERIVE;
1155
+ let checked = 0;
1156
+ for (const page of pages) {
1157
+ const prefixHit = Object.entries(prefixes).sort((a, b) => b[0].length - a[0].length).find(
1158
+ ([p]) => page.route.startsWith(p.endsWith("/") ? p : `${p}/`) || `${page.route}/` === (p.endsWith("/") ? p : `${p}/`)
1159
+ );
1160
+ if (!prefixHit) {
1161
+ issues.push({ level: "error", message: `\u8DEF\u7531 "${page.route}" \u4E0D\u5728\u4EFB\u4F55 remotePrefixes \u524D\u7F00\u4E0B` });
1162
+ continue;
1163
+ }
1164
+ checked++;
1165
+ const m = manifests.get(prefixHit[1]);
1166
+ if (!m) continue;
1167
+ const spec = normSpecKey(String(page.spec ?? derive(page.route)));
1168
+ if (!m.exposes.has(spec)) {
1169
+ issues.push({
1170
+ level: "error",
1171
+ message: `\u8DEF\u7531 "${page.route}" \u7684 spec "${spec}" \u4E0D\u5728\u8FDC\u7A0B "${prefixHit[1]}" \u7684 exposes \u6E05\u5355\u4E2D\uFF08\u6765\u6E90 ${m.from}\uFF09\u3002
1172
+ \u8FDC\u7A0B\u5B9E\u9645 exposes\uFF08\u524D 10\uFF09\uFF1A${[...m.exposes].slice(0, 10).join("\u3001") || "\uFF08\u7A7A\uFF09"}
1173
+ \u4FEE\u6CD5: \u6838\u5BF9\u8FDC\u7A0B federation({ exposes }) \u952E\u540D\u6216\u4FEE\u6B63\u9875\u9762\u8868 spec`
1174
+ });
1175
+ }
1176
+ }
1177
+ const failed = issues.some((i) => i.level === "error");
1178
+ return { app: options.name, checked, issues, failed, unverifiedFailed: !!opts.requireVerified, manifestSources: sources };
1179
+ }
1180
+ async function checkPagesRepo(cfg, appPathOrName, opts) {
740
1181
  const app = cfg.apps.find((a) => a.path === appPathOrName || a.name === appPathOrName);
741
1182
  if (!app) {
742
1183
  throw new Error(`[fulgurjs:check-pages] \u5E94\u7528 "${appPathOrName}" \u4E0D\u5728\u914D\u7F6E\u4E2D\uFF08\u53EF\u7528\uFF1A${cfg.apps.map((a) => a.path).join("\u3001")}\uFF09`);
@@ -749,7 +1190,7 @@ async function checkPages(configPath, appPathOrName, opts = {}) {
749
1190
  level: "unverified",
750
1191
  message: "\u4ED3\u5E93\u914D\u7F6E\u672A\u63D0\u4F9B\u9875\u9762\u8868\uFF08pages \u53EF\u9009\uFF0C\u5E94\u7528\u4EE3\u7801\u9875\u9762\u8868\u4E3A\u8FD0\u884C\u65F6\u771F\u6E90\uFF09\u2014\u2014\u672C\u547D\u4EE4\u65E0\u4ECE\u6838\u5BF9\u3002\n \u4FEE\u6CD5: \u5728 fulgurjs.config.ts \u5BBF\u4E3B host.pages \u63D0\u4F9B\u9875\u9762\u8868\u526F\u672C\uFF0C\u6216\u4F9D\u8D56 dev \u671F remoteSchema \u63A2\u9488\u6821\u9A8C"
751
1192
  });
752
- return { app: app.name, checked: 0, issues, failed: false };
1193
+ return { app: app.name, checked: 0, issues, failed: false, unverifiedFailed: !!opts.requireVerified, manifestSources: [] };
753
1194
  }
754
1195
  const violations = validatePages(pages, { remotes: prefixes });
755
1196
  for (const v of violations) {
@@ -757,13 +1198,14 @@ async function checkPages(configPath, appPathOrName, opts = {}) {
757
1198
  }
758
1199
  const remoteNames = [...new Set(Object.values(prefixes))];
759
1200
  const manifests = /* @__PURE__ */ new Map();
1201
+ const sources = [];
760
1202
  for (const name of remoteNames) {
761
1203
  const remoteApp = cfg.apps.find((a) => a.name === name);
762
1204
  if (!remoteApp) {
763
1205
  issues.push({ level: "error", message: `\u8DEF\u7531\u524D\u7F00\u6620\u5C04\u5230\u672A\u77E5\u8FDC\u7A0B "${name}"\u2014\u2014\u914D\u7F6E apps \u4E2D\u4E0D\u5B58\u5728\u8BE5\u5BB9\u5668\u540D` });
764
1206
  continue;
765
1207
  }
766
- const got = await fetchRemoteManifest(cfg, remoteApp, opts);
1208
+ const got = await fetchRemoteManifestRepo(cfg, remoteApp, opts);
767
1209
  if (!got) {
768
1210
  issues.push({
769
1211
  level: "unverified",
@@ -773,6 +1215,7 @@ async function checkPages(configPath, appPathOrName, opts = {}) {
773
1215
  continue;
774
1216
  }
775
1217
  manifests.set(name, { exposes: new Set([...normalizeExposes(got.manifest).keys()].map(normSpecKey)), from: got.from });
1218
+ sources.push({ remote: name, from: got.from });
776
1219
  }
777
1220
  const derive = app.host?.deriveSpec ?? ((r) => r.split("/").filter(Boolean).slice(1).filter((s) => !s.startsWith(":")).join("/"));
778
1221
  let checked = 0;
@@ -797,7 +1240,18 @@ async function checkPages(configPath, appPathOrName, opts = {}) {
797
1240
  });
798
1241
  }
799
1242
  }
800
- return { app: app.name, checked, issues, failed: issues.some((i) => i.level === "error") };
1243
+ const failed = issues.some((i) => i.level === "error");
1244
+ return { app: app.name, checked, issues, failed, unverifiedFailed: !!opts.requireVerified, manifestSources: sources };
1245
+ }
1246
+ async function checkPages(configPath, appPathOrName, opts = {}) {
1247
+ const loaded = await loadAppConfig(configPath);
1248
+ if (loaded.kind === "repo") {
1249
+ if (!appPathOrName) {
1250
+ throw new Error("[fulgurjs:check-pages] \u805A\u5408\u914D\u7F6E\u9700 --app <\u5E94\u7528\u76EE\u5F55\u540D\u6216\u5BB9\u5668\u540D>\uFF08\u5355\u9879\u76EE\u914D\u7F6E\u53EF\u7701\u7565\uFF09");
1251
+ }
1252
+ return checkPagesRepo(loaded.repo, appPathOrName, opts);
1253
+ }
1254
+ return checkPagesApp(loaded, opts);
801
1255
  }
802
1256
  function formatCheckPages(r) {
803
1257
  const L = [];
@@ -805,19 +1259,28 @@ function formatCheckPages(r) {
805
1259
  const unverified = r.issues.filter((i) => i.level === "unverified");
806
1260
  const warns = r.issues.filter((i) => i.level === "warn");
807
1261
  L.push(`[fulgurjs:check-pages] \u5E94\u7528 ${r.app}\uFF1A\u6838\u5BF9 ${r.checked} \u6761\u9875\u9762\uFF0Cerror ${errors.length} / warn ${warns.length} / \u65E0\u6CD5\u9A8C\u8BC1 ${unverified.length}`);
1262
+ if (r.manifestSources?.length) {
1263
+ L.push("manifest \u6765\u6E90\uFF08\u4F18\u5148\u7EA7\uFF1A--manifest > --site/prod \u63A8\u5BFC > \u672C\u5730 dist\uFF1B\u4EE5\u5B9E\u9645\u547D\u4E2D\u7684\u6765\u6E90\u4E3A\u51C6\uFF09\uFF1A");
1264
+ for (const s of r.manifestSources) L.push(` ${s.remote} \u2190 ${s.from}`);
1265
+ }
808
1266
  for (const i of [...errors, ...warns, ...unverified]) L.push(`
809
1267
  [${i.level.toUpperCase()}] ${i.message}`);
810
1268
  if (r.issues.length === 0) L.push("\u5168\u90E8\u9875\u9762 spec \u4E0E\u8FDC\u7A0B exposes \u4E00\u81F4 \u2713");
1269
+ if (r.unverifiedFailed && unverified.length > 0 && errors.length === 0) {
1270
+ L.push(`
1271
+ --require-verified\uFF1A\u5B58\u5728 ${unverified.length} \u9879\u65E0\u6CD5\u9A8C\u8BC1\u2014\u2014\u6309 CI \u4E25\u683C\u6A21\u5F0F\u5224\u5B9A\u4E3A\u672A\u901A\u8FC7\uFF08\u9000\u51FA\u7801\u975E\u96F6\uFF09`);
1272
+ }
811
1273
  return L.join("\n");
812
1274
  }
813
- var normSpecKey;
1275
+ var DEFAULT_DERIVE, normSpecKey;
814
1276
  var init_commands = __esm({
815
1277
  "src/commands.ts"() {
816
1278
  "use strict";
817
- init_config();
1279
+ init_app_config();
818
1280
  init_config();
819
1281
  init_pages();
820
1282
  init_manifest();
1283
+ DEFAULT_DERIVE = (route) => route.split("/").filter(Boolean).slice(1).filter((s) => !s.startsWith(":")).join("/");
821
1284
  normSpecKey = (s) => s.replace(/^[./]+/, "").replace(/\/+$/, "");
822
1285
  }
823
1286
  });
@@ -1135,24 +1598,31 @@ function formatDoctorReport(checks) {
1135
1598
  // src/cli.ts
1136
1599
  var HELP = `fulgurjs \u2014 Vite Module Federation CLI (@fulgurjs/federation)
1137
1600
 
1138
- \u7528\u6CD5\uFF1A
1139
- fulgurjs init [--template <path>] [--force] \u751F\u6210\u5E26\u6CE8\u91CA\u7684 fulgurjs.config.ts \u8D77\u6B65\u6A21\u677F
1140
- fulgurjs init --config <path> \u6821\u9A8C\u914D\u7F6E\uFF1B\u8F93\u51FA\u5404\u5E94\u7528 federation() \u7C98\u8D34\u5757\u3001
1141
- NGINX no-cache \u7AD9\u70B9\u6A21\u677F\u4E0E\u63A5\u5165\u6838\u5BF9\u6E05\u5355
1142
- fulgurjs explain --config <path> --app <name> [--json]
1143
- \u89E3\u91CA\u67D0\u5E94\u7528\u7684\u6709\u6548\u8054\u90A6\u5F62\u6001\u4E0E\u52A0\u8F7D\u94FE\uFF08\u7EAF\u672C\u5730\uFF0C\u65E0\u7F51\u7EDC\uFF09
1144
- fulgurjs check-pages --config <path> --app <name> [--site <URL>] [--json]
1145
- \u6838\u5BF9\u5BBF\u4E3B\u9875\u9762\u8868\u4E0E\u8FDC\u7A0B exposes\uFF08\u672C\u5730 dist \u4F18\u5148\uFF1B\u786E\u5B9A\u6027\u9519\u8BEF\u975E\u96F6\u9000\u51FA\uFF09
1601
+ \u7528\u6CD5\uFF08\u5355\u9879\u76EE fulgurjs.config.ts \u5728\u5E94\u7528\u6839\u76EE\u5F55\uFF1B\u547D\u4EE4\u9ED8\u8BA4\u8BFB ./fulgurjs.config.ts\uFF09\uFF1A
1602
+ fulgurjs init [--template <path>] [--force] \u751F\u6210\u5355\u9879\u76EE fulgurjs.config.ts \u8D77\u6B65\u6A21\u677F
1603
+ \uFF08\u9ED8\u8BA4\u5BFC\u51FA\u76F4\u63A5\u662F federation() \u9009\u9879\uFF09
1604
+ fulgurjs init --config <path> \u6821\u9A8C\u914D\u7F6E\uFF1B\u8F93\u51FA federation(fulgurjsConfig) \u63A5\u5165\u5757
1605
+ \u4E0E\u63A5\u5165\u6838\u5BF9\u6E05\u5355\uFF08NGINX \u6837\u677F\u4EC5\u968F\u65E7\u805A\u5408\u914D\u7F6E\u6253\u5370\uFF09
1606
+ fulgurjs explain [--config <path>] [--app <name>] [--json]
1607
+ \u89E3\u91CA\u672C\u5E94\u7528\u6709\u6548\u8054\u90A6\u5F62\u6001\u4E0E\u52A0\u8F7D\u94FE\uFF08\u7EAF\u672C\u5730\uFF0C\u65E0\u7F51\u7EDC\uFF1B
1608
+ \u5355\u9879\u76EE\u5F62\u6001\u514D --app\uFF0C\u65E7\u805A\u5408\u914D\u7F6E\u5FC5\u586B\uFF09
1609
+ fulgurjs check-pages [--config <path>] [--app <name>] [--site <URL>]
1610
+ [--manifest <remote>=<\u8DEF\u5F84|URL>]... [--require-verified] [--json]
1611
+ \u6838\u5BF9\u5BBF\u4E3B\u9875\u9762\u8868\u4E0E\u8FDC\u7A0B exposes\uFF08\u5BBF\u4E3B\u9879\u76EE\u8FD0\u884C\uFF1B
1612
+ manifest \u6765\u6E90\u4F18\u5148\u7EA7 --manifest > --site/prod \u63A8\u5BFC\uFF1B
1613
+ \u786E\u5B9A\u6027\u9519\u8BEF\u975E\u96F6\u9000\u51FA\uFF1B--require-verified \u65F6\u65E0\u6CD5\u9A8C\u8BC1\u4E5F\u975E\u96F6\uFF09
1146
1614
  fulgurjs doctor --base <URL> --apps <a,b,c> [--dev] [--json] [--chunk-sample N]
1147
1615
  fulgurjs --help
1148
1616
 
1149
- \u793A\u4F8B\uFF1A
1617
+ \u793A\u4F8B\uFF08\u5355\u9879\u76EE\u5F62\u6001\uFF0C\u5E94\u7528\u6839\u76EE\u5F55\u5185\u8FD0\u884C\uFF09\uFF1A
1150
1618
  fulgurjs init # \u5F53\u524D\u76EE\u5F55\u5199 fulgurjs.config.ts\uFF08\u5DF2\u5B58\u5728\u5219\u62D2\u7EDD\uFF0C--force \u8986\u76D6\uFF09
1151
- fulgurjs init --config fulgurjs.config.ts # \u6821\u9A8C + \u8F93\u51FA\u6837\u677F
1152
- fulgurjs explain --config fulgurjs.config.ts --app apps/remote-a
1153
- fulgurjs check-pages --config fulgurjs.config.ts --app apps/host --site http://your-site
1154
- fulgurjs doctor --base http://your-site --apps app-a,app-b
1155
- fulgurjs doctor --base http://localhost:5173 --apps app-a --dev
1619
+ fulgurjs explain # \u89E3\u91CA\u672C\u5E94\u7528\uFF08--config \u6307\u5411\u5176\u4ED6\u8DEF\u5F84\u65F6\u663E\u5F0F\u4F20\uFF09
1620
+ fulgurjs check-pages --site http://your-site # \u6309 remotes prod \u5730\u5740\u63A8\u5BFC\u8FDC\u7A0B manifest \u6838\u5BF9
1621
+ fulgurjs check-pages --manifest remote-a=https://cdn.example.com/remote-a/fulgurjs-manifest.json
1622
+ fulgurjs doctor --base http://your-site --apps my-app,remote-a
1623
+
1624
+ \u65E7\u805A\u5408\u914D\u7F6E\uFF08root + apps[]\uFF0C\u517C\u5BB9\u671F\uFF09\uFF1A\u6240\u6709\u5B50\u547D\u4EE4\u52A0 --config \u6307\u5411\u805A\u5408\u6587\u4EF6\uFF0C
1625
+ explain/check-pages \u9700 --app <\u5E94\u7528\u76EE\u5F55\u540D\u6216\u5BB9\u5668\u540D>\u3002
1156
1626
  `;
1157
1627
  async function main() {
1158
1628
  const argv = process.argv.slice(2);
@@ -1166,6 +1636,17 @@ async function main() {
1166
1636
  return i > -1 ? argv[i + 1] : void 0;
1167
1637
  };
1168
1638
  const has = (k) => argv.includes(k);
1639
+ const kvAllOf = (k) => {
1640
+ const out = {};
1641
+ argv.forEach((a, i) => {
1642
+ if (a !== k) return;
1643
+ const v = argv[i + 1];
1644
+ const eq = v?.indexOf("=");
1645
+ if (!v || !eq || eq < 1) return;
1646
+ out[v.slice(0, eq)] = v.slice(eq + 1);
1647
+ });
1648
+ return out;
1649
+ };
1169
1650
  if (cmd === "init") {
1170
1651
  const { writeConfigTemplate: writeConfigTemplate2, inspectConfig: inspectConfig2 } = await Promise.resolve().then(() => (init_init(), init_exports));
1171
1652
  const configPath = argOf("--config");
@@ -1184,34 +1665,37 @@ async function main() {
1184
1665
  console.error(`[fulgurjs:init] ${target} \u5DF2\u5B58\u5728\uFF0C\u62D2\u7EDD\u8986\u76D6\uFF08--force \u5F3A\u5236\u8986\u76D6\uFF09`);
1185
1666
  return 2;
1186
1667
  }
1187
- console.log(`[fulgurjs:init] \u5DF2\u751F\u6210\u8D77\u6B65\u6A21\u677F ${target}
1668
+ console.log(`[fulgurjs:init] \u5DF2\u751F\u6210\u5355\u9879\u76EE\u8D77\u6B65\u6A21\u677F ${target}
1188
1669
  \u540E\u7EED\u6B65\u9AA4\uFF1A
1189
- 1. \u7F16\u8F91 fulgurjs.config.ts\uFF1A\u586B\u5165\u4F60\u7684\u5E94\u7528\u76EE\u5F55/\u5BB9\u5668\u540D/\u7AEF\u53E3/base/\u9875\u9762\u8DEF\u7531\u8868/exposes
1190
- 2. npx fulgurjs init --config fulgurjs.config.ts # \u6821\u9A8C\u5E76\u8F93\u51FA\u53EF\u7C98\u8D34\u6837\u677F\u4E0E\u6838\u5BF9\u6E05\u5355
1191
- 3. \u6309\u6E05\u5355\u628A federation(federationOptionsForApp(...)) \u63A5\u5165\u5404\u5E94\u7528 vite.config.ts\uFF08\u5355\u914D\u7F6E\u9A71\u52A8\uFF0C\u89C1 README\uFF09\uFF0C\u5B89\u88C5\u4F9D\u8D56
1192
- 4. npx fulgurjs explain --config fulgurjs.config.ts --app <\u5E94\u7528> # \u6838\u5BF9\u6709\u6548\u5F62\u6001\u4E0E\u52A0\u8F7D\u94FE
1193
- 5. \u90E8\u7F72\u540E\uFF1Anpx fulgurjs doctor --base <URL> --apps <\u5E94\u7528...>`);
1670
+ 1. \u7F16\u8F91 fulgurjs.config.ts\uFF1A\u586B\u5165\u5BB9\u5668\u540D/exposes/remotes/shared\uFF08\u9ED8\u8BA4\u5BFC\u51FA\u76F4\u63A5\u662F federation() \u9009\u9879\uFF09
1671
+ 2. vite.config.ts \u63A5\u5165\uFF08\u4EC5\u4E24\u884C\u8054\u90A6\u76F8\u5173\u4EE3\u7801\uFF09\uFF1A
1672
+ import federation from '@fulgurjs/federation'
1673
+ import fulgurjsConfig from './fulgurjs.config'
1674
+ // plugins: [ ...\u539F\u6709\u63D2\u4EF6, federation(fulgurjsConfig) ]
1675
+ 3. npx fulgurjs explain # \u6838\u5BF9\u6709\u6548\u5F62\u6001\u4E0E\u52A0\u8F7D\u94FE
1676
+ 4. \u5BBF\u4E3B\u5E94\u7528\u53E6\u5728 fulgurjs.config.ts \u5177\u540D\u5BFC\u51FA hostPages\uFF08\u4E0E\u8FD0\u884C\u65F6\u9875\u9762\u6570\u636E\u6A21\u5757\u540C\u6E90\uFF09\uFF0C
1677
+ \u8FD0\u884C npx fulgurjs check-pages --site <\u7AD9\u70B9> \u6838\u5BF9\u9875\u9762\u5951\u7EA6
1678
+ 5. \u90E8\u7F72\u540E\uFF1Anpx fulgurjs doctor --base <URL> --apps <\u5BB9\u5668\u540D>`);
1194
1679
  return 0;
1195
1680
  }
1196
1681
  if (cmd === "explain" || cmd === "check-pages") {
1197
- const configPath = argOf("--config");
1198
1682
  const app = argOf("--app");
1199
- if (!configPath || !app) {
1200
- console.error(`[fulgurjs:${cmd}] \u7F3A\u5C11\u53C2\u6570\uFF1A--config <fulgurjs.config.ts \u8DEF\u5F84> \u4E0E --app <\u5E94\u7528\u76EE\u5F55\u540D\u6216\u5BB9\u5668\u540D>`);
1201
- return 2;
1202
- }
1203
1683
  try {
1204
1684
  if (cmd === "explain") {
1205
1685
  const { explainApp: explainApp2, formatExplain: formatExplain2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
1206
- const r2 = await explainApp2(resolve(configPath), app);
1686
+ const r2 = await explainApp2(resolve(argOf("--config") ?? "fulgurjs.config.ts"), app);
1207
1687
  console.log(has("--json") ? JSON.stringify(r2, null, 2) : formatExplain2(r2));
1208
1688
  return 0;
1209
1689
  }
1210
1690
  const { checkPages: checkPages2, formatCheckPages: formatCheckPages2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
1211
- const site = argOf("--site");
1212
- const r = await checkPages2(resolve(configPath), app, site ? { site } : {});
1691
+ const opts = {
1692
+ ...argOf("--site") ? { site: argOf("--site") } : {},
1693
+ ...Object.keys(kvAllOf("--manifest")).length ? { manifests: kvAllOf("--manifest") } : {},
1694
+ ...has("--require-verified") ? { requireVerified: true } : {}
1695
+ };
1696
+ const r = await checkPages2(resolve(argOf("--config") ?? "fulgurjs.config.ts"), app, opts);
1213
1697
  console.log(has("--json") ? JSON.stringify(r, null, 2) : formatCheckPages2(r));
1214
- return r.failed ? 1 : 0;
1698
+ return r.failed || r.unverifiedFailed ? 1 : 0;
1215
1699
  } catch (e) {
1216
1700
  console.error(String(e.message ?? e));
1217
1701
  return 2;