@fulgurjs/federation 0.4.2
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 +56 -0
- package/DESIGN.md +190 -0
- package/LICENSE +21 -0
- package/README.md +513 -0
- package/dist/cli.js +650 -0
- package/dist/config.cjs +114 -0
- package/dist/config.d.cts +79 -0
- package/dist/config.d.ts +79 -0
- package/dist/config.js +77 -0
- package/dist/index.cjs +1698 -0
- package/dist/index.d.cts +82 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +1663 -0
- package/dist/pages.cjs +169 -0
- package/dist/pages.d.cts +66 -0
- package/dist/pages.d.ts +66 -0
- package/dist/pages.js +143 -0
- package/dist/runtime.js +3 -0
- package/docs/P5-vite7-8/345/205/274/345/256/271/347/237/251/351/230/265.md +36 -0
- package/docs/manual.html +467 -0
- package/docs/vite-upstream-issue-irregexp.md +91 -0
- package/docs/webpack-mf-/345/257/271/347/205/247/344/270/216/347/274/272/345/217/243.md +37 -0
- package/docs//346/262/231/347/256/261/350/276/271/347/225/214/345/256/241/350/256/241.md +45 -0
- package/docs//350/277/201/347/247/273/346/214/207/345/215/227.md +80 -0
- package/examples/fulgur.config.example.ts +41 -0
- package/package.json +90 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/config.ts
|
|
13
|
+
async function loadFulgurConfig(configPath) {
|
|
14
|
+
const { pathToFileURL, fileURLToPath } = await import("node:url");
|
|
15
|
+
const { createRequire } = await import("node:module");
|
|
16
|
+
const fs2 = await import("node:fs");
|
|
17
|
+
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 : "@fulgur-federation-config-unavailable";
|
|
22
|
+
const rewriteSelfImports = (source) => source.replace(/(['"])@fulgur\/federation\/config\1/g, (_m, q) => `${q}${SELF_SPEC}${q}`);
|
|
23
|
+
const importConfigs = async (cacheKey) => {
|
|
24
|
+
return await import(`${cacheKey}?t=${Date.now()}`);
|
|
25
|
+
};
|
|
26
|
+
let mod;
|
|
27
|
+
if (configPath.endsWith(".json")) {
|
|
28
|
+
mod = { default: JSON.parse(fs2.readFileSync(configPath, "utf8")) };
|
|
29
|
+
} else {
|
|
30
|
+
const source = rewriteSelfImports(fs2.readFileSync(configPath, "utf8"));
|
|
31
|
+
const tmpBase = path2.join(os.tmpdir(), `fulgur-config-${Date.now()}`);
|
|
32
|
+
if (/\.(ts|mts|cts)$/.test(configPath)) {
|
|
33
|
+
try {
|
|
34
|
+
const tmpTs = `${tmpBase}.ts`;
|
|
35
|
+
fs2.writeFileSync(tmpTs, source);
|
|
36
|
+
mod = await importConfigs(pathToFileURL(tmpTs).href);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
if (!/Unexpected|SyntaxError|ERR_UNSUPPORTED_NODE_MODULES|ERR_UNKNOWN_FILE_EXTENSION|ERR_IMPORT_ATTRIBUTE/.test(String(e)) && process.version.startsWith("v2")) {
|
|
39
|
+
throw e;
|
|
40
|
+
}
|
|
41
|
+
const requireFromConfig = createRequire(configPath);
|
|
42
|
+
let esbuild;
|
|
43
|
+
try {
|
|
44
|
+
esbuild = requireFromConfig("esbuild");
|
|
45
|
+
} catch {
|
|
46
|
+
const requireFromSelf = createRequire(import.meta.url);
|
|
47
|
+
esbuild = requireFromSelf("esbuild");
|
|
48
|
+
}
|
|
49
|
+
const out = esbuild.buildSync({ entryPoints: [configPath], bundle: false, write: false, format: "esm", target: "node18" });
|
|
50
|
+
const jsFile = `${tmpBase}.mjs`;
|
|
51
|
+
fs2.writeFileSync(jsFile, rewriteSelfImports(out.outputFiles[0].text));
|
|
52
|
+
mod = await importConfigs(pathToFileURL(jsFile).href);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
const jsFile = `${tmpBase}.mjs`;
|
|
56
|
+
fs2.writeFileSync(jsFile, source);
|
|
57
|
+
mod = await importConfigs(pathToFileURL(jsFile).href);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const cfg = mod.default;
|
|
61
|
+
if (!cfg?.root) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`[fulgur:init] \u914D\u7F6E\u7F3A\u5C11 root\uFF08\u5DE5\u7A0B\u6839\u76EE\u5F55\u7EDD\u5BF9\u8DEF\u5F84\uFF09
|
|
64
|
+
\u6839\u56E0\uFF1ACLI \u9700\u8981\u77E5\u9053\u5728\u54EA\u4E2A\u5DE5\u7A0B\u4E0A\u505A\u96C6\u6210
|
|
65
|
+
\u4FEE\u6CD5\uFF1Afulgur.config.ts \u9876\u5C42\u8865 root: '/abs/path/to/repo'\uFF08\u8D77\u6B65\u6A21\u677F\uFF1Afulgur init\uFF09`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (!Array.isArray(cfg.apps) || cfg.apps.length === 0) {
|
|
69
|
+
throw new Error("[fulgur:init] \u914D\u7F6E\u7F3A\u5C11 apps\uFF08\u81F3\u5C11\u4E00\u4E2A\u5BBF\u4E3B\u6216\u8FDC\u7A0B\u5E94\u7528\uFF09");
|
|
70
|
+
}
|
|
71
|
+
for (const app of cfg.apps) {
|
|
72
|
+
if (!app.host && !app.remote) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`[fulgur:init] \u5E94\u7528 "${app.path}" \u65E2\u65E0 host \u4E5F\u65E0 remote \u89D2\u8272
|
|
75
|
+
\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 }`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return cfg;
|
|
81
|
+
}
|
|
82
|
+
var init_config = __esm({
|
|
83
|
+
"src/config.ts"() {
|
|
84
|
+
"use strict";
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// src/init.ts
|
|
89
|
+
var init_exports = {};
|
|
90
|
+
__export(init_exports, {
|
|
91
|
+
STARTER_CONFIG: () => STARTER_CONFIG,
|
|
92
|
+
inspectConfig: () => inspectConfig,
|
|
93
|
+
writeConfigTemplate: () => writeConfigTemplate
|
|
94
|
+
});
|
|
95
|
+
import fs from "node:fs";
|
|
96
|
+
import path from "node:path";
|
|
97
|
+
async function writeConfigTemplate(target, force = false) {
|
|
98
|
+
if (fs.existsSync(target) && !force) {
|
|
99
|
+
return "exists";
|
|
100
|
+
}
|
|
101
|
+
fs.mkdirSync(path.dirname(path.resolve(target)), { recursive: true });
|
|
102
|
+
fs.writeFileSync(path.resolve(target), STARTER_CONFIG);
|
|
103
|
+
return "written";
|
|
104
|
+
}
|
|
105
|
+
function remoteEntriesLiteral(remotes) {
|
|
106
|
+
return Object.entries(remotes).map(([k, v]) => ` '${k}': { dev: '${v.dev}', prod: '${v.prod}' },`).join("\n");
|
|
107
|
+
}
|
|
108
|
+
function exposesLiteral(exposes) {
|
|
109
|
+
return Object.entries(exposes).map(([k, v]) => ` '${k}': '${v}',`).join("\n");
|
|
110
|
+
}
|
|
111
|
+
function viteSnippetFor(app) {
|
|
112
|
+
const hostRemotes = app.host ? remoteEntriesLiteral(app.host.remotes) : "";
|
|
113
|
+
const remoteRemotes = app.remote?.remotes ? remoteEntriesLiteral(app.remote.remotes) : "";
|
|
114
|
+
const remotesBlock = hostRemotes || remoteRemotes ? ` remotes: {
|
|
115
|
+
${hostRemotes || remoteRemotes}
|
|
116
|
+
},
|
|
117
|
+
` : "";
|
|
118
|
+
const exposesBlock = app.remote?.exposes ? ` exposes: {
|
|
119
|
+
${exposesLiteral(app.remote.exposes)}
|
|
120
|
+
},
|
|
121
|
+
` : "";
|
|
122
|
+
const sharedBlock = app.shared ? ` shared: {
|
|
123
|
+
${Object.entries(app.shared).map(([k, v]) => {
|
|
124
|
+
const opts = [v.singleton ? "singleton: true" : "", v.requiredVersion ? `requiredVersion: '${v.requiredVersion}'` : ""].filter(Boolean);
|
|
125
|
+
return ` '${k}': { ${opts.join(", ")} },`;
|
|
126
|
+
}).join("\n")}
|
|
127
|
+
},
|
|
128
|
+
` : ` ${DEFAULT_SHARED_BLOCK},
|
|
129
|
+
`;
|
|
130
|
+
return `federation({
|
|
131
|
+
name: '${app.name}',
|
|
132
|
+
${remotesBlock}${exposesBlock}${sharedBlock}})`;
|
|
133
|
+
}
|
|
134
|
+
function nginxSnippetFor(cfg) {
|
|
135
|
+
const listen = cfg.deploy?.listen ?? 8080;
|
|
136
|
+
const webRoot = cfg.deploy?.webRoot ?? "/var/www/your-site";
|
|
137
|
+
const hostApp = cfg.apps.find((a) => a.host) ?? cfg.apps[0];
|
|
138
|
+
const hostIndex = `/${hostApp.base.replace(/^\/|\/$/g, "")}/index.html`.replace(/\/{2,}/g, "/");
|
|
139
|
+
const blocks = cfg.apps.map(
|
|
140
|
+
(a) => {
|
|
141
|
+
const b = a.base === "/" || a.base === "" ? "/" : a.base.replace(/\/$/, "");
|
|
142
|
+
return ` location ${b} {
|
|
143
|
+
try_files $uri $uri/ ${hostIndex};
|
|
144
|
+
|
|
145
|
+
location = ${b}/fulgur-remoteEntry.js {
|
|
146
|
+
add_header Cache-Control "no-cache";
|
|
147
|
+
add_header Access-Control-Allow-Origin "*";
|
|
148
|
+
}
|
|
149
|
+
location = ${b}/fulgur-manifest.json {
|
|
150
|
+
add_header Cache-Control "no-cache";
|
|
151
|
+
add_header Access-Control-Allow-Origin "*";
|
|
152
|
+
}
|
|
153
|
+
location = ${b}/index.html {
|
|
154
|
+
add_header Cache-Control "no-cache";
|
|
155
|
+
}
|
|
156
|
+
}`;
|
|
157
|
+
}
|
|
158
|
+
).join("\n\n");
|
|
159
|
+
return `# \u8054\u90A6\u7AD9\u70B9\u6A21\u677F\uFF08\u8981\u70B9\uFF1AremoteEntry/manifest/index.html \u5FC5\u987B no-cache\u2014\u2014
|
|
160
|
+
# \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
|
+
# \u5E26 hash \u7684 assets \u624D\u914D\u957F\u7F13\u5B58\uFF1B\u8DE8\u6E90\u90E8\u7F72\u65F6\u4FDD\u7559 Access-Control-Allow-Origin\uFF09
|
|
162
|
+
server {
|
|
163
|
+
listen ${listen};
|
|
164
|
+
root ${webRoot};
|
|
165
|
+
|
|
166
|
+
${blocks}
|
|
167
|
+
}`;
|
|
168
|
+
}
|
|
169
|
+
function appSummary(app) {
|
|
170
|
+
const role = app.host ? app.remote ? "\u5BBF\u4E3B+\u8FDC\u7A0B" : "\u5BBF\u4E3B" : "\u8FDC\u7A0B";
|
|
171
|
+
const lines = [];
|
|
172
|
+
lines.push(` ${app.name}\uFF08${role}\uFF0C\u76EE\u5F55 ${app.path}\uFF0Cdev \u7AEF\u53E3 ${app.port}\uFF0Cbase ${app.base}\uFF09`);
|
|
173
|
+
if (app.host) {
|
|
174
|
+
for (const [k, v] of Object.entries(app.host.remotes)) {
|
|
175
|
+
lines.push(` \u6D88\u8D39\u8FDC\u7A0B ${k} \u2192 dev ${v.dev} / prod ${v.prod}`);
|
|
176
|
+
}
|
|
177
|
+
lines.push(` \u9875\u9762\u8DEF\u7531\u8868\uFF1A${app.host.pages.length} \u6761`);
|
|
178
|
+
}
|
|
179
|
+
if (app.remote?.exposes) {
|
|
180
|
+
lines.push(` exposes\uFF1A${Object.keys(app.remote.exposes).join("\u3001")}`);
|
|
181
|
+
}
|
|
182
|
+
if (app.remote?.remotes) {
|
|
183
|
+
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`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return lines.join("\n");
|
|
188
|
+
}
|
|
189
|
+
async function inspectConfig(configPath) {
|
|
190
|
+
const cfg = await loadFulgurConfig(configPath);
|
|
191
|
+
const out = [];
|
|
192
|
+
out.push(`${MARK} \u914D\u7F6E\u6821\u9A8C\u901A\u8FC7\uFF1Aroot=${cfg.root}\uFF0Capps=${cfg.apps.length}`);
|
|
193
|
+
out.push("\u5E94\u7528\u6458\u8981\uFF1A");
|
|
194
|
+
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");
|
|
196
|
+
for (const app of cfg.apps) {
|
|
197
|
+
out.push(`
|
|
198
|
+
# ${app.path}\uFF08${app.name}\uFF09`);
|
|
199
|
+
out.push(viteSnippetFor(app));
|
|
200
|
+
}
|
|
201
|
+
out.push("\n\u2500\u2500 NGINX \u7AD9\u70B9\u6A21\u677F \u2500\u2500");
|
|
202
|
+
out.push(nginxSnippetFor(cfg));
|
|
203
|
+
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\u5168\u5C40\u914D\u7F6E\uFF08locale/size \u7B49\uFF09\u7528\u8FD0\u884C\u65F6 provideFulgurAppConfig / getFulgurAppConfig \u534F\u5546\uFF0C\u4E0D\u8981\u5404\u526F\u672C\u624B\u5DE5\u6CE8\u5165");
|
|
209
|
+
out.push("5. \u8FDC\u7A0B\u9875\u9762\u53D6\u5BBF\u4E3B\u8FD0\u884C\u65F6\u4E00\u5F8B\u8D70 globalThis.__FULGUR_RUNTIME__ / getRuntime()\uFF0C\u7981\u6B62\u9759\u6001\u5BFC\u5165\u865A\u62DF\u8FD0\u884C\u65F6\uFF08DEV-008\uFF09");
|
|
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\uFF1Afulgur 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");
|
|
213
|
+
return out.join("\n");
|
|
214
|
+
}
|
|
215
|
+
var MARK, STARTER_CONFIG, DEFAULT_SHARED_BLOCK;
|
|
216
|
+
var init_init = __esm({
|
|
217
|
+
"src/init.ts"() {
|
|
218
|
+
"use strict";
|
|
219
|
+
init_config();
|
|
220
|
+
MARK = "[fulgur:init]";
|
|
221
|
+
STARTER_CONFIG = `// fulgur.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 fulgur init --config fulgur.config.ts \u6821\u9A8C\u914D\u7F6E\u5E76\u8F93\u51FA\u53EF\u7C98\u8D34\u6837\u677F\u4E0E\u6838\u5BF9\u6E05\u5355
|
|
223
|
+
// npx fulgur doctor --base http://<\u7AD9\u70B9> --apps <\u5E94\u7528\u76EE\u5F55\u540D...> \u90E8\u7F72\u4F53\u68C0
|
|
224
|
+
import { defineFulgurConfig } from '@fulgurjs/federation/config'
|
|
225
|
+
|
|
226
|
+
export default defineFulgurConfig({
|
|
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
|
+
})
|
|
268
|
+
`;
|
|
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
|
+
}`;
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
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";
|
|
283
|
+
function fetchHeadOrGet(url, method = "GET") {
|
|
284
|
+
return new Promise((resolve2, reject) => {
|
|
285
|
+
const mod = url.startsWith("https:") ? https : http;
|
|
286
|
+
const req = mod.request(url, { method, headers: { accept: "*/*" } }, (res) => {
|
|
287
|
+
const chunks = [];
|
|
288
|
+
res.on("data", (c) => {
|
|
289
|
+
if (chunks.reduce((n, b) => n + b.length, 0) < 2 * 1024 * 1024) chunks.push(c);
|
|
290
|
+
});
|
|
291
|
+
res.on(
|
|
292
|
+
"end",
|
|
293
|
+
() => resolve2({
|
|
294
|
+
status: res.statusCode ?? 0,
|
|
295
|
+
headers: res.headers,
|
|
296
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
297
|
+
})
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
req.on("error", reject);
|
|
301
|
+
req.setTimeout(8e3, () => req.destroy(new Error("timeout after 8000ms")));
|
|
302
|
+
req.end();
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function header(res, name) {
|
|
306
|
+
const v = res.headers[name];
|
|
307
|
+
return Array.isArray(v) ? v[0] : v;
|
|
308
|
+
}
|
|
309
|
+
async function checkStatus(url, app, item, opts = {}) {
|
|
310
|
+
let res;
|
|
311
|
+
try {
|
|
312
|
+
res = await fetchHeadOrGet(url, opts.method ?? "GET");
|
|
313
|
+
} catch (e) {
|
|
314
|
+
return {
|
|
315
|
+
check: {
|
|
316
|
+
app,
|
|
317
|
+
item,
|
|
318
|
+
level: "FAIL",
|
|
319
|
+
symptom: `${item} \u4E0D\u53EF\u8FBE\uFF1A${url}\uFF08${String(e.message ?? e)}\uFF09`,
|
|
320
|
+
cause: "\u9759\u6001\u8D44\u6E90\u672A\u90E8\u7F72\u3001nginx \u7AD9\u70B9\u672A\u8D77\u6216\u8DEF\u5F84\u9519\u4F4D",
|
|
321
|
+
fix: "\u6838\u5BF9\u90E8\u7F72\u76EE\u5F55\uFF08rsync dist/{app}/\uFF09\u4E0E nginx location \u56DE\u9000\u89C4\u5219\uFF1Bcurl \u8BE5 URL \u786E\u8BA4\u53EF\u8FBE"
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (res.status !== 200) {
|
|
326
|
+
return {
|
|
327
|
+
check: {
|
|
328
|
+
app,
|
|
329
|
+
item,
|
|
330
|
+
level: "FAIL",
|
|
331
|
+
symptom: `${item} \u8FD4\u56DE ${res.status}\uFF1A${url}`,
|
|
332
|
+
cause: "\u6587\u4EF6\u7F3A\u5931\uFF08\u672A\u90E8\u7F72/\u5DF2\u88AB\u6E05\uFF09\u6216 nginx \u56DE\u9000\u5230\u4E86\u5BBF\u4E3B index.html\uFF08\u6DF1\u94FE\u56DE\u9000\u8FC7\u5BBD\uFF09",
|
|
333
|
+
fix: "\u91CD\u65B0\u90E8\u7F72\u8BE5\u5E94\u7528\u4EA7\u7269\uFF1B\u6838\u5BF9 nginx location \u7CBE\u786E\u5339\u914D remoteEntry \u7684\u89C4\u5219"
|
|
334
|
+
},
|
|
335
|
+
res
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (opts.expectJs) {
|
|
339
|
+
const head = res.body.slice(0, 200).trimStart();
|
|
340
|
+
if (head.startsWith("<")) {
|
|
341
|
+
return {
|
|
342
|
+
res,
|
|
343
|
+
check: {
|
|
344
|
+
app,
|
|
345
|
+
item,
|
|
346
|
+
level: "FAIL",
|
|
347
|
+
symptom: `${item} \u8FD4\u56DE\u7684\u662F HTML \u800C\u975E JS\uFF1A${url}`,
|
|
348
|
+
cause: "nginx try_files/\u6DF1\u94FE\u56DE\u9000\u628A\u5165\u53E3\u8BF7\u6C42\u515C\u5230\u4E86 index.html\uFF08\u8054\u90A6\u5BBF\u4E3B\u4F1A\u628A\u5B83\u5F53\u6A21\u5757\u89E3\u6790\u76F4\u63A5\u5931\u8D25\uFF09",
|
|
349
|
+
fix: "\u4E3A remoteEntry/\u5165\u53E3\u589E\u52A0\u7CBE\u786E location \u539F\u6837\u8FD4\u56DE JS"
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (opts.expectNoCache) {
|
|
355
|
+
const cc = header(res, "cache-control") ?? "";
|
|
356
|
+
if (/immutable/i.test(cc)) {
|
|
357
|
+
return {
|
|
358
|
+
res,
|
|
359
|
+
check: {
|
|
360
|
+
app,
|
|
361
|
+
item,
|
|
362
|
+
level: "FAIL",
|
|
363
|
+
symptom: `${item} \u7684 Cache-Control=${cc}\uFF08immutable\uFF09\uFF1A${url}`,
|
|
364
|
+
cause: "\u6587\u4EF6\u540D\u56FA\u5B9A\u800C\u5185\u5BB9\u6BCF\u6B21\u6784\u5EFA\u53D8\u5316\u2014\u2014\u6D4F\u89C8\u5668\u4F1A\u957F\u671F\u6301\u6709\u65E7\u7248\u672C\uFF0C\u91CD\u90E8\u7F72\u540E\u65E7 hash chunk \u88AB\u6E05\u7406\u5373\u5168 404\uFF082026-09-17 \u7528\u6237\u5B9E\u6D4B\u8E29\u5751\uFF09",
|
|
365
|
+
fix: 'nginx \u6539\u4E3A Cache-Control "no-cache"\uFF08\u534F\u5546\u7F13\u5B58\uFF09\uFF1B\u5E26 hash \u7684 assets \u624D\u914D\u957F\u7F13\u5B58'
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
if (!/no-cache|max-age=0/i.test(cc) && !/no-store/i.test(cc)) {
|
|
370
|
+
return {
|
|
371
|
+
res,
|
|
372
|
+
check: {
|
|
373
|
+
app,
|
|
374
|
+
item,
|
|
375
|
+
level: "WARN",
|
|
376
|
+
symptom: `${item} \u7F3A\u5C11 no-cache \u7F13\u5B58\u5934\uFF08Cache-Control=${cc || "\u65E0"}\uFF09\uFF1A${url}`,
|
|
377
|
+
cause: "\u6D4F\u89C8\u5668/\u4E2D\u95F4\u5C42\u53EF\u80FD\u6301\u6709\u65E7\u7248\u672C\uFF0C\u91CD\u90E8\u7F72\u540E\u884C\u4E3A\u4E0D\u53EF\u9884\u671F",
|
|
378
|
+
fix: 'nginx \u4E3A remoteEntry/manifest/index.html \u663E\u5F0F add_header Cache-Control "no-cache"'
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return { res };
|
|
384
|
+
}
|
|
385
|
+
function extractChunkRefs(...sources) {
|
|
386
|
+
const refs = /* @__PURE__ */ new Set();
|
|
387
|
+
const add = (raw) => refs.add(raw.replace(/^\.\//, ""));
|
|
388
|
+
for (const body of sources) {
|
|
389
|
+
if (!body) continue;
|
|
390
|
+
for (const m of body.matchAll(/\.\/([\w./-]+\.js)/g)) add(m[1]);
|
|
391
|
+
for (const m of body.matchAll(/"([\w./-]+\.js)"/g)) add(m[1]);
|
|
392
|
+
for (const m of body.matchAll(/(?:src|href)="([\w./-]+\.js)"/g)) add(m[1]);
|
|
393
|
+
}
|
|
394
|
+
return [...refs];
|
|
395
|
+
}
|
|
396
|
+
function compareSharedVersions(perAppShared) {
|
|
397
|
+
const out = [];
|
|
398
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
399
|
+
for (const { app, shared } of perAppShared) {
|
|
400
|
+
for (const s of shared ?? []) {
|
|
401
|
+
if (!byKey.has(s.name)) byKey.set(s.name, /* @__PURE__ */ new Map());
|
|
402
|
+
const byVer = byKey.get(s.name);
|
|
403
|
+
if (!byVer.has(s.version)) byVer.set(s.version, /* @__PURE__ */ new Set());
|
|
404
|
+
byVer.get(s.version).add(app);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
for (const [key, byVer] of byKey) {
|
|
408
|
+
if (byVer.size <= 1) continue;
|
|
409
|
+
const dist = [...byVer.entries()].map(([v, apps]) => `${v}(${[...apps].join("/")})`).join(" vs ");
|
|
410
|
+
out.push({
|
|
411
|
+
app: "*",
|
|
412
|
+
item: `shared \u7248\u672C skew\uFF1A${key}`,
|
|
413
|
+
level: "WARN",
|
|
414
|
+
symptom: `\u5171\u4EAB\u952E "${key}" \u5728\u591A\u5E94\u7528\u95F4\u7248\u672C\u4E0D\u4E00\u81F4\uFF1A${dist}`,
|
|
415
|
+
cause: "\u975E singleton \u65F6\u9875\u9762\u5C06\u6309\u6700\u9AD8\u7248\u672C\u534F\u5546\uFF08\u5404\u5E94\u7528\u62FF\u5230\u7684\u5B9E\u4F8B\u53EF\u80FD\u4E0D\u540C\uFF09\uFF1Bsingleton \u65F6\u5168\u90E8\u6536\u655B\u5230\u5DF2\u52A0\u8F7D\u5B9E\u4F8B\u2014\u2014\u884C\u4E3A\u9700\u8981\u88AB\u77E5\u6653\u800C\u4E0D\u662F\u649E\u4E0A",
|
|
416
|
+
fix: "\u7EDF\u4E00\u5404\u5E94\u7528\u4F9D\u8D56\u7248\u672C\uFF1Bsingleton \u573A\u666F\u786E\u8BA4 skew \u544A\u8B66\uFF08MFU-010\uFF09\u53EF\u63A5\u53D7"
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
async function runDoctor(opts) {
|
|
422
|
+
const checks = [];
|
|
423
|
+
const perAppShared = [];
|
|
424
|
+
const chunkSample = opts.chunkSample ?? 16;
|
|
425
|
+
for (const app of opts.apps) {
|
|
426
|
+
const root = `${opts.base.replace(/\/$/, "")}/${app.replace(/^\//, "")}`;
|
|
427
|
+
const entry = await checkStatus(`${root}/fulgur-remoteEntry.js`, app, "fulgur-remoteEntry.js", {
|
|
428
|
+
expectJs: true,
|
|
429
|
+
expectNoCache: !opts.dev
|
|
430
|
+
});
|
|
431
|
+
if (entry.check) checks.push(entry.check);
|
|
432
|
+
if (entry.res && !opts.dev) {
|
|
433
|
+
const acao = header(entry.res, "access-control-allow-origin");
|
|
434
|
+
checks.push({
|
|
435
|
+
app,
|
|
436
|
+
item: "remoteEntry CORS",
|
|
437
|
+
level: acao ? "PASS" : "WARN",
|
|
438
|
+
symptom: acao ? `Access-Control-Allow-Origin: ${acao}` : "remoteEntry \u672A\u5E26 Access-Control-Allow-Origin",
|
|
439
|
+
cause: acao ? void 0 : "\u8DE8\u6E90\u8054\u90A6\uFF08dev \u53CC\u7AEF\u53E3/\u5F02\u57DF\u90E8\u7F72\uFF09\u4F1A\u88AB CORS \u62E6\u622A\uFF1B\u540C\u6E90\u90E8\u7F72\u4E0D\u53D7\u5F71\u54CD",
|
|
440
|
+
fix: acao ? void 0 : 'nginx \u5BF9 remoteEntry add_header Access-Control-Allow-Origin "*"'
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
const manifestRes = await checkStatus(`${root}/fulgur-manifest.json`, app, "fulgur-manifest.json", {
|
|
444
|
+
expectNoCache: !opts.dev
|
|
445
|
+
});
|
|
446
|
+
if (manifestRes.check) checks.push(manifestRes.check);
|
|
447
|
+
let manifest;
|
|
448
|
+
if (manifestRes.res?.status === 200) {
|
|
449
|
+
try {
|
|
450
|
+
manifest = JSON.parse(manifestRes.res.body);
|
|
451
|
+
perAppShared.push({ app, shared: manifest.shared ?? [] });
|
|
452
|
+
} catch {
|
|
453
|
+
checks.push({
|
|
454
|
+
app,
|
|
455
|
+
item: "manifest \u89E3\u6790",
|
|
456
|
+
level: "FAIL",
|
|
457
|
+
symptom: "fulgur-manifest.json \u4E0D\u662F\u5408\u6CD5 JSON",
|
|
458
|
+
cause: "\u4EA7\u7269\u4E0D\u5B8C\u6574\u6216\u88AB\u4E2D\u95F4\u5C42\u6539\u5199",
|
|
459
|
+
fix: "\u91CD\u65B0\u6784\u5EFA\u90E8\u7F72\uFF1B\u786E\u8BA4 nginx \u672A\u5BF9\u8BE5\u8DEF\u5F84\u505A sub/\u62FC\u63A5\u6539\u5199"
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
let htmlBody = "";
|
|
464
|
+
if (!opts.dev) {
|
|
465
|
+
const html = await checkStatus(`${root}/index.html`, app, "index.html", { expectNoCache: true });
|
|
466
|
+
if (html.check) checks.push(html.check);
|
|
467
|
+
htmlBody = html.res?.body ?? "";
|
|
468
|
+
}
|
|
469
|
+
if (opts.dev) {
|
|
470
|
+
const devEntry = await checkStatus(`${root}/@fulgur-entry.js`, app, "@fulgur-entry.js\uFF08dev \u5BB9\u5668\u5165\u53E3\uFF09", {
|
|
471
|
+
expectJs: true
|
|
472
|
+
});
|
|
473
|
+
if (devEntry.check) checks.push(devEntry.check);
|
|
474
|
+
}
|
|
475
|
+
if (!opts.dev && manifest) {
|
|
476
|
+
const manifestRefs = [];
|
|
477
|
+
const exposes = manifest.exposes ?? {};
|
|
478
|
+
for (const key of Object.keys(exposes)) {
|
|
479
|
+
const f = exposes[key]?.file;
|
|
480
|
+
if (f) manifestRefs.push(String(f).replace(/^\.\//, ""));
|
|
481
|
+
}
|
|
482
|
+
const htmlRefs = extractChunkRefs(htmlBody);
|
|
483
|
+
const entryRefs = extractChunkRefs(entry.res?.body ?? "");
|
|
484
|
+
const refs = [.../* @__PURE__ */ new Set([...htmlRefs, ...manifestRefs, ...entryRefs])];
|
|
485
|
+
const resolveUrl = (ref) => {
|
|
486
|
+
if (/^(https?:)?\/\//.test(ref)) return ref;
|
|
487
|
+
if (ref.startsWith("/")) return new URL(ref, opts.base).href;
|
|
488
|
+
return `${root}/${ref}`;
|
|
489
|
+
};
|
|
490
|
+
const maskedByFallback = (body) => body.trimStart().startsWith("<");
|
|
491
|
+
for (const ref of refs.slice(0, chunkSample)) {
|
|
492
|
+
const url = resolveUrl(ref);
|
|
493
|
+
try {
|
|
494
|
+
const r = await fetchHeadOrGet(url, "GET");
|
|
495
|
+
if (r.status !== 200) {
|
|
496
|
+
checks.push({
|
|
497
|
+
app,
|
|
498
|
+
item: `chunk \u53EF\u8FBE\uFF1A${ref}`,
|
|
499
|
+
level: "FAIL",
|
|
500
|
+
symptom: `\u5165\u53E3/manifest \u5F15\u7528\u7684 chunk \u8FD4\u56DE ${r.status}\uFF1A${url}`,
|
|
501
|
+
cause: "\u4EA7\u7269\u4E0D\u5B8C\u6574\u6216\u90E8\u7F72\u540E partial \u6E05\u7406\u2014\u2014\u6D4F\u89C8\u5668\u6301\u65E7 remoteEntry \u65F6\u5178\u578B\u75C7\u72B6\uFF08immutable \u5751\uFF09",
|
|
502
|
+
fix: "\u91CD\u65B0\u5B8C\u6574\u90E8\u7F72\u8BE5\u5E94\u7528\uFF08rsync -a --delete dist/<app>/\uFF09\uFF1B\u786E\u8BA4\u7F13\u5B58\u5934\u4E3A no-cache"
|
|
503
|
+
});
|
|
504
|
+
} else if (maskedByFallback(r.body)) {
|
|
505
|
+
checks.push({
|
|
506
|
+
app,
|
|
507
|
+
item: `chunk \u53EF\u8FBE\uFF1A${ref}`,
|
|
508
|
+
level: "FAIL",
|
|
509
|
+
symptom: `chunk \u8BF7\u6C42\u8FD4\u56DE 200 \u4F46\u5185\u5BB9\u662F HTML\uFF08nginx \u6DF1\u94FE\u56DE\u9000\u63A9\u76D6\u4E86\u8D44\u6E90\u7F3A\u5931\uFF09\uFF1A${url}`,
|
|
510
|
+
cause: "try_files \u56DE\u9000\u8FC7\u5BBD\uFF1A\u7F3A\u5931\u7684\u9759\u6001\u8D44\u6E90\u88AB\u515C\u5230\u5BBF\u4E3B index.html\uFF0C\u6D4F\u89C8\u5668\u628A\u5B83\u5F53\u6A21\u5757\u89E3\u6790\u76F4\u63A5\u62A5\u9519",
|
|
511
|
+
fix: "\u4E3A\u9759\u6001 assets \u76EE\u5F55\u589E\u52A0\u7CBE\u786E\u5339\u914D\uFF08try_files $uri =404 \u6216\u72EC\u7ACB location\uFF09\uFF0C\u907F\u514D JS \u8BF7\u6C42\u88AB\u56DE\u9000\u6210 HTML"
|
|
512
|
+
});
|
|
513
|
+
} else {
|
|
514
|
+
for (const sub of extractChunkRefs(r.body)) if (!refs.includes(sub)) refs.push(sub);
|
|
515
|
+
}
|
|
516
|
+
} catch (e) {
|
|
517
|
+
checks.push({
|
|
518
|
+
app,
|
|
519
|
+
item: `chunk \u53EF\u8FBE\uFF1A${ref}`,
|
|
520
|
+
level: "FAIL",
|
|
521
|
+
symptom: `chunk \u8BF7\u6C42\u5931\u8D25\uFF1A${url}\uFF08${String(e.message ?? e)}\uFF09`,
|
|
522
|
+
cause: "\u7F51\u7EDC/\u90E8\u7F72\u9762\u5F02\u5E38",
|
|
523
|
+
fix: "curl \u590D\u6838\u8BE5 URL\uFF1B\u6838\u5BF9\u90E8\u7F72\u5B8C\u6574\u6027"
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
for (const ref of refs.slice(chunkSample, chunkSample * 4)) {
|
|
528
|
+
const url = resolveUrl(ref);
|
|
529
|
+
try {
|
|
530
|
+
const r = await fetchHeadOrGet(url, "HEAD");
|
|
531
|
+
if (r.status !== 200 && r.status !== 404) {
|
|
532
|
+
checks.push({
|
|
533
|
+
app,
|
|
534
|
+
item: `chunk \u53EF\u8FBE\uFF1A${ref}`,
|
|
535
|
+
level: "FAIL",
|
|
536
|
+
symptom: `chunk \u5F15\u7528\u8FD4\u56DE ${r.status}\uFF1A${url}`,
|
|
537
|
+
cause: "\u4EA7\u7269\u4E0D\u5B8C\u6574\u6216\u90E8\u7F72\u540E partial \u6E05\u7406",
|
|
538
|
+
fix: "\u91CD\u65B0\u5B8C\u6574\u90E8\u7F72\u8BE5\u5E94\u7528\uFF08rsync -a --delete dist/<app>/\uFF09"
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
} catch {
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
checks.push(...compareSharedVersions(perAppShared));
|
|
547
|
+
return { checks, failed: checks.some((c) => c.level === "FAIL") };
|
|
548
|
+
}
|
|
549
|
+
function formatDoctorReport(checks) {
|
|
550
|
+
const lines = [];
|
|
551
|
+
for (const c of checks) {
|
|
552
|
+
const head = `[fulgur:doctor] ${c.level} [${c.app}] ${c.item}`;
|
|
553
|
+
lines.push(c.level === "PASS" ? `${head} \u2014 ${c.symptom}` : `${head}
|
|
554
|
+
\u73B0\u8C61\uFF1A${c.symptom}`);
|
|
555
|
+
if (c.cause) lines.push(` \u6839\u56E0\uFF1A${c.cause}`);
|
|
556
|
+
if (c.fix) lines.push(` \u4FEE\u6CD5\uFF1A${c.fix}`);
|
|
557
|
+
}
|
|
558
|
+
const fail = checks.filter((c) => c.level === "FAIL").length;
|
|
559
|
+
const warn = checks.filter((c) => c.level === "WARN").length;
|
|
560
|
+
const pass = checks.filter((c) => c.level === "PASS").length;
|
|
561
|
+
lines.push(`[fulgur:doctor] \u6C47\u603B\uFF1A${pass} PASS / ${warn} WARN / ${fail} FAIL`);
|
|
562
|
+
return lines.join("\n");
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/cli.ts
|
|
566
|
+
var HELP = `fulgur \u2014 Vite Module Federation CLI (@fulgurjs/federation)
|
|
567
|
+
|
|
568
|
+
\u7528\u6CD5\uFF1A
|
|
569
|
+
fulgur init [--template <path>] [--force] \u751F\u6210\u5E26\u6CE8\u91CA\u7684 fulgur.config.ts \u8D77\u6B65\u6A21\u677F
|
|
570
|
+
fulgur init --config <path> \u6821\u9A8C\u914D\u7F6E\uFF1B\u8F93\u51FA\u5404\u5E94\u7528 federation() \u7C98\u8D34\u5757\u3001
|
|
571
|
+
NGINX no-cache \u7AD9\u70B9\u6A21\u677F\u4E0E\u63A5\u5165\u6838\u5BF9\u6E05\u5355
|
|
572
|
+
fulgur doctor --base <URL> --apps <a,b,c> [--dev] [--json] [--chunk-sample N]
|
|
573
|
+
fulgur --help
|
|
574
|
+
|
|
575
|
+
\u793A\u4F8B\uFF1A
|
|
576
|
+
fulgur init # \u5F53\u524D\u76EE\u5F55\u5199 fulgur.config.ts\uFF08\u5DF2\u5B58\u5728\u5219\u62D2\u7EDD\uFF0C--force \u8986\u76D6\uFF09
|
|
577
|
+
fulgur init --config fulgur.config.ts # \u6821\u9A8C + \u8F93\u51FA\u6837\u677F
|
|
578
|
+
fulgur doctor --base http://your-site --apps app-a,app-b
|
|
579
|
+
fulgur doctor --base http://localhost:5173 --apps app-a --dev
|
|
580
|
+
`;
|
|
581
|
+
async function main() {
|
|
582
|
+
const argv = process.argv.slice(2);
|
|
583
|
+
const cmd = argv[0];
|
|
584
|
+
if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
585
|
+
console.log(HELP);
|
|
586
|
+
return 0;
|
|
587
|
+
}
|
|
588
|
+
const argOf = (k) => {
|
|
589
|
+
const i = argv.indexOf(k);
|
|
590
|
+
return i > -1 ? argv[i + 1] : void 0;
|
|
591
|
+
};
|
|
592
|
+
const has = (k) => argv.includes(k);
|
|
593
|
+
if (cmd === "init") {
|
|
594
|
+
const { writeConfigTemplate: writeConfigTemplate2, inspectConfig: inspectConfig2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
595
|
+
const configPath = argOf("--config");
|
|
596
|
+
if (configPath) {
|
|
597
|
+
try {
|
|
598
|
+
console.log(await inspectConfig2(resolve(configPath)));
|
|
599
|
+
return 0;
|
|
600
|
+
} catch (e) {
|
|
601
|
+
console.error(String(e.message ?? e));
|
|
602
|
+
return 2;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const target = resolve(argOf("--template") ?? "fulgur.config.ts");
|
|
606
|
+
const result = await writeConfigTemplate2(target, has("--force"));
|
|
607
|
+
if (result === "exists") {
|
|
608
|
+
console.error(`[fulgur:init] ${target} \u5DF2\u5B58\u5728\uFF0C\u62D2\u7EDD\u8986\u76D6\uFF08--force \u5F3A\u5236\u8986\u76D6\uFF09`);
|
|
609
|
+
return 2;
|
|
610
|
+
}
|
|
611
|
+
console.log(`[fulgur:init] \u5DF2\u751F\u6210\u8D77\u6B65\u6A21\u677F ${target}
|
|
612
|
+
\u540E\u7EED\u6B65\u9AA4\uFF1A
|
|
613
|
+
1. \u7F16\u8F91 fulgur.config.ts\uFF1A\u586B\u5165\u4F60\u7684\u5E94\u7528\u76EE\u5F55/\u5BB9\u5668\u540D/\u7AEF\u53E3/base/\u9875\u9762\u8DEF\u7531\u8868/exposes
|
|
614
|
+
2. npx fulgur init --config fulgur.config.ts # \u6821\u9A8C\u5E76\u8F93\u51FA\u53EF\u7C98\u8D34\u6837\u677F\u4E0E\u6838\u5BF9\u6E05\u5355
|
|
615
|
+
3. \u6309\u6E05\u5355\u628A federation() \u5757\u63A5\u5165\u5404\u5E94\u7528 vite.config.ts\uFF0C\u5B89\u88C5\u4F9D\u8D56
|
|
616
|
+
4. \u90E8\u7F72\u540E\uFF1Anpx fulgur doctor --base <URL> --apps <\u5E94\u7528...>`);
|
|
617
|
+
return 0;
|
|
618
|
+
}
|
|
619
|
+
if (cmd === "doctor") {
|
|
620
|
+
const base = argOf("--base");
|
|
621
|
+
const appsRaw = argOf("--apps") ?? "main";
|
|
622
|
+
if (!base) {
|
|
623
|
+
console.error("[fulgur:doctor] \u7F3A\u5C11 --base <URL>\uFF08\u7AD9\u70B9\u6839\u5730\u5740\uFF0C\u5982 http://your-site\uFF09");
|
|
624
|
+
return 2;
|
|
625
|
+
}
|
|
626
|
+
const apps = appsRaw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
627
|
+
const { checks, failed } = await runDoctor({
|
|
628
|
+
base,
|
|
629
|
+
apps,
|
|
630
|
+
dev: has("--dev"),
|
|
631
|
+
chunkSample: argOf("--chunk-sample") ? Number(argOf("--chunk-sample")) : void 0
|
|
632
|
+
});
|
|
633
|
+
if (has("--json")) {
|
|
634
|
+
console.log(JSON.stringify({ base, apps, dev: has("--dev"), checks, failed }, null, 2));
|
|
635
|
+
} else {
|
|
636
|
+
console.log(formatDoctorReport(checks));
|
|
637
|
+
}
|
|
638
|
+
return failed ? 1 : 0;
|
|
639
|
+
}
|
|
640
|
+
console.error(`\u672A\u77E5\u547D\u4EE4\uFF1A${cmd}
|
|
641
|
+
${HELP}`);
|
|
642
|
+
return 2;
|
|
643
|
+
}
|
|
644
|
+
main().then(
|
|
645
|
+
(code) => process.exit(code),
|
|
646
|
+
(e) => {
|
|
647
|
+
console.error("[fulgur] \u6267\u884C\u5931\u8D25\uFF1A", e);
|
|
648
|
+
process.exit(2);
|
|
649
|
+
}
|
|
650
|
+
);
|