@fulgurjs/federation 4.0.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
@@ -9,52 +9,208 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/manifest.ts
13
+ function validAssetRef(v) {
14
+ if (typeof v !== "string" || v.length === 0) return false;
15
+ if (/[\s\u0000-\u001f]/.test(v)) return false;
16
+ if (/^https?:\/\/[^/@\s]+:[^/@\s]+@/.test(v)) return false;
17
+ return true;
18
+ }
19
+ function validExposeName(v) {
20
+ return typeof v === "string" && /^\.[\w./-]+$/.test(v);
21
+ }
22
+ function parseManifest(input) {
23
+ const issues = [];
24
+ if (!isPlainObject(input)) {
25
+ return { issues: [{ field: "$", message: "manifest \u5FC5\u987B\u662F JSON \u5BF9\u8C61", got: typeof input }] };
26
+ }
27
+ const m = input;
28
+ let schemaVersion = MANIFEST_SCHEMA_VERSION;
29
+ if (m.schemaVersion !== void 0) {
30
+ if (typeof m.schemaVersion !== "number" || !Number.isInteger(m.schemaVersion)) {
31
+ issues.push({ field: "schemaVersion", message: "\u5FC5\u987B\u662F\u6574\u6570", got: m.schemaVersion });
32
+ } else if (m.schemaVersion !== MANIFEST_SCHEMA_VERSION) {
33
+ return {
34
+ issues: [
35
+ {
36
+ field: "schemaVersion",
37
+ message: `\u4E0D\u652F\u6301\u7684 manifest \u4E3B\u7248\u672C\uFF08\u5F53\u524D\u6D88\u8D39\u7AEF\u652F\u6301 ${MANIFEST_SCHEMA_VERSION}\uFF09`,
38
+ got: m.schemaVersion
39
+ }
40
+ ],
41
+ unsupportedVersion: m.schemaVersion
42
+ };
43
+ }
44
+ }
45
+ if (typeof m.name !== "string" || m.name.length === 0) {
46
+ issues.push({ field: "name", message: "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32", got: m.name });
47
+ }
48
+ const exposes = m.exposes;
49
+ const shared = m.shared;
50
+ const sharedIssues = validateShared(shared);
51
+ issues.push(...sharedIssues);
52
+ if (Array.isArray(exposes)) {
53
+ if (m.devServer !== true) {
54
+ issues.push({ field: "devServer", message: "dev \u6570\u7EC4\u5F62\u6001 exposes \u5FC5\u987B\u4F34\u968F devServer: true", got: m.devServer });
55
+ }
56
+ if (typeof m.base !== "string" || !/^\/(?:.*\/)?$/.test(m.base)) {
57
+ issues.push({ field: "base", message: "\u5FC5\u987B\u662F\u4EE5 / \u5F00\u5934\u4E14 / \u7ED3\u5C3E\u7684\u8DEF\u5F84", got: m.base });
58
+ }
59
+ if (!validAssetRef(m.entry)) {
60
+ issues.push({ field: "entry", message: "\u5FC5\u987B\u662F\u5408\u6CD5\u8D44\u6E90\u5730\u5740", got: m.entry });
61
+ }
62
+ for (const [i, e] of exposes.entries()) {
63
+ if (!isPlainObject(e)) {
64
+ issues.push({ field: `exposes[${i}]`, message: "\u5FC5\u987B\u662F\u5BF9\u8C61", got: typeof e });
65
+ continue;
66
+ }
67
+ if (!validExposeName(e.name)) {
68
+ issues.push({ field: `exposes[${i}].name`, message: '\u5FC5\u987B\u662F "./xxx" \u5F62\u6001\u7684 expose \u540D', got: e.name });
69
+ }
70
+ if (typeof e.src !== "string" || e.src.length === 0) {
71
+ issues.push({ field: `exposes[${i}].src`, message: "dev \u5F62\u6001\u5FC5\u987B\u63D0\u4F9B\u6E90\u7801\u76F8\u5BF9\u8DEF\u5F84", got: e.src });
72
+ }
73
+ if (!validAssetRef(e.file)) {
74
+ issues.push({ field: `exposes[${i}].file`, message: "\u5FC5\u987B\u662F\u53EF\u8BF7\u6C42\u7684\u6A21\u5757\u5730\u5740", got: e.file });
75
+ }
76
+ }
77
+ } else if (isPlainObject(exposes)) {
78
+ if (!validAssetRef(m.entry)) {
79
+ issues.push({ field: "entry", message: "\u5FC5\u987B\u662F\u5408\u6CD5\u8D44\u6E90\u5730\u5740\uFF08\u76F8\u5BF9\u57FA\u51C6 = \u6240\u5728\u76EE\u5F55\uFF09", got: m.entry });
80
+ }
81
+ for (const [key, v] of Object.entries(exposes)) {
82
+ if (!validExposeName(key)) {
83
+ issues.push({ field: `exposes["${key}"]`, message: '\u952E\u5FC5\u987B\u662F "./xxx" \u5F62\u6001\u7684 expose \u540D' });
84
+ }
85
+ if (!isPlainObject(v)) {
86
+ issues.push({ field: `exposes["${key}"]`, message: "\u503C\u5FC5\u987B\u662F\u5BF9\u8C61", got: typeof v });
87
+ continue;
88
+ }
89
+ if (!validAssetRef(v.file)) {
90
+ issues.push({ field: `exposes["${key}"].file`, message: "\u5FC5\u987B\u662F\u8D44\u6E90\u8DEF\u5F84\uFF08\u76F8\u5BF9 entry \u76EE\u5F55\uFF09", got: v.file });
91
+ }
92
+ if (v.css !== void 0) {
93
+ if (!Array.isArray(v.css)) {
94
+ issues.push({ field: `exposes["${key}"].css`, message: "\u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4", got: typeof v.css });
95
+ } else {
96
+ v.css.forEach((css, i) => {
97
+ if (!validAssetRef(css)) {
98
+ issues.push({ field: `exposes["${key}"].css[${i}]`, message: "\u5FC5\u987B\u662F\u8D44\u6E90\u8DEF\u5F84", got: css });
99
+ }
100
+ });
101
+ }
102
+ }
103
+ }
104
+ } else {
105
+ issues.push({ field: "exposes", message: "\u5FC5\u987B\u662F\u6570\u7EC4\uFF08dev\uFF09\u6216\u5BF9\u8C61\uFF08prod\uFF09", got: typeof exposes });
106
+ }
107
+ if (issues.length > 0) return { issues };
108
+ return { manifest: input, issues };
109
+ }
110
+ function validateShared(shared) {
111
+ if (shared === void 0) return [];
112
+ const issues = [];
113
+ if (!Array.isArray(shared)) {
114
+ return [{ field: "shared", message: "\u5FC5\u987B\u662F\u6570\u7EC4", got: typeof shared }];
115
+ }
116
+ for (const [i, s] of shared.entries()) {
117
+ if (!isPlainObject(s)) {
118
+ issues.push({ field: `shared[${i}]`, message: "\u5FC5\u987B\u662F\u5BF9\u8C61", got: typeof s });
119
+ continue;
120
+ }
121
+ if (typeof s.name !== "string" || s.name.length === 0) {
122
+ issues.push({ field: `shared[${i}].name`, message: "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32", got: s.name });
123
+ }
124
+ if (typeof s.version !== "string" || s.version.length === 0) {
125
+ issues.push({ field: `shared[${i}].version`, message: "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32", got: s.version });
126
+ }
127
+ }
128
+ return issues;
129
+ }
130
+ function isDevManifest(m) {
131
+ return Array.isArray(m.exposes);
132
+ }
133
+ function normalizeExposes(manifest) {
134
+ const out = /* @__PURE__ */ new Map();
135
+ if (isDevManifest(manifest)) {
136
+ for (const e of manifest.exposes) {
137
+ if (!validExposeName(e.name)) continue;
138
+ out.set(e.name, { name: e.name, file: e.file, css: [], src: e.src });
139
+ }
140
+ } else {
141
+ for (const [key, v] of Object.entries(manifest.exposes)) {
142
+ if (!validExposeName(key)) continue;
143
+ out.set(key, { name: key, file: v.file, css: Array.isArray(v.css) ? v.css.filter((c) => typeof c === "string") : [] });
144
+ }
145
+ }
146
+ return out;
147
+ }
148
+ var MANIFEST_SCHEMA_VERSION, isPlainObject;
149
+ var init_manifest = __esm({
150
+ "src/manifest.ts"() {
151
+ "use strict";
152
+ MANIFEST_SCHEMA_VERSION = 1;
153
+ isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
154
+ }
155
+ });
156
+
12
157
  // src/config.ts
13
158
  async function loadRepoConfig(configPath) {
14
- const { pathToFileURL, fileURLToPath } = await import("node:url");
15
- const { createRequire } = await import("node:module");
16
- const fs2 = await import("node:fs");
159
+ const { pathToFileURL: pathToFileURL2, fileURLToPath } = await import("node:url");
160
+ const { createRequire: createRequire2 } = await import("node:module");
161
+ const fs3 = await import("node:fs");
17
162
  const os = await import("node:os");
18
- const path2 = await import("node:path");
19
- const selfDir = path2.dirname(fileURLToPath(import.meta.url));
20
- const selfConfigJs = path2.join(selfDir, "config.js");
21
- const SELF_SPEC = fs2.existsSync(selfConfigJs) ? pathToFileURL(selfConfigJs).href : "@fulgurjs-federation-config-unavailable";
163
+ const path3 = await import("node:path");
164
+ const selfDir = await (async () => {
165
+ try {
166
+ return path3.dirname(fileURLToPath(import.meta.url));
167
+ } catch {
168
+ }
169
+ try {
170
+ return __dirname;
171
+ } catch {
172
+ }
173
+ return "";
174
+ })();
175
+ const selfConfigJs = selfDir ? path3.join(selfDir, "config.js") : "";
176
+ const SELF_SPEC = selfConfigJs && fs3.existsSync(selfConfigJs) ? pathToFileURL2(selfConfigJs).href : "@fulgurjs-federation-config-unavailable";
22
177
  const rewriteSelfImports = (source) => source.replace(/(['"])@fulgurjs\/federation\/config\1/g, (_m, q) => `${q}${SELF_SPEC}${q}`);
178
+ let cacheBust = 0;
23
179
  const importConfigs = async (cacheKey) => {
24
- return await import(`${cacheKey}?t=${Date.now()}`);
180
+ return await import(`${cacheKey}?t=${Date.now()}-${++cacheBust}`);
25
181
  };
26
182
  let mod;
27
183
  if (configPath.endsWith(".json")) {
28
- mod = { default: JSON.parse(fs2.readFileSync(configPath, "utf8")) };
184
+ mod = { default: JSON.parse(fs3.readFileSync(configPath, "utf8")) };
29
185
  } else {
30
- const source = rewriteSelfImports(fs2.readFileSync(configPath, "utf8"));
31
- const tmpBase = path2.join(os.tmpdir(), `fulgurjs-config-${Date.now()}`);
186
+ const source = rewriteSelfImports(fs3.readFileSync(configPath, "utf8"));
187
+ const tmpBase = path3.join(os.tmpdir(), `fulgurjs-config-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
32
188
  if (/\.(ts|mts|cts)$/.test(configPath)) {
33
189
  try {
34
190
  const tmpTs = `${tmpBase}.ts`;
35
- fs2.writeFileSync(tmpTs, source);
36
- mod = await importConfigs(pathToFileURL(tmpTs).href);
191
+ fs3.writeFileSync(tmpTs, source);
192
+ mod = await importConfigs(pathToFileURL2(tmpTs).href);
37
193
  } catch (e) {
38
194
  if (!/Unexpected|SyntaxError|ERR_UNSUPPORTED_NODE_MODULES|ERR_UNKNOWN_FILE_EXTENSION|ERR_IMPORT_ATTRIBUTE/.test(String(e)) && process.version.startsWith("v2")) {
39
195
  throw e;
40
196
  }
41
- const requireFromConfig = createRequire(configPath);
197
+ const requireFromConfig = createRequire2(configPath);
42
198
  let esbuild;
43
199
  try {
44
200
  esbuild = requireFromConfig("esbuild");
45
201
  } catch {
46
- const requireFromSelf = createRequire(import.meta.url);
202
+ const requireFromSelf = createRequire2(import.meta.url);
47
203
  esbuild = requireFromSelf("esbuild");
48
204
  }
49
205
  const out = esbuild.buildSync({ entryPoints: [configPath], bundle: false, write: false, format: "esm", target: "node18" });
50
206
  const jsFile = `${tmpBase}.mjs`;
51
- fs2.writeFileSync(jsFile, rewriteSelfImports(out.outputFiles[0].text));
52
- mod = await importConfigs(pathToFileURL(jsFile).href);
207
+ fs3.writeFileSync(jsFile, rewriteSelfImports(out.outputFiles[0].text));
208
+ mod = await importConfigs(pathToFileURL2(jsFile).href);
53
209
  }
54
210
  } else {
55
211
  const jsFile = `${tmpBase}.mjs`;
56
- fs2.writeFileSync(jsFile, source);
57
- mod = await importConfigs(pathToFileURL(jsFile).href);
212
+ fs3.writeFileSync(jsFile, source);
213
+ mod = await importConfigs(pathToFileURL2(jsFile).href);
58
214
  }
59
215
  }
60
216
  const cfg = mod.default;
@@ -73,18 +229,278 @@ async function loadRepoConfig(configPath) {
73
229
  throw new Error(
74
230
  `[fulgurjs:init] \u5E94\u7528 "${app.path}" \u65E2\u65E0 host \u4E5F\u65E0 remote \u89D2\u8272
75
231
  \u6839\u56E0\uFF1A\u5E94\u7528\u5FC5\u987B\u81F3\u5C11\u58F0\u660E\u4E00\u4E2A\u89D2\u8272
76
- \u4FEE\u6CD5\uFF1A\u5BBF\u4E3B\u8865 host: { pages, remotePrefixes, remotes }\uFF0C\u8FDC\u7A0B\u8865 remote: { exposes }`
232
+ \u4FEE\u6CD5\uFF1A\u5BBF\u4E3B\u8865 host: { remotePrefixes, remotes }\uFF08pages \u53EF\u9009\uFF09\uFF0C\u8FDC\u7A0B\u8865 remote: { exposes }`
77
233
  );
78
234
  }
79
235
  }
80
236
  return cfg;
81
237
  }
238
+ function federationOptionsForApp(config, appPathOrName) {
239
+ const app = config.apps.find(
240
+ (a) => a.path === appPathOrName || a.name === appPathOrName
241
+ );
242
+ if (!app) {
243
+ throw new Error(
244
+ `[fulgurjs] \u5E94\u7528 "${appPathOrName}" \u4E0D\u5728\u4ED3\u5E93\u914D\u7F6E\u4E2D
245
+ \u53EF\u7528\u5E94\u7528: ${config.apps.map((a) => `${a.path}(${a.name})`).join("\u3001")}
246
+ \u4FEE\u6CD5: \u4FEE\u6B63\u5E94\u7528\u76EE\u5F55\u540D\u6216\u5BB9\u5668\u540D\uFF08fulgurjs.config.ts apps \u6570\u7EC4\u5185\u4E8C\u9009\u4E00\u5339\u914D\uFF09`
247
+ );
248
+ }
249
+ if (!app.host && !app.remote) {
250
+ throw new Error(
251
+ `[fulgurjs] \u5E94\u7528 "${app.path}" \u65E2\u65E0 host \u4E5F\u65E0 remote \u89D2\u8272
252
+ \u4FEE\u6CD5: \u5BBF\u4E3B\u8865 host: { remotePrefixes, remotes }\uFF0C\u8FDC\u7A0B\u8865 remote: { exposes }`
253
+ );
254
+ }
255
+ const remotes = {};
256
+ const remoteSource = /* @__PURE__ */ new Map();
257
+ const mergeRemotes = (from, role) => {
258
+ for (const [key, addr] of Object.entries(from ?? {})) {
259
+ const prev = remotes[key];
260
+ if (prev && (prev.dev !== addr.dev || prev.prod !== addr.prod)) {
261
+ throw new Error(
262
+ `[fulgurjs] \u5E94\u7528 "${app.path}" \u7684\u8FDC\u7A0B "${key}" \u5728 ${role} \u4E0E ${remoteSource.get(key)} \u4E24\u5904\u5730\u5740\u4E0D\u4E00\u81F4
263
+ ${remoteSource.get(key)}: dev=${prev.dev} prod=${prev.prod}
264
+ ${role}: dev=${addr.dev} prod=${addr.prod}
265
+ \u4FEE\u6CD5: \u4FEE\u6B63 fulgurjs.config.ts \u4F7F\u4E24\u5904\u4E00\u81F4\uFF08\u540C\u4E00\u8FDC\u7A0B\u5728\u4E00\u4E2A\u5E94\u7528\u5185\u53EA\u80FD\u6709\u4E00\u4E2A\u5730\u5740\uFF09`
266
+ );
267
+ }
268
+ remotes[key] = addr;
269
+ remoteSource.set(key, role);
270
+ }
271
+ };
272
+ mergeRemotes(app.host?.remotes, "host.remotes");
273
+ mergeRemotes(app.remote?.remotes, "remote.remotes");
274
+ const hasExposes = !!app.remote && Object.keys(app.remote.exposes).length > 0;
275
+ const hasRemotes = Object.keys(remotes).length > 0;
276
+ return {
277
+ name: app.name,
278
+ ...hasRemotes ? { remotes } : {},
279
+ ...hasExposes ? { exposes: app.remote.exposes } : {},
280
+ ...app.remote?.setup ? { setup: app.remote.setup } : {},
281
+ ...app.shared ? { shared: app.shared } : {},
282
+ devSharedSelf: app.devSharedSelf ?? (hasExposes || !!app.remote?.setup)
283
+ };
284
+ }
82
285
  var init_config = __esm({
83
286
  "src/config.ts"() {
84
287
  "use strict";
85
288
  }
86
289
  });
87
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
+
88
504
  // src/init.ts
89
505
  var init_exports = {};
90
506
  __export(init_exports, {
@@ -118,6 +534,8 @@ ${hostRemotes || remoteRemotes}
118
534
  const exposesBlock = app.remote?.exposes ? ` exposes: {
119
535
  ${exposesLiteral(app.remote.exposes)}
120
536
  },
537
+ ` : "";
538
+ const setupBlock = app.remote?.setup ? ` setup: '${app.remote.setup}',
121
539
  ` : "";
122
540
  const sharedBlock = app.shared ? ` shared: {
123
541
  ${Object.entries(app.shared).map(([k, v]) => {
@@ -125,11 +543,22 @@ ${Object.entries(app.shared).map(([k, v]) => {
125
543
  return ` '${k}': { ${opts.join(", ")} },`;
126
544
  }).join("\n")}
127
545
  },
128
- ` : ` ${DEFAULT_SHARED_BLOCK},
129
- `;
546
+ ` : "";
130
547
  return `federation({
131
548
  name: '${app.name}',
132
- ${remotesBlock}${exposesBlock}${sharedBlock}})`;
549
+ ${remotesBlock}${exposesBlock}${setupBlock}${sharedBlock}})`;
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
+ })`;
133
562
  }
134
563
  function nginxSnippetFor(cfg) {
135
564
  const listen = cfg.deploy?.listen ?? 8080;
@@ -156,7 +585,7 @@ function nginxSnippetFor(cfg) {
156
585
  }`;
157
586
  }
158
587
  ).join("\n\n");
159
- 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
160
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
161
590
  # \u5E26 hash \u7684 assets \u624D\u914D\u957F\u7F13\u5B58\uFF1B\u8DE8\u6E90\u90E8\u7F72\u65F6\u4FDD\u7559 Access-Control-Allow-Origin\uFF09
162
591
  server {
@@ -174,235 +603,695 @@ function appSummary(app) {
174
603
  for (const [k, v] of Object.entries(app.host.remotes)) {
175
604
  lines.push(` \u6D88\u8D39\u8FDC\u7A0B ${k} \u2192 dev ${v.dev} / prod ${v.prod}`);
176
605
  }
177
- lines.push(` \u9875\u9762\u8DEF\u7531\u8868\uFF1A${app.host.pages.length} \u6761`);
606
+ lines.push(` \u9875\u9762\u8DEF\u7531\u8868\uFF1A${app.host.pages?.length ?? 0} \u6761${app.host.pages ? "" : "\uFF08\u672A\u5728\u4ED3\u5E93\u914D\u7F6E\u4E2D\u63D0\u4F9B\u2014\u2014\u4EE5\u5E94\u7528\u4EE3\u7801\u9875\u9762\u8868\u4E3A\u51C6\uFF09"}`);
178
607
  }
179
608
  if (app.remote?.exposes) {
180
609
  lines.push(` exposes\uFF1A${Object.keys(app.remote.exposes).join("\u3001")}`);
181
610
  }
611
+ if (app.remote?.setup) {
612
+ lines.push(` setup\uFF08\u53EF\u9009\u8FDC\u7A0B\u521D\u59CB\u5316\uFF09\uFF1A${app.remote.setup}`);
613
+ }
182
614
  if (app.remote?.remotes) {
183
615
  for (const [k, v] of Object.entries(app.remote.remotes)) {
184
- lines.push(` \u53CD\u5411\u6D88\u8D39 ${k} \u2192 dev ${v.dev} / prod ${v.prod}\uFF08\u53CC\u5411\u8054\u90A6\uFF0Cdev \u4E0B\u9700 devSharedSelf: true\uFF09`);
616
+ lines.push(` \u53CD\u5411\u6D88\u8D39 ${k} \u2192 dev ${v.dev} / prod ${v.prod}\uFF08\u53CC\u5411\u8054\u90A6\uFF09`);
185
617
  }
186
618
  }
187
619
  return lines.join("\n");
188
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
+ }
189
643
  async function inspectConfig(configPath) {
190
- const cfg = await loadRepoConfig(configPath);
644
+ const loaded = await loadAppConfig(configPath);
191
645
  const out = [];
192
- 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`);
193
658
  out.push("\u5E94\u7528\u6458\u8981\uFF1A");
194
659
  for (const app of cfg.apps) out.push(appSummary(app));
195
- 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");
196
661
  for (const app of cfg.apps) {
197
662
  out.push(`
198
663
  # ${app.path}\uFF08${app.name}\uFF09`);
199
664
  out.push(viteSnippetFor(app));
200
665
  }
201
- 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");
202
667
  out.push(nginxSnippetFor(cfg));
203
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");
204
- out.push("1. \u5BBF\u4E3B\u4E0E\u8FDC\u7A0B\u90FD\u5B89\u88C5\u4F9D\u8D56\uFF1Apnpm add @fulgurjs/federation");
205
- 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");
206
- 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");
207
- out.push("4. \u8FDC\u7A0B\u7684\u5168\u5C40\u526F\u4F5C\u7528\uFF08\u5168\u5C40\u7EC4\u4EF6/\u6307\u4EE4/\u542F\u52A8\u671F\u521D\u59CB\u5316\uFF09\u5C01\u88C5\u4E3A\u542F\u52A8\u5668\u6A21\u5757\u5E76 expose\uFF0C\u5BBF\u4E3B\u5728 loadRemote \u9875\u9762\u524D\u8C03\u7528\uFF1B");
208
- out.push(" \u8DE8\u5E94\u7528\u4F20\u503C\uFF08locale/store/\u4E8B\u4EF6\u7B49\uFF09\u7EDF\u4E00\u8D70 @fulgurjs/federation/runtime \u7684 context \u51FD\u6570\uFF1A\u5BBF\u4E3B provideAppContext \u4E00\u6B21\u5199\u5165\uFF0C\u8FDC\u7A0B boot \u7528 getAppContext / requireAppContext \u6D88\u8D39");
209
- out.push("5. \u5E94\u7528\u4EE3\u7801\u552F\u4E00 API \u5165\u53E3\uFF1Aimport { loadRemote, provideAppContext, getAppContext, definePages, remoteSchema, remoteComponent } from '@fulgurjs/federation/runtime'\u2014\u20144.0.0 \u8D77 virtual:fulgurjs-api \u5DF2\u5220\u9664\uFF0C\u7C7B\u578B\u7531\u7269\u7406\u5B50\u8DEF\u5F84\u76F4\u63A5\u89E3\u6790");
210
- 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');
211
- 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");
212
- 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);
213
670
  return out.join("\n");
214
671
  }
215
- var MARK, STARTER_CONFIG, DEFAULT_SHARED_BLOCK;
672
+ var MARK, STARTER_CONFIG, COMMON_CHECKLIST;
216
673
  var init_init = __esm({
217
674
  "src/init.ts"() {
218
675
  "use strict";
676
+ init_app_config();
219
677
  init_config();
220
678
  MARK = "[fulgurjs:init]";
221
- 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
222
- // \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
223
- // npx fulgurjs doctor --base http://<\u7AD9\u70B9> --apps <\u5E94\u7528\u76EE\u5F55\u540D...> \u90E8\u7F72\u4F53\u68C0
224
- 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'
225
691
 
226
- export default defineRepoConfig({
227
- // \u5DE5\u7A0B\u6839\u76EE\u5F55\uFF08monorepo \u6839\u6216\u5355\u5E94\u7528\u4ED3\u5E93\u6839\uFF09
228
- root: process.cwd(),
229
- apps: [
230
- // \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
231
- {
232
- path: 'apps/host', // \u76F8\u5BF9 root \u7684\u5E94\u7528\u76EE\u5F55
233
- name: 'host-app', // \u8054\u90A6\u5BB9\u5668\u540D\uFF08\u8FDC\u7A0B\u5F15\u7528\u5B83\u65F6\u7528\u8FD9\u4E2A\u540D\uFF09
234
- port: 5173, // dev \u7AEF\u53E3
235
- base: '/', // \u90E8\u7F72/dev \u7684 URL \u524D\u7F00
236
- host: {
237
- // \u8DEF\u7531\u524D\u7F00 \u2192 \u8FDC\u7A0B\u540D\uFF1A\u5BBF\u4E3B\u628A /remote-a/** \u7684\u9875\u9762\u89E3\u6790\u5230\u8FDC\u7A0B remote-a
238
- remotePrefixes: { '/remote-a/': 'remote-a' },
239
- // \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
240
- remotes: {
241
- 'remote-a': { dev: 'http://localhost:5174/remote-a', prod: '/remote-a' },
242
- },
243
- // \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
244
- // \u5E26\u53C2\u8DEF\u7531\u6216\u975E\u9ED8\u8BA4\u952E\u7528 spec \u663E\u5F0F\u8986\u76D6\uFF09
245
- pages: [
246
- { route: '/remote-a/home', name: 'RemoteAHome', title: '\u9996\u9875' },
247
- { route: '/remote-a/detail/:id', name: 'RemoteADetail', spec: 'pages/remote-a/detail', title: '\u8BE6\u60C5' },
248
- ],
249
- },
250
- },
251
- // \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
252
- {
253
- path: 'apps/remote-a',
254
- name: 'remote-a',
255
- port: 5174,
256
- base: '/remote-a',
257
- remote: {
258
- exposes: {
259
- './pages/remote-a/home': './src/views/Home.vue',
260
- './pages/remote-a/detail': './src/views/Detail.vue',
261
- },
262
- },
263
- },
264
- ],
265
- // \u90E8\u7F72\u4FE1\u606F\uFF08\u53EF\u9009\uFF09\uFF1A\u4EC5\u4F9B init \u8F93\u51FA NGINX \u6837\u677F\u65F6\u4F7F\u7528
266
- deploy: { webRoot: '/var/www/your-site', listen: 8080 },
267
- })
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 }
268
722
  `;
269
- DEFAULT_SHARED_BLOCK = `shared: {
270
- vue: { singleton: true, requiredVersion: '^3.4.0' },
271
- 'vue-router': { singleton: true, requiredVersion: '^4.4.5' },
272
- pinia: { singleton: true, requiredVersion: '^2.1.7' },
273
- }`;
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
+ ];
274
738
  }
275
739
  });
276
740
 
277
- // src/cli.ts
278
- import { resolve } from "node:path";
279
-
280
- // src/doctor.ts
281
- import http from "node:http";
282
- import https from "node:https";
741
+ // src/pages.ts
742
+ function defaultDeriveSpec(route) {
743
+ return segs(route).slice(1).filter((seg) => !seg.startsWith(":")).join("/");
744
+ }
745
+ function shadows(earlier, later) {
746
+ const a = segs(earlier);
747
+ const b = segs(later);
748
+ if (a.length !== b.length) return null;
749
+ if (earlier === later) return "full";
750
+ let hasParam2 = false;
751
+ for (let i = 0; i < a.length; i++) {
752
+ if (a[i] === b[i]) continue;
753
+ if (a[i].startsWith(":")) {
754
+ hasParam2 = true;
755
+ continue;
756
+ }
757
+ return null;
758
+ }
759
+ return hasParam2 ? "param" : null;
760
+ }
761
+ function remoteOfRoute(route, remotes) {
762
+ if (!remotes) return null;
763
+ for (const [prefix, name] of Object.entries(remotes)) {
764
+ if (route === prefix.replace(/\/+$/, "") || route.startsWith(prefix)) return name;
765
+ }
766
+ return null;
767
+ }
768
+ function validatePages(pages, options = {}) {
769
+ const deriveSpec = options.deriveSpec ?? defaultDeriveSpec;
770
+ const violations = [];
771
+ const effective = pages.map((p) => normSpec(p.spec ?? deriveSpec(p.route)));
772
+ pages.forEach((p, i) => {
773
+ if (p.spec || !hasParam(p.route)) return;
774
+ pages.forEach((q, j) => {
775
+ if (i === j) return;
776
+ if (effective[j] !== effective[i]) return;
777
+ violations.push({
778
+ rule: "R1",
779
+ level: "error",
780
+ message: `[R1] \u8DEF\u7531 "${p.route}" \u5265\u53C2\u63A8\u5BFC spec "${effective[i]}" \u4E0E\u8DEF\u7531 "${q.route}"${q.spec ? "\uFF08\u663E\u5F0F spec\uFF09" : ""}\u6536\u655B\u76F8\u540C\u3002
781
+ \u6839\u56E0\uFF1A\u53C2\u6570\u6BB5\u5265\u79BB\u540E expose \u952E\u4E0E\u5176\u5B83\u6761\u76EE\u76F8\u540C\uFF0C\u8BE5\u8DEF\u7531\u4F1A\u9759\u9ED8\u52A0\u8F7D\u9519\u8BEF\u7EC4\u4EF6
782
+ \uFF08\u771F\u5B9E\u4E8B\u6545\uFF1A\u5E26\u53C2\u52A8\u4F5C\u9875\u8BEF\u8D70\u5217\u8868/\u66F4\u65B0\u8BED\u4E49\uFF0C\u6570\u636E\u98CE\u9669\uFF09\u3002
783
+ \u4FEE\u6CD5\uFF1A\u4E3A\u8BE5\u6761\u76EE\u663E\u5F0F\u58F0\u660E spec: '...'\uFF08\u6307\u5411\u5E26\u53C2\u9875\u9762\u81EA\u5DF1\u7684 expose\uFF09\uFF0C\u6216\u8C03\u6574\u8DEF\u7531\u62C6\u5206\u3002`
784
+ });
785
+ });
786
+ });
787
+ const seenSpec = /* @__PURE__ */ new Map();
788
+ pages.forEach((p, i) => {
789
+ const key = effective[i];
790
+ if (seenSpec.has(key)) {
791
+ const first = pages[seenSpec.get(key)];
792
+ const bothExplicit = !!p.spec && !!first.spec;
793
+ violations.push({
794
+ rule: "R2",
795
+ level: "warn",
796
+ message: `[R2] \u8DEF\u7531 "${p.route}" \u4E0E "${first.route}" \u6709\u6548 spec \u76F8\u540C\uFF08"${key}"\uFF09\u3002${bothExplicit ? "\u4E24\u6761\u5747\u4E3A\u663E\u5F0F spec" : "\u5176\u4E2D\u542B\u63A8\u5BFC spec"}\u2014\u2014\u82E5\u4E3A\u523B\u610F\u7684\u83DC\u5355\u522B\u540D\u53EF\u5FFD\u7565\u672C\u8B66\u544A\uFF0C\u5426\u5219\u8BF7\u6838\u5BF9\u3002`
797
+ });
798
+ } else {
799
+ seenSpec.set(key, i);
800
+ }
801
+ });
802
+ if (options.schema && options.remotes) {
803
+ pages.forEach((p, i) => {
804
+ const remote = remoteOfRoute(p.route, options.remotes);
805
+ if (!remote) return;
806
+ const entry = options.schema[remote];
807
+ if (!entry || entry.exists === false) return;
808
+ const exposes = new Set((entry.exposes ?? []).map(normSpec));
809
+ if (!exposes.has(effective[i])) {
810
+ violations.push({
811
+ rule: "R3",
812
+ level: "error",
813
+ message: `[R3] \u8DEF\u7531 "${p.route}" \u7684 spec "${effective[i]}" \u4E0D\u5728\u8FDC\u7A0B "${remote}" \u7684 exposes \u6E05\u5355\u4E2D\u3002
814
+ \u6839\u56E0\uFF1Aspec \u62FC\u5199\u4E0E\u8FDC\u7A0B exposes \u952E\u4E0D\u4E00\u81F4\uFF08\u8FD0\u884C\u65F6\u5C06 MFU-006 \u52A0\u8F7D\u5931\u8D25\uFF09\u3002
815
+ \u4FEE\u6CD5\uFF1A\u6838\u5BF9\u8FDC\u7A0B vite.config exposes \u952E\u540D\uFF0C\u6216\u4FEE\u6B63\u672C\u8868 spec\u3002
816
+ \u8FDC\u7A0B\u5B9E\u9645 exposes\uFF08\u524D 10\uFF09\uFF1A${[...exposes].slice(0, 10).join("\u3001") || "\uFF08\u7A7A\uFF09"}`
817
+ });
818
+ }
819
+ });
820
+ }
821
+ pages.forEach((p, i) => {
822
+ pages.forEach((q, j) => {
823
+ if (j <= i) return;
824
+ const kind = shadows(p.route, q.route);
825
+ if (!kind) return;
826
+ violations.push({
827
+ rule: "R4",
828
+ level: "error",
829
+ message: kind === "full" ? `[R4] \u8DEF\u7531\u5B8C\u5168\u91CD\u590D\uFF1A"${p.route}"\uFF08\u7B2C ${i + 1} \u6761\uFF09\u4E0E "${q.route}"\uFF08\u7B2C ${j + 1} \u6761\uFF09\u3002` : `[R4] \u8DEF\u7531 "${q.route}"\uFF08\u7B2C ${j + 1} \u6761\uFF09\u4F1A\u88AB\u66F4\u9760\u524D\u7684 "${p.route}" \u906E\u853D\uFF08\u5339\u914D\u5148\u5230\u5148\u5F97\uFF0C\u540E\u8005\u6210\u6B7B\u8DEF\u7531\uFF09\u3002
830
+ \u4FEE\u6CD5\uFF1A\u8C03\u6574\u6570\u7EC4\u987A\u5E8F\uFF08\u66F4\u5177\u4F53\u7684\u9759\u6001\u8DEF\u7531\u653E\u524D\u9762\uFF09\u6216\u5408\u5E76\u6761\u76EE\u3002`
831
+ });
832
+ });
833
+ });
834
+ const seenName = /* @__PURE__ */ new Map();
835
+ pages.forEach((p, i) => {
836
+ const name = typeof p.name === "string" ? p.name : "";
837
+ if (!name) return;
838
+ if (seenName.has(name)) {
839
+ violations.push({
840
+ rule: "R5",
841
+ level: "warn",
842
+ message: `[R5] name "${name}" \u91CD\u590D\uFF08"${pages[seenName.get(name)].route}" \u4E0E "${p.route}"\uFF09\u2014\u2014vue-router \u547D\u540D\u8DF3\u8F6C push({name}) \u5C06\u547D\u4E2D\u524D\u8005\uFF0C\u8BF7\u6838\u5BF9\u3002`
843
+ });
844
+ } else {
845
+ seenName.set(name, i);
846
+ }
847
+ });
848
+ return violations;
849
+ }
850
+ var normSpec, hasParam, segs;
851
+ var init_pages = __esm({
852
+ "src/pages.ts"() {
853
+ "use strict";
854
+ normSpec = (s) => s.replace(/^[./]+/, "").replace(/\/+$/, "");
855
+ hasParam = (route) => /(^|\/):[\w]+/.test(route);
856
+ segs = (route) => route.split("/").filter(Boolean);
857
+ }
858
+ });
283
859
 
284
- // src/manifest.ts
285
- var MANIFEST_SCHEMA_VERSION = 1;
286
- var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
287
- function validAssetRef(v) {
288
- if (typeof v !== "string" || v.length === 0) return false;
289
- if (/[\s\u0000-\u001f]/.test(v)) return false;
290
- if (/^https?:\/\/[^/@\s]+:[^/@\s]+@/.test(v)) return false;
291
- return true;
860
+ // src/commands.ts
861
+ var commands_exports = {};
862
+ __export(commands_exports, {
863
+ checkPages: () => checkPages,
864
+ explainApp: () => explainApp,
865
+ formatCheckPages: () => formatCheckPages,
866
+ formatExplain: () => formatExplain
867
+ });
868
+ import fs2 from "node:fs";
869
+ import path2 from "node:path";
870
+ function roleOf(app) {
871
+ if (app.host && app.remote) return "dual";
872
+ return app.host ? "host" : "remote";
292
873
  }
293
- function validExposeName(v) {
294
- return typeof v === "string" && /^\.[\w./-]+$/.test(v);
874
+ function pageMappings(app) {
875
+ const pages = app.host?.pages ?? [];
876
+ const prefixes = Object.entries(app.host?.remotePrefixes ?? {}).sort((a, b) => b[0].length - a[0].length);
877
+ const remoteOf = (route) => {
878
+ for (const [prefix, name] of prefixes) {
879
+ const p = prefix.endsWith("/") ? prefix : `${prefix}/`;
880
+ if (route.startsWith(p) || `${route}/` === p) return name;
881
+ }
882
+ return null;
883
+ };
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);
295
886
  }
296
- function parseManifest(input) {
297
- const issues = [];
298
- if (!isPlainObject(input)) {
299
- return { issues: [{ field: "$", message: "manifest \u5FC5\u987B\u662F JSON \u5BF9\u8C61", got: typeof input }] };
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;
898
+ const out = [];
899
+ for (const page of pages) {
900
+ const remote = remoteOf(page.route);
901
+ if (!remote) continue;
902
+ const spec = `${remote}/${String(page.spec ?? derive(page.route)).replace(/^[./]+/, "")}`;
903
+ out.push({ route: page.route, remote, spec, title: typeof page.title === "string" ? page.title : void 0 });
300
904
  }
301
- const m = input;
302
- let schemaVersion = MANIFEST_SCHEMA_VERSION;
303
- if (m.schemaVersion !== void 0) {
304
- if (typeof m.schemaVersion !== "number" || !Number.isInteger(m.schemaVersion)) {
305
- issues.push({ field: "schemaVersion", message: "\u5FC5\u987B\u662F\u6574\u6570", got: m.schemaVersion });
306
- } else if (m.schemaVersion !== MANIFEST_SCHEMA_VERSION) {
307
- return {
308
- issues: [
309
- {
310
- field: "schemaVersion",
311
- message: `\u4E0D\u652F\u6301\u7684 manifest \u4E3B\u7248\u672C\uFF08\u5F53\u524D\u6D88\u8D39\u7AEF\u652F\u6301 ${MANIFEST_SCHEMA_VERSION}\uFF09`,
312
- got: m.schemaVersion
313
- }
314
- ],
315
- unsupportedVersion: m.schemaVersion
316
- };
905
+ return out;
906
+ }
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) {
966
+ const app = cfg.apps.find((a) => a.path === appPathOrName || a.name === appPathOrName);
967
+ if (!app) {
968
+ throw new Error(
969
+ `[fulgurjs:explain] \u5E94\u7528 "${appPathOrName}" \u4E0D\u5728\u914D\u7F6E\u4E2D\uFF08\u53EF\u7528\uFF1A${cfg.apps.map((a) => a.path).join("\u3001")}\uFF09`
970
+ );
971
+ }
972
+ const opts = federationOptionsForApp(cfg, appPathOrName);
973
+ const remotes = Object.entries(opts.remotes ?? {}).map(
974
+ ([key, v]) => ({ key, dev: String(v.dev ?? v.external ?? ""), prod: String(v.prod ?? v.external ?? "") })
975
+ );
976
+ const shared = Object.entries(opts.shared ?? {}).map(
977
+ ([name, v]) => ({ name, singleton: !!v.singleton, requiredVersion: v.requiredVersion ?? false })
978
+ );
979
+ const chain = [];
980
+ chain.push(app.host ? "\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" : "\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");
981
+ if (app.remote?.setup) {
982
+ chain.push(`setup \u5165\u53E3 ${app.remote.setup}\uFF1A\u9ED8\u8BA4\u5BFC\u51FA\u5E94\u7528\u7EA7\u6267\u884C\u4E00\u6B21\uFF1B${"onSession"} \u6309\u5BBF\u4E3B sessionKey \u53BB\u91CD\uFF08\u7F3A sessionKey \u65F6\u62A5 MFU-013\uFF09`);
983
+ } else {
984
+ 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");
985
+ }
986
+ return {
987
+ app: app.name,
988
+ role: roleOf(app),
989
+ mode: "repo",
990
+ path: app.path,
991
+ base: app.base,
992
+ port: app.port,
993
+ remotes,
994
+ exposes: Object.keys(app.remote?.exposes ?? {}),
995
+ setup: app.remote?.setup,
996
+ shared,
997
+ devSharedSelf: {
998
+ value: opts.devSharedSelf === true,
999
+ source: app.devSharedSelf === void 0 ? "inferred" : "explicit"
1000
+ },
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",
1003
+ chain
1004
+ };
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");
317
1011
  }
1012
+ return explainRepoMode(loaded.repo, appName);
318
1013
  }
319
- if (typeof m.name !== "string" || m.name.length === 0) {
320
- issues.push({ field: "name", message: "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32", got: m.name });
1014
+ return explainAppMode(loaded, appName);
1015
+ }
1016
+ function formatExplain(r) {
1017
+ const roleText = r.role === "dual" ? "\u5BBF\u4E3B+\u8FDC\u7A0B\uFF08\u53CC\u89D2\u8272\uFF09" : r.role === "host" ? "\u5BBF\u4E3B" : "\u8FDC\u7A0B";
1018
+ const L = [];
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`);
321
1023
  }
322
- const exposes = m.exposes;
323
- const shared = m.shared;
324
- const sharedIssues = validateShared(shared);
325
- issues.push(...sharedIssues);
326
- if (Array.isArray(exposes)) {
327
- if (m.devServer !== true) {
328
- issues.push({ field: "devServer", message: "dev \u6570\u7EC4\u5F62\u6001 exposes \u5FC5\u987B\u4F34\u968F devServer: true", got: m.devServer });
1024
+ if (r.remotes.length) {
1025
+ L.push("\u6D88\u8D39\u8FDC\u7A0B\uFF1A");
1026
+ for (const x of r.remotes) L.push(` ${x.key} \u2192 dev ${x.dev} / prod ${x.prod}`);
1027
+ } else {
1028
+ L.push("\u6D88\u8D39\u8FDC\u7A0B\uFF1A\uFF08\u65E0\uFF09");
1029
+ }
1030
+ L.push(`\u516C\u5F00 exposes\uFF08${r.exposes.length}\uFF09\uFF1A${r.exposes.join("\u3001") || "\uFF08\u7A7A\uFF09"}`);
1031
+ L.push(`\u5185\u90E8 setup\uFF1A${r.setup ?? "\uFF08\u672A\u914D\u7F6E\u2014\u2014\u65E0\u521D\u59CB\u5316\u751F\u547D\u5468\u671F\uFF09"}`);
1032
+ if (r.shared.length) {
1033
+ L.push("shared\uFF1A");
1034
+ for (const s of r.shared) {
1035
+ L.push(` ${s.name}${s.singleton ? " [singleton]" : ""} requiredVersion=${String(s.requiredVersion)}`);
329
1036
  }
330
- if (typeof m.base !== "string" || !/^\/(?:.*\/)?$/.test(m.base)) {
331
- issues.push({ field: "base", message: "\u5FC5\u987B\u662F\u4EE5 / \u5F00\u5934\u4E14 / \u7ED3\u5C3E\u7684\u8DEF\u5F84", got: m.base });
1037
+ }
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`);
1039
+ if (r.pages.length) {
1040
+ L.push(`\u9875\u9762 spec \u6620\u5C04\uFF08${r.pages.length}\uFF09\u2014\u2014\u6765\u6E90\uFF1A${r.pagesSource}\uFF1A`);
1041
+ for (const p of r.pages) L.push(` ${p.route} \u2192 ${p.spec}${p.title ? `\uFF08${p.title}\uFF09` : ""}`);
1042
+ } else {
1043
+ L.push(`\u9875\u9762 spec \u6620\u5C04\uFF1A\uFF08\u65E0\uFF09\u2014\u2014\u6765\u6E90\uFF1A${r.pagesSource}`);
1044
+ }
1045
+ L.push("\u52A0\u8F7D\u94FE\uFF1A");
1046
+ for (const c of r.chain) L.push(` \xB7 ${c}`);
1047
+ return L.join("\n");
1048
+ }
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;
332
1063
  }
333
- if (!validAssetRef(m.entry)) {
334
- issues.push({ field: "entry", message: "\u5FC5\u987B\u662F\u5408\u6CD5\u8D44\u6E90\u5730\u5740", got: m.entry });
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
+ }
1076
+ const candidates = [app.deployDir || app.base.replace(/^\/|\/$/g, ""), "dist"];
1077
+ for (const dir of candidates) {
1078
+ const p = path2.join(cfg.root, app.path, dir, "fulgurjs-manifest.json");
1079
+ const got = manifestFromFile(p);
1080
+ if (got) return { manifest: got, from: p };
1081
+ }
1082
+ if (opts.site) {
1083
+ const base = app.base === "/" ? "" : app.base.replace(/\/$/, "");
1084
+ const url = `${opts.site.replace(/\/$/, "")}${base}/fulgurjs-manifest.json`;
1085
+ const got = await manifestFromUrl(url);
1086
+ if (got) return { manifest: got, from: url };
1087
+ }
1088
+ return void 0;
1089
+ }
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;
335
1140
  }
336
- for (const [i, e] of exposes.entries()) {
337
- if (!isPlainObject(e)) {
338
- issues.push({ field: `exposes[${i}]`, message: "\u5FC5\u987B\u662F\u5BF9\u8C61", got: typeof e });
339
- continue;
340
- }
341
- if (!validExposeName(e.name)) {
342
- issues.push({ field: `exposes[${i}].name`, message: '\u5FC5\u987B\u662F "./xxx" \u5F62\u6001\u7684 expose \u540D', got: e.name });
343
- }
344
- if (typeof e.src !== "string" || e.src.length === 0) {
345
- issues.push({ field: `exposes[${i}].src`, message: "dev \u5F62\u6001\u5FC5\u987B\u63D0\u4F9B\u6E90\u7801\u76F8\u5BF9\u8DEF\u5F84", got: e.src });
346
- }
347
- if (!validAssetRef(e.file)) {
348
- issues.push({ field: `exposes[${i}].file`, message: "\u5FC5\u987B\u662F\u53EF\u8BF7\u6C42\u7684\u6A21\u5757\u5730\u5740", got: e.file });
349
- }
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;
350
1150
  }
351
- } else if (isPlainObject(exposes)) {
352
- if (!validAssetRef(m.entry)) {
353
- issues.push({ field: "entry", message: "\u5FC5\u987B\u662F\u5408\u6CD5\u8D44\u6E90\u5730\u5740\uFF08\u76F8\u5BF9\u57FA\u51C6 = \u6240\u5728\u76EE\u5F55\uFF09", got: m.entry });
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;
354
1163
  }
355
- for (const [key, v] of Object.entries(exposes)) {
356
- if (!validExposeName(key)) {
357
- issues.push({ field: `exposes["${key}"]`, message: '\u952E\u5FC5\u987B\u662F "./xxx" \u5F62\u6001\u7684 expose \u540D' });
358
- }
359
- if (!isPlainObject(v)) {
360
- issues.push({ field: `exposes["${key}"]`, message: "\u503C\u5FC5\u987B\u662F\u5BF9\u8C61", got: typeof v });
361
- continue;
362
- }
363
- if (!validAssetRef(v.file)) {
364
- issues.push({ field: `exposes["${key}"].file`, message: "\u5FC5\u987B\u662F\u8D44\u6E90\u8DEF\u5F84\uFF08\u76F8\u5BF9 entry \u76EE\u5F55\uFF09", got: v.file });
365
- }
366
- if (v.css !== void 0) {
367
- if (!Array.isArray(v.css)) {
368
- issues.push({ field: `exposes["${key}"].css`, message: "\u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4", got: typeof v.css });
369
- } else {
370
- v.css.forEach((css, i) => {
371
- if (!validAssetRef(css)) {
372
- issues.push({ field: `exposes["${key}"].css[${i}]`, message: "\u5FC5\u987B\u662F\u8D44\u6E90\u8DEF\u5F84", got: css });
373
- }
374
- });
375
- }
376
- }
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
+ });
377
1175
  }
378
- } else {
379
- issues.push({ field: "exposes", message: "\u5FC5\u987B\u662F\u6570\u7EC4\uFF08dev\uFF09\u6216\u5BF9\u8C61\uFF08prod\uFF09", got: typeof exposes });
380
1176
  }
381
- if (issues.length > 0) return { issues };
382
- return { manifest: input, issues };
1177
+ const failed = issues.some((i) => i.level === "error");
1178
+ return { app: options.name, checked, issues, failed, unverifiedFailed: !!opts.requireVerified, manifestSources: sources };
383
1179
  }
384
- function validateShared(shared) {
385
- if (shared === void 0) return [];
1180
+ async function checkPagesRepo(cfg, appPathOrName, opts) {
1181
+ const app = cfg.apps.find((a) => a.path === appPathOrName || a.name === appPathOrName);
1182
+ if (!app) {
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`);
1184
+ }
386
1185
  const issues = [];
387
- if (!Array.isArray(shared)) {
388
- return [{ field: "shared", message: "\u5FC5\u987B\u662F\u6570\u7EC4", got: typeof shared }];
1186
+ const pages = app.host?.pages ?? [];
1187
+ const prefixes = app.host?.remotePrefixes ?? {};
1188
+ if (pages.length === 0) {
1189
+ issues.push({
1190
+ level: "unverified",
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"
1192
+ });
1193
+ return { app: app.name, checked: 0, issues, failed: false, unverifiedFailed: !!opts.requireVerified, manifestSources: [] };
389
1194
  }
390
- for (const [i, s] of shared.entries()) {
391
- if (!isPlainObject(s)) {
392
- issues.push({ field: `shared[${i}]`, message: "\u5FC5\u987B\u662F\u5BF9\u8C61", got: typeof s });
1195
+ const violations = validatePages(pages, { remotes: prefixes });
1196
+ for (const v of violations) {
1197
+ issues.push({ level: v.level === "error" ? "error" : "warn", message: v.message });
1198
+ }
1199
+ const remoteNames = [...new Set(Object.values(prefixes))];
1200
+ const manifests = /* @__PURE__ */ new Map();
1201
+ const sources = [];
1202
+ for (const name of remoteNames) {
1203
+ const remoteApp = cfg.apps.find((a) => a.name === name);
1204
+ if (!remoteApp) {
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` });
393
1206
  continue;
394
1207
  }
395
- if (typeof s.name !== "string" || s.name.length === 0) {
396
- issues.push({ field: `shared[${i}].name`, message: "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32", got: s.name });
1208
+ const got = await fetchRemoteManifestRepo(cfg, remoteApp, opts);
1209
+ if (!got) {
1210
+ issues.push({
1211
+ level: "unverified",
1212
+ message: `\u8FDC\u7A0B "${name}" \u7684 manifest \u4E0D\u53EF\u5F97\uFF08\u672C\u5730 dist \u4E0E${opts.site ? `\u7AD9\u70B9 ${opts.site}` : "\u9ED8\u8BA4 dist \u8DEF\u5F84"}\u5747\u672A\u547D\u4E2D\uFF09\u2014\u2014\u8BE5\u8FDC\u7A0B\u7684 spec \u5B58\u5728\u6027\u65E0\u6CD5\u6838\u5BF9\u3002
1213
+ \u4FEE\u6CD5: \u5148\u6784\u5EFA\u8BE5\u8FDC\u7A0B\uFF08\u4EA7\u7269\u542B fulgurjs-manifest.json\uFF09\uFF0C\u6216\u7528 --site <URL> \u6307\u5411\u5DF2\u90E8\u7F72\u7AD9\u70B9`
1214
+ });
1215
+ continue;
397
1216
  }
398
- if (typeof s.version !== "string" || s.version.length === 0) {
399
- issues.push({ field: `shared[${i}].version`, message: "\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32", got: s.version });
1217
+ manifests.set(name, { exposes: new Set([...normalizeExposes(got.manifest).keys()].map(normSpecKey)), from: got.from });
1218
+ sources.push({ remote: name, from: got.from });
1219
+ }
1220
+ const derive = app.host?.deriveSpec ?? ((r) => r.split("/").filter(Boolean).slice(1).filter((s) => !s.startsWith(":")).join("/"));
1221
+ let checked = 0;
1222
+ for (const page of pages) {
1223
+ const prefixHit = Object.entries(prefixes).sort((a, b) => b[0].length - a[0].length).find(
1224
+ ([p]) => page.route.startsWith(p.endsWith("/") ? p : `${p}/`) || `${page.route}/` === (p.endsWith("/") ? p : `${p}/`)
1225
+ );
1226
+ if (!prefixHit) {
1227
+ issues.push({ level: "error", message: `\u8DEF\u7531 "${page.route}" \u4E0D\u5728\u4EFB\u4F55 remotePrefixes \u524D\u7F00\u4E0B` });
1228
+ continue;
1229
+ }
1230
+ checked++;
1231
+ const m = manifests.get(prefixHit[1]);
1232
+ if (!m) continue;
1233
+ const spec = normSpecKey(String(page.spec ?? derive(page.route)));
1234
+ if (!m.exposes.has(spec)) {
1235
+ issues.push({
1236
+ level: "error",
1237
+ 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
1238
+ \u8FDC\u7A0B\u5B9E\u9645 exposes\uFF08\u524D 10\uFF09\uFF1A${[...m.exposes].slice(0, 10).join("\u3001") || "\uFF08\u7A7A\uFF09"}
1239
+ \u4FEE\u6CD5: \u6838\u5BF9\u8FDC\u7A0B federation({ exposes }) \u952E\u540D\u6216\u4FEE\u6B63\u9875\u9762\u8868 spec`
1240
+ });
400
1241
  }
401
1242
  }
402
- return issues;
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);
403
1255
  }
1256
+ function formatCheckPages(r) {
1257
+ const L = [];
1258
+ const errors = r.issues.filter((i) => i.level === "error");
1259
+ const unverified = r.issues.filter((i) => i.level === "unverified");
1260
+ const warns = r.issues.filter((i) => i.level === "warn");
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
+ }
1266
+ for (const i of [...errors, ...warns, ...unverified]) L.push(`
1267
+ [${i.level.toUpperCase()}] ${i.message}`);
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
+ }
1273
+ return L.join("\n");
1274
+ }
1275
+ var DEFAULT_DERIVE, normSpecKey;
1276
+ var init_commands = __esm({
1277
+ "src/commands.ts"() {
1278
+ "use strict";
1279
+ init_app_config();
1280
+ init_config();
1281
+ init_pages();
1282
+ init_manifest();
1283
+ DEFAULT_DERIVE = (route) => route.split("/").filter(Boolean).slice(1).filter((s) => !s.startsWith(":")).join("/");
1284
+ normSpecKey = (s) => s.replace(/^[./]+/, "").replace(/\/+$/, "");
1285
+ }
1286
+ });
1287
+
1288
+ // src/cli.ts
1289
+ import { resolve } from "node:path";
404
1290
 
405
1291
  // src/doctor.ts
1292
+ init_manifest();
1293
+ import http from "node:http";
1294
+ import https from "node:https";
406
1295
  function fetchHeadOrGet(url, method = "GET") {
407
1296
  return new Promise((resolve2, reject) => {
408
1297
  const mod = url.startsWith("https:") ? https : http;
@@ -709,18 +1598,31 @@ function formatDoctorReport(checks) {
709
1598
  // src/cli.ts
710
1599
  var HELP = `fulgurjs \u2014 Vite Module Federation CLI (@fulgurjs/federation)
711
1600
 
712
- \u7528\u6CD5\uFF1A
713
- fulgurjs init [--template <path>] [--force] \u751F\u6210\u5E26\u6CE8\u91CA\u7684 fulgurjs.config.ts \u8D77\u6B65\u6A21\u677F
714
- fulgurjs init --config <path> \u6821\u9A8C\u914D\u7F6E\uFF1B\u8F93\u51FA\u5404\u5E94\u7528 federation() \u7C98\u8D34\u5757\u3001
715
- NGINX no-cache \u7AD9\u70B9\u6A21\u677F\u4E0E\u63A5\u5165\u6838\u5BF9\u6E05\u5355
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
716
1614
  fulgurjs doctor --base <URL> --apps <a,b,c> [--dev] [--json] [--chunk-sample N]
717
1615
  fulgurjs --help
718
1616
 
719
- \u793A\u4F8B\uFF1A
1617
+ \u793A\u4F8B\uFF08\u5355\u9879\u76EE\u5F62\u6001\uFF0C\u5E94\u7528\u6839\u76EE\u5F55\u5185\u8FD0\u884C\uFF09\uFF1A
720
1618
  fulgurjs init # \u5F53\u524D\u76EE\u5F55\u5199 fulgurjs.config.ts\uFF08\u5DF2\u5B58\u5728\u5219\u62D2\u7EDD\uFF0C--force \u8986\u76D6\uFF09
721
- fulgurjs init --config fulgurjs.config.ts # \u6821\u9A8C + \u8F93\u51FA\u6837\u677F
722
- fulgurjs doctor --base http://your-site --apps app-a,app-b
723
- 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
724
1626
  `;
725
1627
  async function main() {
726
1628
  const argv = process.argv.slice(2);
@@ -734,6 +1636,17 @@ async function main() {
734
1636
  return i > -1 ? argv[i + 1] : void 0;
735
1637
  };
736
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
+ };
737
1650
  if (cmd === "init") {
738
1651
  const { writeConfigTemplate: writeConfigTemplate2, inspectConfig: inspectConfig2 } = await Promise.resolve().then(() => (init_init(), init_exports));
739
1652
  const configPath = argOf("--config");
@@ -752,14 +1665,42 @@ async function main() {
752
1665
  console.error(`[fulgurjs:init] ${target} \u5DF2\u5B58\u5728\uFF0C\u62D2\u7EDD\u8986\u76D6\uFF08--force \u5F3A\u5236\u8986\u76D6\uFF09`);
753
1666
  return 2;
754
1667
  }
755
- 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}
756
1669
  \u540E\u7EED\u6B65\u9AA4\uFF1A
757
- 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
758
- 2. npx fulgurjs init --config fulgurjs.config.ts # \u6821\u9A8C\u5E76\u8F93\u51FA\u53EF\u7C98\u8D34\u6837\u677F\u4E0E\u6838\u5BF9\u6E05\u5355
759
- 3. \u6309\u6E05\u5355\u628A federation() \u5757\u63A5\u5165\u5404\u5E94\u7528 vite.config.ts\uFF0C\u5B89\u88C5\u4F9D\u8D56
760
- 4. \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>`);
761
1679
  return 0;
762
1680
  }
1681
+ if (cmd === "explain" || cmd === "check-pages") {
1682
+ const app = argOf("--app");
1683
+ try {
1684
+ if (cmd === "explain") {
1685
+ const { explainApp: explainApp2, formatExplain: formatExplain2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
1686
+ const r2 = await explainApp2(resolve(argOf("--config") ?? "fulgurjs.config.ts"), app);
1687
+ console.log(has("--json") ? JSON.stringify(r2, null, 2) : formatExplain2(r2));
1688
+ return 0;
1689
+ }
1690
+ const { checkPages: checkPages2, formatCheckPages: formatCheckPages2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
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);
1697
+ console.log(has("--json") ? JSON.stringify(r, null, 2) : formatCheckPages2(r));
1698
+ return r.failed || r.unverifiedFailed ? 1 : 0;
1699
+ } catch (e) {
1700
+ console.error(String(e.message ?? e));
1701
+ return 2;
1702
+ }
1703
+ }
763
1704
  if (cmd === "doctor") {
764
1705
  const base = argOf("--base");
765
1706
  const appsRaw = argOf("--apps") ?? "main";