@fulgurjs/federation 4.0.0 → 4.1.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/CHANGELOG.md +17 -0
- package/README.md +286 -95
- package/dist/{chunk-X4LZDAZQ.js → chunk-75PP6EAK.js} +1 -1
- package/dist/chunk-NUZKZUTD.js +144 -0
- package/dist/chunk-TSXZ7TWA.js +144 -0
- package/dist/{chunk-3VBYOVH4.js → chunk-WBCXLR2N.js} +9 -1
- package/dist/cli.js +584 -127
- package/dist/config.cjs +63 -4
- package/dist/config.d.cts +31 -4
- package/dist/config.d.ts +31 -4
- package/dist/config.js +62 -4
- package/dist/context.cjs +12 -2
- package/dist/context.d.cts +21 -5
- package/dist/context.d.ts +21 -5
- package/dist/context.js +12 -3
- package/dist/index.cjs +72 -21
- package/dist/index.d.cts +2 -92
- package/dist/index.d.ts +2 -92
- package/dist/index.js +72 -21
- package/dist/options-CRGpSmVj.d.cts +100 -0
- package/dist/options-CRGpSmVj.d.ts +100 -0
- package/dist/pages.js +4 -139
- package/dist/runtime-entry.d.ts +86 -5
- package/dist/runtime-entry.js +3 -3
- package/dist/runtime.js +10 -3
- package/dist/vue-adapter.cjs +241 -0
- package/dist/vue-adapter.d.cts +47 -1
- package/dist/vue-adapter.d.ts +47 -1
- package/dist/vue-adapter.js +4 -1
- package/dist/vue.cjs +402 -3
- package/dist/vue.d.cts +6 -2
- package/dist/vue.d.ts +6 -2
- package/dist/vue.js +2 -1
- package/docs//350/277/201/347/247/273/346/214/207/345/215/227.md +11 -8
- package/package.json +1 -1
- package/dist/chunk-AYS56IRF.js +0 -42
package/dist/cli.js
CHANGED
|
@@ -9,30 +9,185 @@ 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
159
|
const { pathToFileURL, fileURLToPath } = await import("node:url");
|
|
15
160
|
const { createRequire } = await import("node:module");
|
|
16
|
-
const
|
|
161
|
+
const fs3 = await import("node:fs");
|
|
17
162
|
const os = await import("node:os");
|
|
18
|
-
const
|
|
19
|
-
const selfDir =
|
|
20
|
-
|
|
21
|
-
|
|
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) ? pathToFileURL(selfConfigJs).href : "@fulgurjs-federation-config-unavailable";
|
|
22
177
|
const rewriteSelfImports = (source) => source.replace(/(['"])@fulgurjs\/federation\/config\1/g, (_m, q) => `${q}${SELF_SPEC}${q}`);
|
|
23
178
|
const importConfigs = async (cacheKey) => {
|
|
24
179
|
return await import(`${cacheKey}?t=${Date.now()}`);
|
|
25
180
|
};
|
|
26
181
|
let mod;
|
|
27
182
|
if (configPath.endsWith(".json")) {
|
|
28
|
-
mod = { default: JSON.parse(
|
|
183
|
+
mod = { default: JSON.parse(fs3.readFileSync(configPath, "utf8")) };
|
|
29
184
|
} else {
|
|
30
|
-
const source = rewriteSelfImports(
|
|
31
|
-
const tmpBase =
|
|
185
|
+
const source = rewriteSelfImports(fs3.readFileSync(configPath, "utf8"));
|
|
186
|
+
const tmpBase = path3.join(os.tmpdir(), `fulgurjs-config-${Date.now()}`);
|
|
32
187
|
if (/\.(ts|mts|cts)$/.test(configPath)) {
|
|
33
188
|
try {
|
|
34
189
|
const tmpTs = `${tmpBase}.ts`;
|
|
35
|
-
|
|
190
|
+
fs3.writeFileSync(tmpTs, source);
|
|
36
191
|
mod = await importConfigs(pathToFileURL(tmpTs).href);
|
|
37
192
|
} catch (e) {
|
|
38
193
|
if (!/Unexpected|SyntaxError|ERR_UNSUPPORTED_NODE_MODULES|ERR_UNKNOWN_FILE_EXTENSION|ERR_IMPORT_ATTRIBUTE/.test(String(e)) && process.version.startsWith("v2")) {
|
|
@@ -48,12 +203,12 @@ async function loadRepoConfig(configPath) {
|
|
|
48
203
|
}
|
|
49
204
|
const out = esbuild.buildSync({ entryPoints: [configPath], bundle: false, write: false, format: "esm", target: "node18" });
|
|
50
205
|
const jsFile = `${tmpBase}.mjs`;
|
|
51
|
-
|
|
206
|
+
fs3.writeFileSync(jsFile, rewriteSelfImports(out.outputFiles[0].text));
|
|
52
207
|
mod = await importConfigs(pathToFileURL(jsFile).href);
|
|
53
208
|
}
|
|
54
209
|
} else {
|
|
55
210
|
const jsFile = `${tmpBase}.mjs`;
|
|
56
|
-
|
|
211
|
+
fs3.writeFileSync(jsFile, source);
|
|
57
212
|
mod = await importConfigs(pathToFileURL(jsFile).href);
|
|
58
213
|
}
|
|
59
214
|
}
|
|
@@ -73,12 +228,59 @@ async function loadRepoConfig(configPath) {
|
|
|
73
228
|
throw new Error(
|
|
74
229
|
`[fulgurjs:init] \u5E94\u7528 "${app.path}" \u65E2\u65E0 host \u4E5F\u65E0 remote \u89D2\u8272
|
|
75
230
|
\u6839\u56E0\uFF1A\u5E94\u7528\u5FC5\u987B\u81F3\u5C11\u58F0\u660E\u4E00\u4E2A\u89D2\u8272
|
|
76
|
-
\u4FEE\u6CD5\uFF1A\u5BBF\u4E3B\u8865 host: {
|
|
231
|
+
\u4FEE\u6CD5\uFF1A\u5BBF\u4E3B\u8865 host: { remotePrefixes, remotes }\uFF08pages \u53EF\u9009\uFF09\uFF0C\u8FDC\u7A0B\u8865 remote: { exposes }`
|
|
77
232
|
);
|
|
78
233
|
}
|
|
79
234
|
}
|
|
80
235
|
return cfg;
|
|
81
236
|
}
|
|
237
|
+
function federationOptionsForApp(config, appPathOrName) {
|
|
238
|
+
const app = config.apps.find(
|
|
239
|
+
(a) => a.path === appPathOrName || a.name === appPathOrName
|
|
240
|
+
);
|
|
241
|
+
if (!app) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`[fulgurjs] \u5E94\u7528 "${appPathOrName}" \u4E0D\u5728\u4ED3\u5E93\u914D\u7F6E\u4E2D
|
|
244
|
+
\u53EF\u7528\u5E94\u7528: ${config.apps.map((a) => `${a.path}(${a.name})`).join("\u3001")}
|
|
245
|
+
\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`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (!app.host && !app.remote) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`[fulgurjs] \u5E94\u7528 "${app.path}" \u65E2\u65E0 host \u4E5F\u65E0 remote \u89D2\u8272
|
|
251
|
+
\u4FEE\u6CD5: \u5BBF\u4E3B\u8865 host: { remotePrefixes, remotes }\uFF0C\u8FDC\u7A0B\u8865 remote: { exposes }`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
const remotes = {};
|
|
255
|
+
const remoteSource = /* @__PURE__ */ new Map();
|
|
256
|
+
const mergeRemotes = (from, role) => {
|
|
257
|
+
for (const [key, addr] of Object.entries(from ?? {})) {
|
|
258
|
+
const prev = remotes[key];
|
|
259
|
+
if (prev && (prev.dev !== addr.dev || prev.prod !== addr.prod)) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
`[fulgurjs] \u5E94\u7528 "${app.path}" \u7684\u8FDC\u7A0B "${key}" \u5728 ${role} \u4E0E ${remoteSource.get(key)} \u4E24\u5904\u5730\u5740\u4E0D\u4E00\u81F4
|
|
262
|
+
${remoteSource.get(key)}: dev=${prev.dev} prod=${prev.prod}
|
|
263
|
+
${role}: dev=${addr.dev} prod=${addr.prod}
|
|
264
|
+
\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`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
remotes[key] = addr;
|
|
268
|
+
remoteSource.set(key, role);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
mergeRemotes(app.host?.remotes, "host.remotes");
|
|
272
|
+
mergeRemotes(app.remote?.remotes, "remote.remotes");
|
|
273
|
+
const hasExposes = !!app.remote && Object.keys(app.remote.exposes).length > 0;
|
|
274
|
+
const hasRemotes = Object.keys(remotes).length > 0;
|
|
275
|
+
return {
|
|
276
|
+
name: app.name,
|
|
277
|
+
...hasRemotes ? { remotes } : {},
|
|
278
|
+
...hasExposes ? { exposes: app.remote.exposes } : {},
|
|
279
|
+
...app.remote?.setup ? { setup: app.remote.setup } : {},
|
|
280
|
+
...app.shared ? { shared: app.shared } : {},
|
|
281
|
+
devSharedSelf: app.devSharedSelf ?? (hasExposes || !!app.remote?.setup)
|
|
282
|
+
};
|
|
283
|
+
}
|
|
82
284
|
var init_config = __esm({
|
|
83
285
|
"src/config.ts"() {
|
|
84
286
|
"use strict";
|
|
@@ -118,6 +320,8 @@ ${hostRemotes || remoteRemotes}
|
|
|
118
320
|
const exposesBlock = app.remote?.exposes ? ` exposes: {
|
|
119
321
|
${exposesLiteral(app.remote.exposes)}
|
|
120
322
|
},
|
|
323
|
+
` : "";
|
|
324
|
+
const setupBlock = app.remote?.setup ? ` setup: '${app.remote.setup}',
|
|
121
325
|
` : "";
|
|
122
326
|
const sharedBlock = app.shared ? ` shared: {
|
|
123
327
|
${Object.entries(app.shared).map(([k, v]) => {
|
|
@@ -129,7 +333,7 @@ ${Object.entries(app.shared).map(([k, v]) => {
|
|
|
129
333
|
`;
|
|
130
334
|
return `federation({
|
|
131
335
|
name: '${app.name}',
|
|
132
|
-
${remotesBlock}${exposesBlock}${sharedBlock}})`;
|
|
336
|
+
${remotesBlock}${exposesBlock}${setupBlock}${sharedBlock}})`;
|
|
133
337
|
}
|
|
134
338
|
function nginxSnippetFor(cfg) {
|
|
135
339
|
const listen = cfg.deploy?.listen ?? 8080;
|
|
@@ -174,14 +378,17 @@ function appSummary(app) {
|
|
|
174
378
|
for (const [k, v] of Object.entries(app.host.remotes)) {
|
|
175
379
|
lines.push(` \u6D88\u8D39\u8FDC\u7A0B ${k} \u2192 dev ${v.dev} / prod ${v.prod}`);
|
|
176
380
|
}
|
|
177
|
-
lines.push(` \u9875\u9762\u8DEF\u7531\u8868\uFF1A${app.host.pages
|
|
381
|
+
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
382
|
}
|
|
179
383
|
if (app.remote?.exposes) {
|
|
180
384
|
lines.push(` exposes\uFF1A${Object.keys(app.remote.exposes).join("\u3001")}`);
|
|
181
385
|
}
|
|
386
|
+
if (app.remote?.setup) {
|
|
387
|
+
lines.push(` setup\uFF08\u53EF\u9009\u8FDC\u7A0B\u521D\u59CB\u5316\uFF09\uFF1A${app.remote.setup}`);
|
|
388
|
+
}
|
|
182
389
|
if (app.remote?.remotes) {
|
|
183
390
|
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\
|
|
391
|
+
lines.push(` \u53CD\u5411\u6D88\u8D39 ${k} \u2192 dev ${v.dev} / prod ${v.prod}\uFF08\u53CC\u5411\u8054\u90A6\uFF09`);
|
|
185
392
|
}
|
|
186
393
|
}
|
|
187
394
|
return lines.join("\n");
|
|
@@ -204,11 +411,15 @@ async function inspectConfig(configPath) {
|
|
|
204
411
|
out.push("1. \u5BBF\u4E3B\u4E0E\u8FDC\u7A0B\u90FD\u5B89\u88C5\u4F9D\u8D56\uFF1Apnpm add @fulgurjs/federation");
|
|
205
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");
|
|
206
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");
|
|
207
|
-
out.push("4. \u8FDC\u7A0B\
|
|
208
|
-
out.push(" \
|
|
209
|
-
out.push("
|
|
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'");
|
|
210
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');
|
|
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");
|
|
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>");
|
|
212
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");
|
|
213
424
|
return out.join("\n");
|
|
214
425
|
}
|
|
@@ -259,6 +470,10 @@ export default defineRepoConfig({
|
|
|
259
470
|
'./pages/remote-a/home': './src/views/Home.vue',
|
|
260
471
|
'./pages/remote-a/detail': './src/views/Detail.vue',
|
|
261
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',
|
|
262
477
|
},
|
|
263
478
|
},
|
|
264
479
|
],
|
|
@@ -274,135 +489,346 @@ export default defineRepoConfig({
|
|
|
274
489
|
}
|
|
275
490
|
});
|
|
276
491
|
|
|
277
|
-
// src/
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// src/doctor.ts
|
|
281
|
-
import http from "node:http";
|
|
282
|
-
import https from "node:https";
|
|
283
|
-
|
|
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;
|
|
292
|
-
}
|
|
293
|
-
function validExposeName(v) {
|
|
294
|
-
return typeof v === "string" && /^\.[\w./-]+$/.test(v);
|
|
492
|
+
// src/pages.ts
|
|
493
|
+
function defaultDeriveSpec(route) {
|
|
494
|
+
return segs(route).slice(1).filter((seg) => !seg.startsWith(":")).join("/");
|
|
295
495
|
}
|
|
296
|
-
function
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
let
|
|
303
|
-
|
|
304
|
-
if (
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
};
|
|
496
|
+
function shadows(earlier, later) {
|
|
497
|
+
const a = segs(earlier);
|
|
498
|
+
const b = segs(later);
|
|
499
|
+
if (a.length !== b.length) return null;
|
|
500
|
+
if (earlier === later) return "full";
|
|
501
|
+
let hasParam2 = false;
|
|
502
|
+
for (let i = 0; i < a.length; i++) {
|
|
503
|
+
if (a[i] === b[i]) continue;
|
|
504
|
+
if (a[i].startsWith(":")) {
|
|
505
|
+
hasParam2 = true;
|
|
506
|
+
continue;
|
|
317
507
|
}
|
|
508
|
+
return null;
|
|
318
509
|
}
|
|
319
|
-
|
|
320
|
-
|
|
510
|
+
return hasParam2 ? "param" : null;
|
|
511
|
+
}
|
|
512
|
+
function remoteOfRoute(route, remotes) {
|
|
513
|
+
if (!remotes) return null;
|
|
514
|
+
for (const [prefix, name] of Object.entries(remotes)) {
|
|
515
|
+
if (route === prefix.replace(/\/+$/, "") || route.startsWith(prefix)) return name;
|
|
321
516
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
function validatePages(pages, options = {}) {
|
|
520
|
+
const deriveSpec = options.deriveSpec ?? defaultDeriveSpec;
|
|
521
|
+
const violations = [];
|
|
522
|
+
const effective = pages.map((p) => normSpec(p.spec ?? deriveSpec(p.route)));
|
|
523
|
+
pages.forEach((p, i) => {
|
|
524
|
+
if (p.spec || !hasParam(p.route)) return;
|
|
525
|
+
pages.forEach((q, j) => {
|
|
526
|
+
if (i === j) return;
|
|
527
|
+
if (effective[j] !== effective[i]) return;
|
|
528
|
+
violations.push({
|
|
529
|
+
rule: "R1",
|
|
530
|
+
level: "error",
|
|
531
|
+
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
|
|
532
|
+
\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
|
|
533
|
+
\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
|
|
534
|
+
\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`
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
const seenSpec = /* @__PURE__ */ new Map();
|
|
539
|
+
pages.forEach((p, i) => {
|
|
540
|
+
const key = effective[i];
|
|
541
|
+
if (seenSpec.has(key)) {
|
|
542
|
+
const first = pages[seenSpec.get(key)];
|
|
543
|
+
const bothExplicit = !!p.spec && !!first.spec;
|
|
544
|
+
violations.push({
|
|
545
|
+
rule: "R2",
|
|
546
|
+
level: "warn",
|
|
547
|
+
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`
|
|
548
|
+
});
|
|
549
|
+
} else {
|
|
550
|
+
seenSpec.set(key, i);
|
|
329
551
|
}
|
|
330
|
-
|
|
331
|
-
|
|
552
|
+
});
|
|
553
|
+
if (options.schema && options.remotes) {
|
|
554
|
+
pages.forEach((p, i) => {
|
|
555
|
+
const remote = remoteOfRoute(p.route, options.remotes);
|
|
556
|
+
if (!remote) return;
|
|
557
|
+
const entry = options.schema[remote];
|
|
558
|
+
if (!entry || entry.exists === false) return;
|
|
559
|
+
const exposes = new Set((entry.exposes ?? []).map(normSpec));
|
|
560
|
+
if (!exposes.has(effective[i])) {
|
|
561
|
+
violations.push({
|
|
562
|
+
rule: "R3",
|
|
563
|
+
level: "error",
|
|
564
|
+
message: `[R3] \u8DEF\u7531 "${p.route}" \u7684 spec "${effective[i]}" \u4E0D\u5728\u8FDC\u7A0B "${remote}" \u7684 exposes \u6E05\u5355\u4E2D\u3002
|
|
565
|
+
\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
|
|
566
|
+
\u4FEE\u6CD5\uFF1A\u6838\u5BF9\u8FDC\u7A0B vite.config exposes \u952E\u540D\uFF0C\u6216\u4FEE\u6B63\u672C\u8868 spec\u3002
|
|
567
|
+
\u8FDC\u7A0B\u5B9E\u9645 exposes\uFF08\u524D 10\uFF09\uFF1A${[...exposes].slice(0, 10).join("\u3001") || "\uFF08\u7A7A\uFF09"}`
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
pages.forEach((p, i) => {
|
|
573
|
+
pages.forEach((q, j) => {
|
|
574
|
+
if (j <= i) return;
|
|
575
|
+
const kind = shadows(p.route, q.route);
|
|
576
|
+
if (!kind) return;
|
|
577
|
+
violations.push({
|
|
578
|
+
rule: "R4",
|
|
579
|
+
level: "error",
|
|
580
|
+
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
|
|
581
|
+
\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`
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
const seenName = /* @__PURE__ */ new Map();
|
|
586
|
+
pages.forEach((p, i) => {
|
|
587
|
+
const name = typeof p.name === "string" ? p.name : "";
|
|
588
|
+
if (!name) return;
|
|
589
|
+
if (seenName.has(name)) {
|
|
590
|
+
violations.push({
|
|
591
|
+
rule: "R5",
|
|
592
|
+
level: "warn",
|
|
593
|
+
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`
|
|
594
|
+
});
|
|
595
|
+
} else {
|
|
596
|
+
seenName.set(name, i);
|
|
332
597
|
}
|
|
333
|
-
|
|
334
|
-
|
|
598
|
+
});
|
|
599
|
+
return violations;
|
|
600
|
+
}
|
|
601
|
+
var normSpec, hasParam, segs;
|
|
602
|
+
var init_pages = __esm({
|
|
603
|
+
"src/pages.ts"() {
|
|
604
|
+
"use strict";
|
|
605
|
+
normSpec = (s) => s.replace(/^[./]+/, "").replace(/\/+$/, "");
|
|
606
|
+
hasParam = (route) => /(^|\/):[\w]+/.test(route);
|
|
607
|
+
segs = (route) => route.split("/").filter(Boolean);
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// src/commands.ts
|
|
612
|
+
var commands_exports = {};
|
|
613
|
+
__export(commands_exports, {
|
|
614
|
+
checkPages: () => checkPages,
|
|
615
|
+
explainApp: () => explainApp,
|
|
616
|
+
formatCheckPages: () => formatCheckPages,
|
|
617
|
+
formatExplain: () => formatExplain
|
|
618
|
+
});
|
|
619
|
+
import fs2 from "node:fs";
|
|
620
|
+
import path2 from "node:path";
|
|
621
|
+
function roleOf(app) {
|
|
622
|
+
if (app.host && app.remote) return "dual";
|
|
623
|
+
return app.host ? "host" : "remote";
|
|
624
|
+
}
|
|
625
|
+
function pageMappings(app) {
|
|
626
|
+
const pages = app.host?.pages ?? [];
|
|
627
|
+
const prefixes = Object.entries(app.host?.remotePrefixes ?? {}).sort((a, b) => b[0].length - a[0].length);
|
|
628
|
+
const remoteOf = (route) => {
|
|
629
|
+
for (const [prefix, name] of prefixes) {
|
|
630
|
+
const p = prefix.endsWith("/") ? prefix : `${prefix}/`;
|
|
631
|
+
if (route.startsWith(p) || `${route}/` === p) return name;
|
|
335
632
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
633
|
+
return null;
|
|
634
|
+
};
|
|
635
|
+
const derive = app.host?.deriveSpec ?? ((route) => route.split("/").filter(Boolean).slice(1).filter((s) => !s.startsWith(":")).join("/"));
|
|
636
|
+
const out = [];
|
|
637
|
+
for (const page of pages) {
|
|
638
|
+
const remote = remoteOf(page.route);
|
|
639
|
+
if (!remote) continue;
|
|
640
|
+
const spec = `${remote}/${String(page.spec ?? derive(page.route)).replace(/^[./]+/, "")}`;
|
|
641
|
+
out.push({ route: page.route, remote, spec, title: typeof page.title === "string" ? page.title : void 0 });
|
|
642
|
+
}
|
|
643
|
+
return out;
|
|
644
|
+
}
|
|
645
|
+
async function explainApp(configPath, appPathOrName) {
|
|
646
|
+
const cfg = await loadRepoConfig(configPath);
|
|
647
|
+
const app = cfg.apps.find((a) => a.path === appPathOrName || a.name === appPathOrName);
|
|
648
|
+
if (!app) {
|
|
649
|
+
throw new Error(
|
|
650
|
+
`[fulgurjs:explain] \u5E94\u7528 "${appPathOrName}" \u4E0D\u5728\u914D\u7F6E\u4E2D\uFF08\u53EF\u7528\uFF1A${cfg.apps.map((a) => a.path).join("\u3001")}\uFF09`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
const opts = federationOptionsForApp(cfg, appPathOrName);
|
|
654
|
+
const remotes = Object.entries(opts.remotes ?? {}).map(
|
|
655
|
+
([key, v]) => ({ key, dev: String(v.dev ?? v.external ?? ""), prod: String(v.prod ?? v.external ?? "") })
|
|
656
|
+
);
|
|
657
|
+
const shared = Object.entries(opts.shared ?? {}).map(
|
|
658
|
+
([name, v]) => ({ name, singleton: !!v.singleton, requiredVersion: v.requiredVersion ?? false })
|
|
659
|
+
);
|
|
660
|
+
const chain = [];
|
|
661
|
+
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");
|
|
662
|
+
if (app.remote?.setup) {
|
|
663
|
+
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`);
|
|
664
|
+
} else {
|
|
665
|
+
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");
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
app: app.name,
|
|
669
|
+
role: roleOf(app),
|
|
670
|
+
path: app.path,
|
|
671
|
+
base: app.base,
|
|
672
|
+
port: app.port,
|
|
673
|
+
remotes,
|
|
674
|
+
exposes: Object.keys(app.remote?.exposes ?? {}),
|
|
675
|
+
setup: app.remote?.setup,
|
|
676
|
+
shared,
|
|
677
|
+
devSharedSelf: {
|
|
678
|
+
value: opts.devSharedSelf === true,
|
|
679
|
+
source: app.devSharedSelf === void 0 ? "inferred" : "explicit"
|
|
680
|
+
},
|
|
681
|
+
pages: pageMappings(app),
|
|
682
|
+
chain
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
function formatExplain(r) {
|
|
686
|
+
const roleText = r.role === "dual" ? "\u5BBF\u4E3B+\u8FDC\u7A0B\uFF08\u53CC\u89D2\u8272\uFF09" : r.role === "host" ? "\u5BBF\u4E3B" : "\u8FDC\u7A0B";
|
|
687
|
+
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`);
|
|
689
|
+
if (r.remotes.length) {
|
|
690
|
+
L.push("\u6D88\u8D39\u8FDC\u7A0B\uFF1A");
|
|
691
|
+
for (const x of r.remotes) L.push(` ${x.key} \u2192 dev ${x.dev} / prod ${x.prod}`);
|
|
692
|
+
} else {
|
|
693
|
+
L.push("\u6D88\u8D39\u8FDC\u7A0B\uFF1A\uFF08\u65E0\uFF09");
|
|
694
|
+
}
|
|
695
|
+
L.push(`\u516C\u5F00 exposes\uFF08${r.exposes.length}\uFF09\uFF1A${r.exposes.join("\u3001") || "\uFF08\u7A7A\uFF09"}`);
|
|
696
|
+
L.push(`\u5185\u90E8 setup\uFF1A${r.setup ?? "\uFF08\u672A\u914D\u7F6E\u2014\u2014\u65E0\u521D\u59CB\u5316\u751F\u547D\u5468\u671F\uFF09"}`);
|
|
697
|
+
if (r.shared.length) {
|
|
698
|
+
L.push("shared\uFF1A");
|
|
699
|
+
for (const s of r.shared) {
|
|
700
|
+
L.push(` ${s.name}${s.singleton ? " [singleton]" : ""} requiredVersion=${String(s.requiredVersion)}`);
|
|
350
701
|
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
702
|
+
}
|
|
703
|
+
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
|
+
if (r.pages.length) {
|
|
705
|
+
L.push(`\u9875\u9762 spec \u6620\u5C04\uFF08${r.pages.length}\uFF09\uFF1A`);
|
|
706
|
+
for (const p of r.pages) L.push(` ${p.route} \u2192 ${p.spec}${p.title ? `\uFF08${p.title}\uFF09` : ""}`);
|
|
707
|
+
} 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");
|
|
709
|
+
}
|
|
710
|
+
L.push("\u52A0\u8F7D\u94FE\uFF1A");
|
|
711
|
+
for (const c of r.chain) L.push(` \xB7 ${c}`);
|
|
712
|
+
return L.join("\n");
|
|
713
|
+
}
|
|
714
|
+
async function fetchRemoteManifest(cfg, app, opts) {
|
|
715
|
+
const candidates = [app.deployDir || app.base.replace(/^\/|\/$/g, ""), "dist"];
|
|
716
|
+
for (const dir of candidates) {
|
|
717
|
+
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 {
|
|
354
722
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
}
|
|
723
|
+
}
|
|
724
|
+
if (opts.site) {
|
|
725
|
+
const base = app.base === "/" ? "" : app.base.replace(/\/$/, "");
|
|
726
|
+
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 };
|
|
376
732
|
}
|
|
733
|
+
} catch {
|
|
377
734
|
}
|
|
378
|
-
} else {
|
|
379
|
-
issues.push({ field: "exposes", message: "\u5FC5\u987B\u662F\u6570\u7EC4\uFF08dev\uFF09\u6216\u5BF9\u8C61\uFF08prod\uFF09", got: typeof exposes });
|
|
380
735
|
}
|
|
381
|
-
|
|
382
|
-
return { manifest: input, issues };
|
|
736
|
+
return void 0;
|
|
383
737
|
}
|
|
384
|
-
function
|
|
385
|
-
|
|
738
|
+
async function checkPages(configPath, appPathOrName, opts = {}) {
|
|
739
|
+
const cfg = await loadRepoConfig(configPath);
|
|
740
|
+
const app = cfg.apps.find((a) => a.path === appPathOrName || a.name === appPathOrName);
|
|
741
|
+
if (!app) {
|
|
742
|
+
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`);
|
|
743
|
+
}
|
|
386
744
|
const issues = [];
|
|
387
|
-
|
|
388
|
-
|
|
745
|
+
const pages = app.host?.pages ?? [];
|
|
746
|
+
const prefixes = app.host?.remotePrefixes ?? {};
|
|
747
|
+
if (pages.length === 0) {
|
|
748
|
+
issues.push({
|
|
749
|
+
level: "unverified",
|
|
750
|
+
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
|
+
});
|
|
752
|
+
return { app: app.name, checked: 0, issues, failed: false };
|
|
389
753
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
754
|
+
const violations = validatePages(pages, { remotes: prefixes });
|
|
755
|
+
for (const v of violations) {
|
|
756
|
+
issues.push({ level: v.level === "error" ? "error" : "warn", message: v.message });
|
|
757
|
+
}
|
|
758
|
+
const remoteNames = [...new Set(Object.values(prefixes))];
|
|
759
|
+
const manifests = /* @__PURE__ */ new Map();
|
|
760
|
+
for (const name of remoteNames) {
|
|
761
|
+
const remoteApp = cfg.apps.find((a) => a.name === name);
|
|
762
|
+
if (!remoteApp) {
|
|
763
|
+
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
764
|
continue;
|
|
394
765
|
}
|
|
395
|
-
|
|
396
|
-
|
|
766
|
+
const got = await fetchRemoteManifest(cfg, remoteApp, opts);
|
|
767
|
+
if (!got) {
|
|
768
|
+
issues.push({
|
|
769
|
+
level: "unverified",
|
|
770
|
+
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
|
|
771
|
+
\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`
|
|
772
|
+
});
|
|
773
|
+
continue;
|
|
397
774
|
}
|
|
398
|
-
|
|
399
|
-
|
|
775
|
+
manifests.set(name, { exposes: new Set([...normalizeExposes(got.manifest).keys()].map(normSpecKey)), from: got.from });
|
|
776
|
+
}
|
|
777
|
+
const derive = app.host?.deriveSpec ?? ((r) => r.split("/").filter(Boolean).slice(1).filter((s) => !s.startsWith(":")).join("/"));
|
|
778
|
+
let checked = 0;
|
|
779
|
+
for (const page of pages) {
|
|
780
|
+
const prefixHit = Object.entries(prefixes).sort((a, b) => b[0].length - a[0].length).find(
|
|
781
|
+
([p]) => page.route.startsWith(p.endsWith("/") ? p : `${p}/`) || `${page.route}/` === (p.endsWith("/") ? p : `${p}/`)
|
|
782
|
+
);
|
|
783
|
+
if (!prefixHit) {
|
|
784
|
+
issues.push({ level: "error", message: `\u8DEF\u7531 "${page.route}" \u4E0D\u5728\u4EFB\u4F55 remotePrefixes \u524D\u7F00\u4E0B` });
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
checked++;
|
|
788
|
+
const m = manifests.get(prefixHit[1]);
|
|
789
|
+
if (!m) continue;
|
|
790
|
+
const spec = normSpecKey(String(page.spec ?? derive(page.route)));
|
|
791
|
+
if (!m.exposes.has(spec)) {
|
|
792
|
+
issues.push({
|
|
793
|
+
level: "error",
|
|
794
|
+
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
|
|
795
|
+
\u8FDC\u7A0B\u5B9E\u9645 exposes\uFF08\u524D 10\uFF09\uFF1A${[...m.exposes].slice(0, 10).join("\u3001") || "\uFF08\u7A7A\uFF09"}
|
|
796
|
+
\u4FEE\u6CD5: \u6838\u5BF9\u8FDC\u7A0B federation({ exposes }) \u952E\u540D\u6216\u4FEE\u6B63\u9875\u9762\u8868 spec`
|
|
797
|
+
});
|
|
400
798
|
}
|
|
401
799
|
}
|
|
402
|
-
return issues;
|
|
800
|
+
return { app: app.name, checked, issues, failed: issues.some((i) => i.level === "error") };
|
|
801
|
+
}
|
|
802
|
+
function formatCheckPages(r) {
|
|
803
|
+
const L = [];
|
|
804
|
+
const errors = r.issues.filter((i) => i.level === "error");
|
|
805
|
+
const unverified = r.issues.filter((i) => i.level === "unverified");
|
|
806
|
+
const warns = r.issues.filter((i) => i.level === "warn");
|
|
807
|
+
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}`);
|
|
808
|
+
for (const i of [...errors, ...warns, ...unverified]) L.push(`
|
|
809
|
+
[${i.level.toUpperCase()}] ${i.message}`);
|
|
810
|
+
if (r.issues.length === 0) L.push("\u5168\u90E8\u9875\u9762 spec \u4E0E\u8FDC\u7A0B exposes \u4E00\u81F4 \u2713");
|
|
811
|
+
return L.join("\n");
|
|
403
812
|
}
|
|
813
|
+
var normSpecKey;
|
|
814
|
+
var init_commands = __esm({
|
|
815
|
+
"src/commands.ts"() {
|
|
816
|
+
"use strict";
|
|
817
|
+
init_config();
|
|
818
|
+
init_config();
|
|
819
|
+
init_pages();
|
|
820
|
+
init_manifest();
|
|
821
|
+
normSpecKey = (s) => s.replace(/^[./]+/, "").replace(/\/+$/, "");
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
// src/cli.ts
|
|
826
|
+
import { resolve } from "node:path";
|
|
404
827
|
|
|
405
828
|
// src/doctor.ts
|
|
829
|
+
init_manifest();
|
|
830
|
+
import http from "node:http";
|
|
831
|
+
import https from "node:https";
|
|
406
832
|
function fetchHeadOrGet(url, method = "GET") {
|
|
407
833
|
return new Promise((resolve2, reject) => {
|
|
408
834
|
const mod = url.startsWith("https:") ? https : http;
|
|
@@ -713,12 +1139,18 @@ var HELP = `fulgurjs \u2014 Vite Module Federation CLI (@fulgurjs/federation)
|
|
|
713
1139
|
fulgurjs init [--template <path>] [--force] \u751F\u6210\u5E26\u6CE8\u91CA\u7684 fulgurjs.config.ts \u8D77\u6B65\u6A21\u677F
|
|
714
1140
|
fulgurjs init --config <path> \u6821\u9A8C\u914D\u7F6E\uFF1B\u8F93\u51FA\u5404\u5E94\u7528 federation() \u7C98\u8D34\u5757\u3001
|
|
715
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
|
|
716
1146
|
fulgurjs doctor --base <URL> --apps <a,b,c> [--dev] [--json] [--chunk-sample N]
|
|
717
1147
|
fulgurjs --help
|
|
718
1148
|
|
|
719
1149
|
\u793A\u4F8B\uFF1A
|
|
720
1150
|
fulgurjs init # \u5F53\u524D\u76EE\u5F55\u5199 fulgurjs.config.ts\uFF08\u5DF2\u5B58\u5728\u5219\u62D2\u7EDD\uFF0C--force \u8986\u76D6\uFF09
|
|
721
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
|
|
722
1154
|
fulgurjs doctor --base http://your-site --apps app-a,app-b
|
|
723
1155
|
fulgurjs doctor --base http://localhost:5173 --apps app-a --dev
|
|
724
1156
|
`;
|
|
@@ -756,10 +1188,35 @@ async function main() {
|
|
|
756
1188
|
\u540E\u7EED\u6B65\u9AA4\uFF1A
|
|
757
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
|
|
758
1190
|
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() \
|
|
760
|
-
4.
|
|
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...>`);
|
|
761
1194
|
return 0;
|
|
762
1195
|
}
|
|
1196
|
+
if (cmd === "explain" || cmd === "check-pages") {
|
|
1197
|
+
const configPath = argOf("--config");
|
|
1198
|
+
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
|
+
try {
|
|
1204
|
+
if (cmd === "explain") {
|
|
1205
|
+
const { explainApp: explainApp2, formatExplain: formatExplain2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
|
|
1206
|
+
const r2 = await explainApp2(resolve(configPath), app);
|
|
1207
|
+
console.log(has("--json") ? JSON.stringify(r2, null, 2) : formatExplain2(r2));
|
|
1208
|
+
return 0;
|
|
1209
|
+
}
|
|
1210
|
+
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 } : {});
|
|
1213
|
+
console.log(has("--json") ? JSON.stringify(r, null, 2) : formatCheckPages2(r));
|
|
1214
|
+
return r.failed ? 1 : 0;
|
|
1215
|
+
} catch (e) {
|
|
1216
|
+
console.error(String(e.message ?? e));
|
|
1217
|
+
return 2;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
763
1220
|
if (cmd === "doctor") {
|
|
764
1221
|
const base = argOf("--base");
|
|
765
1222
|
const appsRaw = argOf("--apps") ?? "main";
|