@lark-apaas/coding-preset-vite-react 0.1.1-alpha.0 → 0.1.1-alpha.1
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/lib/index.d.ts +52 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +406 -0
- package/lib/index.js.map +1 -0
- package/lib/plugins/capabilities.d.ts +23 -0
- package/lib/plugins/capabilities.d.ts.map +1 -0
- package/lib/plugins/capabilities.js +151 -0
- package/lib/plugins/capabilities.js.map +1 -0
- package/lib/plugins/dev-logs.d.ts +44 -0
- package/lib/plugins/dev-logs.d.ts.map +1 -0
- package/lib/plugins/dev-logs.js +544 -0
- package/lib/plugins/dev-logs.js.map +1 -0
- package/lib/plugins/error-overlay.d.ts +19 -0
- package/lib/plugins/error-overlay.d.ts.map +1 -0
- package/lib/plugins/error-overlay.js +136 -0
- package/lib/plugins/error-overlay.js.map +1 -0
- package/lib/plugins/fonts-mirror.d.ts +14 -0
- package/lib/plugins/fonts-mirror.d.ts.map +1 -0
- package/lib/plugins/fonts-mirror.js +70 -0
- package/lib/plugins/fonts-mirror.js.map +1 -0
- package/lib/plugins/health.d.ts +19 -0
- package/lib/plugins/health.d.ts.map +1 -0
- package/lib/plugins/health.js +36 -0
- package/lib/plugins/health.js.map +1 -0
- package/lib/plugins/hmr-timing.d.ts +13 -0
- package/lib/plugins/hmr-timing.d.ts.map +1 -0
- package/lib/plugins/hmr-timing.js +93 -0
- package/lib/plugins/hmr-timing.js.map +1 -0
- package/lib/plugins/html-minify.d.ts +18 -0
- package/lib/plugins/html-minify.d.ts.map +1 -0
- package/lib/plugins/html-minify.js +100 -0
- package/lib/plugins/html-minify.js.map +1 -0
- package/lib/plugins/module-alias.d.ts +12 -0
- package/lib/plugins/module-alias.d.ts.map +1 -0
- package/lib/plugins/module-alias.js +78 -0
- package/lib/plugins/module-alias.js.map +1 -0
- package/lib/plugins/og-meta.d.ts +21 -0
- package/lib/plugins/og-meta.d.ts.map +1 -0
- package/lib/plugins/og-meta.js +60 -0
- package/lib/plugins/og-meta.js.map +1 -0
- package/lib/plugins/polyfill.d.ts +11 -0
- package/lib/plugins/polyfill.d.ts.map +1 -0
- package/lib/plugins/polyfill.js +137 -0
- package/lib/plugins/polyfill.js.map +1 -0
- package/lib/plugins/routes.d.ts +26 -0
- package/lib/plugins/routes.d.ts.map +1 -0
- package/lib/plugins/routes.js +265 -0
- package/lib/plugins/routes.js.map +1 -0
- package/lib/plugins/slardar.d.ts +24 -0
- package/lib/plugins/slardar.d.ts.map +1 -0
- package/lib/plugins/slardar.js +74 -0
- package/lib/plugins/slardar.js.map +1 -0
- package/lib/plugins/static-assets.d.ts +10 -0
- package/lib/plugins/static-assets.d.ts.map +1 -0
- package/lib/plugins/static-assets.js +297 -0
- package/lib/plugins/static-assets.js.map +1 -0
- package/lib/plugins/view-context.d.ts +22 -0
- package/lib/plugins/view-context.d.ts.map +1 -0
- package/lib/plugins/view-context.js +132 -0
- package/lib/plugins/view-context.js.map +1 -0
- package/lib/plugins/vite-client-patch.d.ts +4 -0
- package/lib/plugins/vite-client-patch.d.ts.map +1 -0
- package/lib/plugins/vite-client-patch.js +37 -0
- package/lib/plugins/vite-client-patch.js.map +1 -0
- package/lib/plugins/ws-watchdog.d.ts +22 -0
- package/lib/plugins/ws-watchdog.d.ts.map +1 -0
- package/lib/plugins/ws-watchdog.js +103 -0
- package/lib/plugins/ws-watchdog.js.map +1 -0
- package/lib/polyfills/index.d.ts +15 -0
- package/lib/polyfills/index.d.ts.map +1 -0
- package/lib/polyfills/index.js +36 -0
- package/lib/polyfills/index.js.map +1 -0
- package/lib/utils/normalize-base-path.d.ts +15 -0
- package/lib/utils/normalize-base-path.d.ts.map +1 -0
- package/lib/utils/normalize-base-path.js +27 -0
- package/lib/utils/normalize-base-path.js.map +1 -0
- package/lib/utils/snapdom-proxy.d.ts +12 -0
- package/lib/utils/snapdom-proxy.d.ts.map +1 -0
- package/lib/utils/snapdom-proxy.js +75 -0
- package/lib/utils/snapdom-proxy.js.map +1 -0
- package/package.json +10 -16
- package/dist/index.d.ts +0 -320
- package/dist/index.js +0 -1941
- package/dist/index.js.map +0 -1
package/dist/index.js
DELETED
|
@@ -1,1941 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import * as fs9 from "fs";
|
|
3
|
-
import * as path11 from "path";
|
|
4
|
-
import { createRequire } from "module";
|
|
5
|
-
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
6
|
-
import { mergeConfig } from "vite";
|
|
7
|
-
import react from "@vitejs/plugin-react";
|
|
8
|
-
import tailwindcss from "@tailwindcss/vite";
|
|
9
|
-
|
|
10
|
-
// src/plugins/routes.ts
|
|
11
|
-
import * as fs from "fs";
|
|
12
|
-
import * as path from "path";
|
|
13
|
-
import { parse } from "@babel/parser";
|
|
14
|
-
import _traverse from "@babel/traverse";
|
|
15
|
-
import * as t from "@babel/types";
|
|
16
|
-
|
|
17
|
-
// src/utils/normalize-base-path.ts
|
|
18
|
-
function normalizeBasePath(input) {
|
|
19
|
-
if (!input) return "";
|
|
20
|
-
let p = input.trim();
|
|
21
|
-
if (!p || p === "/") return "";
|
|
22
|
-
if (!p.startsWith("/")) p = "/" + p;
|
|
23
|
-
return p.replace(/\/+$/, "");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// src/plugins/routes.ts
|
|
27
|
-
var traverse = _traverse.default ?? _traverse;
|
|
28
|
-
function isRouteComponent(opening) {
|
|
29
|
-
return t.isJSXIdentifier(opening.name) && opening.name.name === "Route";
|
|
30
|
-
}
|
|
31
|
-
function evaluateTemplateLiteral(tpl) {
|
|
32
|
-
if (tpl.quasis.length === 1 && tpl.expressions.length === 0) {
|
|
33
|
-
return tpl.quasis[0].value.raw;
|
|
34
|
-
}
|
|
35
|
-
return tpl.quasis.map((q) => q.value.raw).join("");
|
|
36
|
-
}
|
|
37
|
-
function extractRouteFrame(opening) {
|
|
38
|
-
const frame = {};
|
|
39
|
-
for (const attr of opening.attributes) {
|
|
40
|
-
if (!t.isJSXAttribute(attr) || !t.isJSXIdentifier(attr.name)) continue;
|
|
41
|
-
const name = attr.name.name;
|
|
42
|
-
if (name !== "path" && name !== "index") continue;
|
|
43
|
-
let value;
|
|
44
|
-
if (attr.value) {
|
|
45
|
-
if (t.isStringLiteral(attr.value)) {
|
|
46
|
-
value = attr.value.value;
|
|
47
|
-
} else if (t.isJSXExpressionContainer(attr.value)) {
|
|
48
|
-
const expr = attr.value.expression;
|
|
49
|
-
if (t.isStringLiteral(expr)) value = expr.value;
|
|
50
|
-
else if (t.isTemplateLiteral(expr)) value = evaluateTemplateLiteral(expr);
|
|
51
|
-
else value = true;
|
|
52
|
-
}
|
|
53
|
-
} else {
|
|
54
|
-
value = true;
|
|
55
|
-
}
|
|
56
|
-
if (name === "path" && typeof value === "string") frame.path = value;
|
|
57
|
-
if (name === "index" && value === true) frame.index = true;
|
|
58
|
-
}
|
|
59
|
-
return frame;
|
|
60
|
-
}
|
|
61
|
-
function buildFullPath(stack, current) {
|
|
62
|
-
let fullPath = "";
|
|
63
|
-
for (const parent of stack) {
|
|
64
|
-
if (!parent.path) continue;
|
|
65
|
-
let p = parent.path;
|
|
66
|
-
if (!p.startsWith("/")) p = "/" + p;
|
|
67
|
-
if (p.endsWith("/") && p !== "/") p = p.slice(0, -1);
|
|
68
|
-
fullPath += p === "/" ? "" : p;
|
|
69
|
-
}
|
|
70
|
-
if (current.index) return fullPath || "/";
|
|
71
|
-
if (current.path) {
|
|
72
|
-
const rp = current.path;
|
|
73
|
-
if (rp === "*") return null;
|
|
74
|
-
if (!rp.startsWith("/")) fullPath = `${fullPath}/${rp}`;
|
|
75
|
-
else fullPath = rp;
|
|
76
|
-
if (fullPath === "") fullPath = "/";
|
|
77
|
-
if (!fullPath.startsWith("/")) fullPath = "/" + fullPath;
|
|
78
|
-
return fullPath;
|
|
79
|
-
}
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
function parseRoutes(source, basePath) {
|
|
83
|
-
const defaultPath = basePath ? `${basePath}/` : "/";
|
|
84
|
-
let ast;
|
|
85
|
-
try {
|
|
86
|
-
ast = parse(source, {
|
|
87
|
-
sourceType: "module",
|
|
88
|
-
plugins: [
|
|
89
|
-
"jsx",
|
|
90
|
-
"typescript",
|
|
91
|
-
"decorators-legacy",
|
|
92
|
-
"classProperties",
|
|
93
|
-
"objectRestSpread",
|
|
94
|
-
"functionBind",
|
|
95
|
-
"exportDefaultFrom",
|
|
96
|
-
"exportNamespaceFrom",
|
|
97
|
-
"dynamicImport",
|
|
98
|
-
"nullishCoalescingOperator",
|
|
99
|
-
"optionalChaining"
|
|
100
|
-
]
|
|
101
|
-
});
|
|
102
|
-
} catch (e) {
|
|
103
|
-
return [{ path: defaultPath }];
|
|
104
|
-
}
|
|
105
|
-
const routeSet = /* @__PURE__ */ new Set();
|
|
106
|
-
const stack = [];
|
|
107
|
-
traverse(ast, {
|
|
108
|
-
JSXElement: {
|
|
109
|
-
enter(p) {
|
|
110
|
-
const opening = p.node.openingElement;
|
|
111
|
-
if (isRouteComponent(opening)) {
|
|
112
|
-
stack.push(extractRouteFrame(opening));
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
exit(p) {
|
|
116
|
-
const opening = p.node.openingElement;
|
|
117
|
-
if (!isRouteComponent(opening)) return;
|
|
118
|
-
const current = stack.pop();
|
|
119
|
-
if (!current) return;
|
|
120
|
-
if (current.path === "*") return;
|
|
121
|
-
if (current.path || current.index) {
|
|
122
|
-
const full = buildFullPath(stack, current);
|
|
123
|
-
if (full) routeSet.add(full);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
const routes = Array.from(routeSet).map((p) => ({
|
|
129
|
-
path: basePath ? `${basePath}${p}` : p
|
|
130
|
-
}));
|
|
131
|
-
return routes.length > 0 ? routes : [{ path: defaultPath }];
|
|
132
|
-
}
|
|
133
|
-
function resolveAppPath(rootDir, hint) {
|
|
134
|
-
if (hint) {
|
|
135
|
-
const abs = path.resolve(rootDir, hint);
|
|
136
|
-
return fs.existsSync(abs) ? abs : null;
|
|
137
|
-
}
|
|
138
|
-
for (const rel of ["src/app.tsx", "client/src/app.tsx"]) {
|
|
139
|
-
const abs = path.resolve(rootDir, rel);
|
|
140
|
-
if (fs.existsSync(abs)) return abs;
|
|
141
|
-
}
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
function routesPlugin(options = {}) {
|
|
145
|
-
const envBase = process.env.CLIENT_BASE_PATH || "";
|
|
146
|
-
const buildBase = normalizeBasePath(options.basePath ?? envBase);
|
|
147
|
-
const serveBase = normalizeBasePath(options.serveBasePath ?? envBase);
|
|
148
|
-
let appAbs = null;
|
|
149
|
-
let cached = null;
|
|
150
|
-
function generate(forBase) {
|
|
151
|
-
if (cached && cached.base === forBase) return cached.routes;
|
|
152
|
-
if (!appAbs || !fs.existsSync(appAbs)) {
|
|
153
|
-
const fallback = [{ path: forBase ? `${forBase}/` : "/" }];
|
|
154
|
-
cached = { routes: fallback, base: forBase };
|
|
155
|
-
return fallback;
|
|
156
|
-
}
|
|
157
|
-
const src = fs.readFileSync(appAbs, "utf-8");
|
|
158
|
-
const routes = parseRoutes(src, forBase);
|
|
159
|
-
cached = { routes, base: forBase };
|
|
160
|
-
return routes;
|
|
161
|
-
}
|
|
162
|
-
return {
|
|
163
|
-
name: "miaoda-routes",
|
|
164
|
-
configResolved(config) {
|
|
165
|
-
appAbs = resolveAppPath(config.root, options.appPath);
|
|
166
|
-
},
|
|
167
|
-
configureServer(server) {
|
|
168
|
-
generate("");
|
|
169
|
-
if (appAbs) {
|
|
170
|
-
server.watcher.add(appAbs);
|
|
171
|
-
server.watcher.on("change", (file) => {
|
|
172
|
-
if (file === appAbs) {
|
|
173
|
-
cached = null;
|
|
174
|
-
generate("");
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
const urlPath = serveBase ? `${serveBase}/routes.json` : "/routes.json";
|
|
179
|
-
server.middlewares.use((req2, res, next) => {
|
|
180
|
-
const pathname = req2.url?.split("?")[0];
|
|
181
|
-
if (pathname === urlPath) {
|
|
182
|
-
const routes = generate("");
|
|
183
|
-
res.setHeader("Content-Type", "application/json");
|
|
184
|
-
res.end(JSON.stringify(routes, null, 2));
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
next();
|
|
188
|
-
});
|
|
189
|
-
},
|
|
190
|
-
buildStart() {
|
|
191
|
-
cached = null;
|
|
192
|
-
generate(buildBase);
|
|
193
|
-
},
|
|
194
|
-
generateBundle() {
|
|
195
|
-
const routes = generate(buildBase);
|
|
196
|
-
this.emitFile({
|
|
197
|
-
type: "asset",
|
|
198
|
-
fileName: "routes.json",
|
|
199
|
-
source: JSON.stringify(routes, null, 2)
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// src/plugins/html-minify.ts
|
|
206
|
-
import * as fs2 from "fs";
|
|
207
|
-
import * as path2 from "path";
|
|
208
|
-
import { minify } from "html-minifier-terser";
|
|
209
|
-
async function minifyHtmlWithHbsProtection(html) {
|
|
210
|
-
const placeholders = [];
|
|
211
|
-
let processed = html.replace(/\{\{\{?.+?\}\}\}?/g, (match) => {
|
|
212
|
-
const i = placeholders.length;
|
|
213
|
-
placeholders.push(match);
|
|
214
|
-
return `__HBS_${i}__`;
|
|
215
|
-
});
|
|
216
|
-
processed = await minify(processed, {
|
|
217
|
-
collapseWhitespace: true,
|
|
218
|
-
removeComments: true,
|
|
219
|
-
removeRedundantAttributes: true,
|
|
220
|
-
removeEmptyAttributes: true,
|
|
221
|
-
minifyCSS: true,
|
|
222
|
-
// 不压 JS:inline script 里可能引用了 HBS 占位符
|
|
223
|
-
minifyJS: false
|
|
224
|
-
});
|
|
225
|
-
return processed.replace(/__HBS_(\d+)__/g, (_, i) => placeholders[Number(i)]);
|
|
226
|
-
}
|
|
227
|
-
function htmlMinifyPlugin() {
|
|
228
|
-
let config;
|
|
229
|
-
let isDev = false;
|
|
230
|
-
return {
|
|
231
|
-
name: "miaoda-html-minify",
|
|
232
|
-
configResolved(resolvedConfig) {
|
|
233
|
-
config = resolvedConfig;
|
|
234
|
-
isDev = resolvedConfig.command === "serve";
|
|
235
|
-
},
|
|
236
|
-
async closeBundle() {
|
|
237
|
-
if (isDev || !config) return;
|
|
238
|
-
const htmlPath = path2.resolve(config.build.outDir, "index.html");
|
|
239
|
-
if (!fs2.existsSync(htmlPath)) return;
|
|
240
|
-
try {
|
|
241
|
-
const source = fs2.readFileSync(htmlPath, "utf-8");
|
|
242
|
-
const minified = await minifyHtmlWithHbsProtection(source);
|
|
243
|
-
fs2.writeFileSync(htmlPath, minified);
|
|
244
|
-
} catch (e) {
|
|
245
|
-
console.warn(`[miaoda-html-minify] minify failed, keeping unminified HTML:`, e);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// src/plugins/static-assets.ts
|
|
252
|
-
import * as fs3 from "fs";
|
|
253
|
-
import * as path3 from "path";
|
|
254
|
-
var PREFIX = "@shared/static/";
|
|
255
|
-
var VIRTUAL_PREFIX = "\0@shared/static/";
|
|
256
|
-
var SCRIPT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"];
|
|
257
|
-
var MIME = {
|
|
258
|
-
".json": "application/json",
|
|
259
|
-
".png": "image/png",
|
|
260
|
-
".jpg": "image/jpeg",
|
|
261
|
-
".jpeg": "image/jpeg",
|
|
262
|
-
".gif": "image/gif",
|
|
263
|
-
".svg": "image/svg+xml",
|
|
264
|
-
".webp": "image/webp",
|
|
265
|
-
".ico": "image/x-icon",
|
|
266
|
-
".woff": "font/woff",
|
|
267
|
-
".woff2": "font/woff2",
|
|
268
|
-
".ttf": "font/ttf",
|
|
269
|
-
".eot": "application/vnd.ms-fontobject",
|
|
270
|
-
".otf": "font/otf",
|
|
271
|
-
".css": "text/css",
|
|
272
|
-
".js": "application/javascript",
|
|
273
|
-
".txt": "text/plain",
|
|
274
|
-
".html": "text/html",
|
|
275
|
-
".xml": "application/xml",
|
|
276
|
-
".pdf": "application/pdf",
|
|
277
|
-
".mp3": "audio/mpeg",
|
|
278
|
-
".mp4": "video/mp4",
|
|
279
|
-
".webm": "video/webm"
|
|
280
|
-
};
|
|
281
|
-
function splitQuery(rel) {
|
|
282
|
-
const i = rel.indexOf("?");
|
|
283
|
-
return i === -1 ? [rel, ""] : [rel.slice(0, i), rel.slice(i)];
|
|
284
|
-
}
|
|
285
|
-
function resolveScriptFile(staticDir, cleanPath) {
|
|
286
|
-
const ext = path3.extname(cleanPath).toLowerCase();
|
|
287
|
-
if (ext && SCRIPT_EXTENSIONS.includes(ext)) {
|
|
288
|
-
return path3.join(staticDir, cleanPath);
|
|
289
|
-
}
|
|
290
|
-
if (!ext) {
|
|
291
|
-
for (const e of SCRIPT_EXTENSIONS) {
|
|
292
|
-
const p = path3.join(staticDir, cleanPath + e);
|
|
293
|
-
if (fs3.existsSync(p)) return p;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return null;
|
|
297
|
-
}
|
|
298
|
-
function walkJson(dir, out, prefix = "") {
|
|
299
|
-
if (!fs3.existsSync(dir)) return;
|
|
300
|
-
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
301
|
-
const full = path3.join(dir, entry.name);
|
|
302
|
-
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
303
|
-
if (entry.isDirectory()) {
|
|
304
|
-
walkJson(full, out, rel);
|
|
305
|
-
} else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
306
|
-
try {
|
|
307
|
-
const key = rel.replace(/\.json$/, "");
|
|
308
|
-
out[key] = JSON.parse(fs3.readFileSync(full, "utf-8"));
|
|
309
|
-
} catch (e) {
|
|
310
|
-
console.warn(`[miaoda-static-assets] skip invalid json: ${rel}: ${e instanceof Error ? e.message : String(e)}`);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
function staticAssetsPlugin(options = {}) {
|
|
316
|
-
const { clientBasePath = "", rootDir = process.cwd() } = options;
|
|
317
|
-
const staticDir = path3.resolve(rootDir, "shared/static");
|
|
318
|
-
let isDev = true;
|
|
319
|
-
let jsonCache = null;
|
|
320
|
-
function getJson() {
|
|
321
|
-
if (!isDev && jsonCache) return jsonCache;
|
|
322
|
-
const data = {};
|
|
323
|
-
walkJson(staticDir, data);
|
|
324
|
-
if (!isDev) jsonCache = data;
|
|
325
|
-
return data;
|
|
326
|
-
}
|
|
327
|
-
return {
|
|
328
|
-
name: "miaoda-static-assets",
|
|
329
|
-
// 必须 enforce:'pre':早于 tsconfig paths 和 vite:json transform
|
|
330
|
-
enforce: "pre",
|
|
331
|
-
config(_config, { command }) {
|
|
332
|
-
isDev = command === "serve";
|
|
333
|
-
},
|
|
334
|
-
resolveId: {
|
|
335
|
-
filter: { id: /^@shared\/static\// },
|
|
336
|
-
handler(id) {
|
|
337
|
-
if (!id.startsWith(PREFIX)) return null;
|
|
338
|
-
const rel = id.slice(PREFIX.length);
|
|
339
|
-
const [cleanPath, query] = splitQuery(rel);
|
|
340
|
-
if (query) return path3.join(staticDir, cleanPath) + query;
|
|
341
|
-
const scriptPath = resolveScriptFile(staticDir, cleanPath);
|
|
342
|
-
if (scriptPath) return scriptPath;
|
|
343
|
-
const virtualId = VIRTUAL_PREFIX + cleanPath;
|
|
344
|
-
return cleanPath.endsWith(".json") ? virtualId + ".js" : virtualId;
|
|
345
|
-
}
|
|
346
|
-
},
|
|
347
|
-
load(id) {
|
|
348
|
-
if (!id.startsWith(VIRTUAL_PREFIX)) return null;
|
|
349
|
-
let rel = id.slice(VIRTUAL_PREFIX.length);
|
|
350
|
-
if (rel.endsWith(".json.js")) rel = rel.slice(0, -3);
|
|
351
|
-
const isJson = rel.endsWith(".json");
|
|
352
|
-
if (isJson) {
|
|
353
|
-
if (isDev) {
|
|
354
|
-
const full = path3.join(staticDir, rel);
|
|
355
|
-
try {
|
|
356
|
-
const content = fs3.readFileSync(full, "utf-8");
|
|
357
|
-
return `export default ${content};`;
|
|
358
|
-
} catch (e) {
|
|
359
|
-
console.warn(`[miaoda-static-assets] read json failed: ${rel}`, e);
|
|
360
|
-
return `export default undefined;`;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
const key = rel.replace(/\.json$/, "");
|
|
364
|
-
return `export default window.__STATIC_JSON__[${JSON.stringify(key)}];`;
|
|
365
|
-
}
|
|
366
|
-
let url;
|
|
367
|
-
if (isDev) {
|
|
368
|
-
url = `${clientBasePath}/static/${rel}`;
|
|
369
|
-
} else {
|
|
370
|
-
const cdnPrefix = (process.env.STATIC_ASSETS_BASE_URL ?? "").replace(/\/+$/, "");
|
|
371
|
-
if (!cdnPrefix) {
|
|
372
|
-
console.warn(
|
|
373
|
-
`[miaoda-static-assets] STATIC_ASSETS_BASE_URL is not set; "${rel}" will resolve to a relative URL. Did you forget to inject MIAODA_STATIC_CDN_PREFIX in build.sh?`
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
url = cdnPrefix ? `${cdnPrefix}/${rel}` : `/${rel}`;
|
|
377
|
-
}
|
|
378
|
-
return `export default ${JSON.stringify(url)};`;
|
|
379
|
-
},
|
|
380
|
-
transformIndexHtml() {
|
|
381
|
-
if (isDev) return;
|
|
382
|
-
const data = getJson();
|
|
383
|
-
if (Object.keys(data).length === 0) return;
|
|
384
|
-
const tags = [
|
|
385
|
-
{
|
|
386
|
-
tag: "script",
|
|
387
|
-
children: `window.__STATIC_JSON__ = ${JSON.stringify(data)};`,
|
|
388
|
-
injectTo: "head-prepend"
|
|
389
|
-
}
|
|
390
|
-
];
|
|
391
|
-
return { html: "", tags };
|
|
392
|
-
},
|
|
393
|
-
configureServer(server) {
|
|
394
|
-
if (!fs3.existsSync(staticDir)) return;
|
|
395
|
-
const watcher = server.watcher;
|
|
396
|
-
watcher.add(staticDir);
|
|
397
|
-
const handleChange = (changedPath) => {
|
|
398
|
-
if (!changedPath.startsWith(staticDir)) return;
|
|
399
|
-
const rel = path3.relative(staticDir, changedPath);
|
|
400
|
-
if (rel.endsWith(".json")) {
|
|
401
|
-
const moduleId = VIRTUAL_PREFIX + rel;
|
|
402
|
-
const mod = server.environments.client.moduleGraph.getModuleById(moduleId);
|
|
403
|
-
if (mod) server.environments.client.moduleGraph.invalidateModule(mod);
|
|
404
|
-
server.hot.send({ type: "custom", event: "static-json-update", data: { path: rel } });
|
|
405
|
-
server.hot.send({ type: "full-reload", path: "*" });
|
|
406
|
-
} else {
|
|
407
|
-
server.hot.send({ type: "custom", event: "static-asset-update", data: { path: rel } });
|
|
408
|
-
}
|
|
409
|
-
};
|
|
410
|
-
watcher.on("change", handleChange);
|
|
411
|
-
watcher.on("add", handleChange);
|
|
412
|
-
watcher.on("unlink", (changedPath) => {
|
|
413
|
-
if (!changedPath.startsWith(staticDir)) return;
|
|
414
|
-
if (changedPath.endsWith(".json")) {
|
|
415
|
-
const rel = path3.relative(staticDir, changedPath);
|
|
416
|
-
const moduleId = VIRTUAL_PREFIX + rel;
|
|
417
|
-
const mod = server.environments.client.moduleGraph.getModuleById(moduleId);
|
|
418
|
-
if (mod) server.environments.client.moduleGraph.invalidateModule(mod);
|
|
419
|
-
}
|
|
420
|
-
server.hot.send({ type: "full-reload", path: "*" });
|
|
421
|
-
});
|
|
422
|
-
server.middlewares.use((req2, res, next) => {
|
|
423
|
-
const staticPrefix = `${clientBasePath}/static/`;
|
|
424
|
-
const url = req2.url || "";
|
|
425
|
-
if (!url.startsWith(staticPrefix)) return next();
|
|
426
|
-
const rawRel = url.slice(staticPrefix.length).split("?")[0];
|
|
427
|
-
let rel;
|
|
428
|
-
try {
|
|
429
|
-
rel = decodeURIComponent(rawRel);
|
|
430
|
-
} catch {
|
|
431
|
-
res.statusCode = 400;
|
|
432
|
-
res.end("Bad Request");
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
const filePath = path3.join(staticDir, rel);
|
|
436
|
-
const normalized = path3.normalize(filePath);
|
|
437
|
-
if (!normalized.startsWith(staticDir)) {
|
|
438
|
-
res.statusCode = 403;
|
|
439
|
-
res.end("Forbidden");
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
if (!fs3.existsSync(filePath)) {
|
|
443
|
-
res.statusCode = 404;
|
|
444
|
-
res.end("Not Found");
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
const stat = fs3.statSync(filePath);
|
|
448
|
-
if (stat.isDirectory()) {
|
|
449
|
-
res.statusCode = 403;
|
|
450
|
-
res.end("Forbidden");
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
const ext = path3.extname(filePath).toLowerCase();
|
|
454
|
-
res.setHeader("Content-Type", MIME[ext] || "application/octet-stream");
|
|
455
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
456
|
-
fs3.createReadStream(filePath).pipe(res);
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
// closeBundle 拷贝 shared/static 的逻辑已删除——build.sh 用 rsync 直接拷到
|
|
460
|
-
// dist/output_static/(部署侧识别的目录),plugin 之前拷到 dist/shared/static
|
|
461
|
-
// 是死代码、没人读,而且没排除 .ts/.tsx 还可能误传脚本文件。
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// src/plugins/capabilities.ts
|
|
466
|
-
import * as fs4 from "fs";
|
|
467
|
-
import * as path4 from "path";
|
|
468
|
-
var VIRTUAL_ID = "virtual:capabilities";
|
|
469
|
-
var RESOLVED = "\0" + VIRTUAL_ID;
|
|
470
|
-
var DEFAULT_DIRS = ["shared/capabilities"];
|
|
471
|
-
function capabilitiesBundlePlugin(options = {}) {
|
|
472
|
-
let rootDir = process.cwd();
|
|
473
|
-
function listDirs() {
|
|
474
|
-
if (options.dir) return [path4.resolve(rootDir, options.dir)];
|
|
475
|
-
return DEFAULT_DIRS.map((d) => path4.resolve(rootDir, d));
|
|
476
|
-
}
|
|
477
|
-
function loadMap() {
|
|
478
|
-
const map = {};
|
|
479
|
-
for (const abs of listDirs()) {
|
|
480
|
-
if (!fs4.existsSync(abs)) continue;
|
|
481
|
-
for (const f of fs4.readdirSync(abs)) {
|
|
482
|
-
if (!f.endsWith(".json")) continue;
|
|
483
|
-
try {
|
|
484
|
-
const cfg = JSON.parse(fs4.readFileSync(path4.join(abs, f), "utf-8"));
|
|
485
|
-
if (cfg?.id) map[cfg.id] = cfg;
|
|
486
|
-
} catch (e) {
|
|
487
|
-
console.warn(
|
|
488
|
-
`[miaoda-capabilities] skip invalid json: ${path4.join(abs, f)}: ${e instanceof Error ? e.message : String(e)}`
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
return map;
|
|
494
|
-
}
|
|
495
|
-
return {
|
|
496
|
-
name: "miaoda-capabilities-bundle",
|
|
497
|
-
configResolved(config) {
|
|
498
|
-
rootDir = config.root;
|
|
499
|
-
},
|
|
500
|
-
// 同时通过 depOptimizer 的 Rolldown 注入解析,让 client-toolkit-lite 这种
|
|
501
|
-
// pre-bundle 进来的依赖也能把 `import 'virtual:capabilities'` 解掉。
|
|
502
|
-
// Vite 8 用 Rolldown 替换了 esbuild 做 depOptimize,所以走 rolldownOptions.plugins。
|
|
503
|
-
config() {
|
|
504
|
-
return {
|
|
505
|
-
optimizeDeps: {
|
|
506
|
-
rolldownOptions: {
|
|
507
|
-
plugins: [
|
|
508
|
-
{
|
|
509
|
-
name: "miaoda-capabilities-rolldown",
|
|
510
|
-
resolveId(source) {
|
|
511
|
-
if (source === VIRTUAL_ID) return RESOLVED;
|
|
512
|
-
return null;
|
|
513
|
-
},
|
|
514
|
-
load(id) {
|
|
515
|
-
if (id !== RESOLVED) return null;
|
|
516
|
-
return `export default ${JSON.stringify(loadMap())};`;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
]
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
|
-
},
|
|
524
|
-
resolveId(id) {
|
|
525
|
-
if (id === VIRTUAL_ID) return RESOLVED;
|
|
526
|
-
return null;
|
|
527
|
-
},
|
|
528
|
-
load(id) {
|
|
529
|
-
if (id !== RESOLVED) return null;
|
|
530
|
-
return `export default ${JSON.stringify(loadMap())};`;
|
|
531
|
-
},
|
|
532
|
-
configureServer(server) {
|
|
533
|
-
for (const abs of listDirs()) {
|
|
534
|
-
if (fs4.existsSync(abs)) server.watcher.add(abs);
|
|
535
|
-
}
|
|
536
|
-
const watching = listDirs();
|
|
537
|
-
const handle = (file) => {
|
|
538
|
-
if (!file.endsWith(".json")) return;
|
|
539
|
-
if (!watching.some((d) => file.startsWith(d))) return;
|
|
540
|
-
const mod = server.moduleGraph.getModuleById(RESOLVED);
|
|
541
|
-
if (mod) server.moduleGraph.invalidateModule(mod);
|
|
542
|
-
server.ws.send({ type: "full-reload", path: "*" });
|
|
543
|
-
};
|
|
544
|
-
server.watcher.on("add", handle);
|
|
545
|
-
server.watcher.on("change", handle);
|
|
546
|
-
server.watcher.on("unlink", handle);
|
|
547
|
-
}
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// src/plugins/error-overlay.ts
|
|
552
|
-
import * as fs5 from "fs";
|
|
553
|
-
import * as path5 from "path";
|
|
554
|
-
import { fileURLToPath } from "url";
|
|
555
|
-
var __dirname = path5.dirname(fileURLToPath(import.meta.url));
|
|
556
|
-
var OVERLAY_PATH = "/@error-overlay.js";
|
|
557
|
-
function resolveClientScriptPath() {
|
|
558
|
-
const candidates = [
|
|
559
|
-
path5.resolve(__dirname, "../src/overlay/vite-client.js"),
|
|
560
|
-
// dist 运行时
|
|
561
|
-
path5.resolve(__dirname, "../overlay/vite-client.js")
|
|
562
|
-
// src/plugins 运行时
|
|
563
|
-
];
|
|
564
|
-
return candidates.find((p) => fs5.existsSync(p)) ?? null;
|
|
565
|
-
}
|
|
566
|
-
function processClientScript(clientBasePath) {
|
|
567
|
-
const scriptPath = resolveClientScriptPath();
|
|
568
|
-
if (!scriptPath) return "// error-overlay client script not found";
|
|
569
|
-
let code = fs5.readFileSync(scriptPath, "utf-8");
|
|
570
|
-
const env = {
|
|
571
|
-
"process.env.FORCE_FRAMEWORK_DOMAIN_MAIN": JSON.stringify(
|
|
572
|
-
process.env.FORCE_FRAMEWORK_DOMAIN_MAIN ?? ""
|
|
573
|
-
),
|
|
574
|
-
"process.env.CLIENT_BASE_PATH": JSON.stringify(clientBasePath)
|
|
575
|
-
};
|
|
576
|
-
for (const [k, v] of Object.entries(env)) {
|
|
577
|
-
code = code.replace(new RegExp(k.replace(/\./g, "\\."), "g"), v);
|
|
578
|
-
}
|
|
579
|
-
return code;
|
|
580
|
-
}
|
|
581
|
-
function errorOverlayPlugin(options = {}) {
|
|
582
|
-
const { enabled = true, clientBasePath = "" } = options;
|
|
583
|
-
const overlayPath = clientBasePath + OVERLAY_PATH;
|
|
584
|
-
return {
|
|
585
|
-
name: "miaoda-error-overlay",
|
|
586
|
-
enforce: "pre",
|
|
587
|
-
// 关掉 Vite 原生 overlay
|
|
588
|
-
config(config) {
|
|
589
|
-
if (!enabled) return;
|
|
590
|
-
return {
|
|
591
|
-
server: {
|
|
592
|
-
...config.server,
|
|
593
|
-
hmr: config.server?.hmr === false ? false : {
|
|
594
|
-
...typeof config.server?.hmr === "object" ? config.server.hmr : {},
|
|
595
|
-
overlay: false
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
};
|
|
599
|
-
},
|
|
600
|
-
configureServer(server) {
|
|
601
|
-
if (!enabled) return;
|
|
602
|
-
server.middlewares.use((req2, res, next) => {
|
|
603
|
-
if (req2.url !== overlayPath) return next();
|
|
604
|
-
try {
|
|
605
|
-
const code = processClientScript(clientBasePath);
|
|
606
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
607
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
608
|
-
res.end(code);
|
|
609
|
-
} catch (e) {
|
|
610
|
-
console.error("[miaoda-error-overlay] serve failed:", e);
|
|
611
|
-
res.statusCode = 500;
|
|
612
|
-
res.end(`// error: ${e instanceof Error ? e.message : String(e)}`);
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
},
|
|
616
|
-
transformIndexHtml(html) {
|
|
617
|
-
if (!enabled) return html;
|
|
618
|
-
const tags = [
|
|
619
|
-
{
|
|
620
|
-
tag: "script",
|
|
621
|
-
attrs: { type: "module", src: overlayPath },
|
|
622
|
-
injectTo: "head"
|
|
623
|
-
}
|
|
624
|
-
];
|
|
625
|
-
return { html, tags };
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// src/plugins/hmr-timing.ts
|
|
631
|
-
import * as fs6 from "fs";
|
|
632
|
-
var VALID_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".css", ".json", ".html"];
|
|
633
|
-
function hmrTimingPlugin() {
|
|
634
|
-
let startTime = 0;
|
|
635
|
-
const changedFiles = /* @__PURE__ */ new Set();
|
|
636
|
-
let server = null;
|
|
637
|
-
return {
|
|
638
|
-
name: "miaoda-hmr-timing",
|
|
639
|
-
configureServer(devServer) {
|
|
640
|
-
server = devServer;
|
|
641
|
-
server.watcher.on("change", (file) => {
|
|
642
|
-
startTime = Date.now();
|
|
643
|
-
changedFiles.add(file);
|
|
644
|
-
});
|
|
645
|
-
},
|
|
646
|
-
hotUpdate(_ctx) {
|
|
647
|
-
if (!server || changedFiles.size === 0) return;
|
|
648
|
-
const duration = Date.now() - startTime;
|
|
649
|
-
const cwd = process.cwd();
|
|
650
|
-
const validFiles = [...changedFiles].map((f) => f.replace(cwd, "").replace(/\\/g, "/")).filter((p) => {
|
|
651
|
-
if (!p) return false;
|
|
652
|
-
if (p.includes("/node_modules/")) return false;
|
|
653
|
-
return VALID_EXTENSIONS.some((ext) => p.toLowerCase().endsWith(ext));
|
|
654
|
-
});
|
|
655
|
-
const totalSize = validFiles.reduce((sum, p) => {
|
|
656
|
-
try {
|
|
657
|
-
return sum + fs6.statSync(cwd + p).size;
|
|
658
|
-
} catch {
|
|
659
|
-
return sum;
|
|
660
|
-
}
|
|
661
|
-
}, 0);
|
|
662
|
-
server.hot.send({
|
|
663
|
-
type: "custom",
|
|
664
|
-
event: "hmr-timing",
|
|
665
|
-
data: { duration, fileCount: validFiles.length, fileTotalSize: totalSize }
|
|
666
|
-
});
|
|
667
|
-
changedFiles.clear();
|
|
668
|
-
}
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// src/plugins/ws-watchdog.ts
|
|
673
|
-
import * as fs7 from "fs";
|
|
674
|
-
import * as path6 from "path";
|
|
675
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
676
|
-
var __dirname2 = path6.dirname(fileURLToPath2(import.meta.url));
|
|
677
|
-
var CLIENT_RUNTIME_PATH = "/@ws-watchdog.js";
|
|
678
|
-
function resolveClientRuntimePath() {
|
|
679
|
-
const candidates = [
|
|
680
|
-
path6.resolve(__dirname2, "../src/runtime/ws-watchdog-client.mjs"),
|
|
681
|
-
// dist 运行时
|
|
682
|
-
path6.resolve(__dirname2, "../runtime/ws-watchdog-client.mjs")
|
|
683
|
-
// src/plugins 运行时
|
|
684
|
-
];
|
|
685
|
-
return candidates.find((p) => fs7.existsSync(p)) ?? null;
|
|
686
|
-
}
|
|
687
|
-
function wsWatchdogPlugin(options = {}) {
|
|
688
|
-
const {
|
|
689
|
-
pingEvent = "ws-watchdog:ping",
|
|
690
|
-
pongEvent = "ws-watchdog:pong",
|
|
691
|
-
clientBasePath = ""
|
|
692
|
-
} = options;
|
|
693
|
-
const watchdogPath = clientBasePath + CLIENT_RUNTIME_PATH;
|
|
694
|
-
return {
|
|
695
|
-
name: "miaoda-ws-watchdog",
|
|
696
|
-
apply: "serve",
|
|
697
|
-
configureServer(server) {
|
|
698
|
-
server.ws.on(pingEvent, (data, client) => {
|
|
699
|
-
if (typeof data?.t !== "number") return;
|
|
700
|
-
client.send(pongEvent, { echo: data.t });
|
|
701
|
-
});
|
|
702
|
-
server.middlewares.use((req2, res, next) => {
|
|
703
|
-
if (req2.url !== watchdogPath) return next();
|
|
704
|
-
const clientPath = resolveClientRuntimePath();
|
|
705
|
-
if (!clientPath) {
|
|
706
|
-
res.statusCode = 500;
|
|
707
|
-
res.end("// ws-watchdog client not found");
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
try {
|
|
711
|
-
let code = fs7.readFileSync(clientPath, "utf-8");
|
|
712
|
-
const env = {
|
|
713
|
-
"process.env.FORCE_FRAMEWORK_DOMAIN_MAIN": JSON.stringify(
|
|
714
|
-
process.env.FORCE_FRAMEWORK_DOMAIN_MAIN ?? ""
|
|
715
|
-
),
|
|
716
|
-
"process.env.CLIENT_BASE_PATH": JSON.stringify(clientBasePath)
|
|
717
|
-
};
|
|
718
|
-
for (const [k, v] of Object.entries(env)) {
|
|
719
|
-
code = code.replace(new RegExp(k.replace(/\./g, "\\."), "g"), v);
|
|
720
|
-
}
|
|
721
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
722
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
723
|
-
res.end(code);
|
|
724
|
-
} catch (e) {
|
|
725
|
-
console.error("[miaoda-ws-watchdog] serve failed:", e);
|
|
726
|
-
res.statusCode = 500;
|
|
727
|
-
res.end(`// error: ${e instanceof Error ? e.message : String(e)}`);
|
|
728
|
-
}
|
|
729
|
-
});
|
|
730
|
-
},
|
|
731
|
-
transformIndexHtml() {
|
|
732
|
-
const tags = [
|
|
733
|
-
{
|
|
734
|
-
tag: "script",
|
|
735
|
-
attrs: { type: "module", src: watchdogPath },
|
|
736
|
-
// head-prepend:尽早注入,避免业务代码先和外壳完成握手才出 watchdog
|
|
737
|
-
injectTo: "head-prepend"
|
|
738
|
-
}
|
|
739
|
-
];
|
|
740
|
-
return tags;
|
|
741
|
-
}
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// src/plugins/health.ts
|
|
746
|
-
function healthMiddlewarePlugin(options = {}) {
|
|
747
|
-
const endpoint = options.endpoint ?? "/dev/health";
|
|
748
|
-
let viteReady = false;
|
|
749
|
-
return {
|
|
750
|
-
name: "miaoda-health",
|
|
751
|
-
apply: "serve",
|
|
752
|
-
configureServer(server) {
|
|
753
|
-
const httpServer = server.httpServer;
|
|
754
|
-
if (!httpServer || httpServer.listening) {
|
|
755
|
-
viteReady = true;
|
|
756
|
-
} else {
|
|
757
|
-
httpServer.once("listening", () => {
|
|
758
|
-
viteReady = true;
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
server.middlewares.use(endpoint, (req2, res, next) => {
|
|
762
|
-
if (req2.method && req2.method !== "GET") return next();
|
|
763
|
-
res.setHeader("Content-Type", "application/json");
|
|
764
|
-
res.setHeader("Cache-Control", "no-store");
|
|
765
|
-
res.statusCode = 200;
|
|
766
|
-
res.end(JSON.stringify({ ready: viteReady }));
|
|
767
|
-
});
|
|
768
|
-
}
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
// src/plugins/view-context.ts
|
|
773
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
774
|
-
var SUDA_HEADER = "x-larkgw-suda-webuser";
|
|
775
|
-
var requestContext = new AsyncLocalStorage();
|
|
776
|
-
function parseSudaWebUser(header) {
|
|
777
|
-
if (typeof header !== "string" || !header) return null;
|
|
778
|
-
try {
|
|
779
|
-
return JSON.parse(decodeURIComponent(header));
|
|
780
|
-
} catch {
|
|
781
|
-
return null;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
function parseCsrfToken(cookieHeader) {
|
|
785
|
-
if (!cookieHeader) return "";
|
|
786
|
-
const m = cookieHeader.match(/(?:^|;\s*)suda-csrf-token=([^;]+)/);
|
|
787
|
-
return m ? decodeURIComponent(m[1]) : "";
|
|
788
|
-
}
|
|
789
|
-
function deriveEnvironment(host) {
|
|
790
|
-
if (!host) return "online";
|
|
791
|
-
if (host.includes("boe")) return "staging";
|
|
792
|
-
if (host.includes("feishu-pre") || host.includes("-pre.")) return "gray";
|
|
793
|
-
return "online";
|
|
794
|
-
}
|
|
795
|
-
function buildRenderContext(req2) {
|
|
796
|
-
const user = parseSudaWebUser(req2.headers[SUDA_HEADER]);
|
|
797
|
-
return {
|
|
798
|
-
userId: user?.user_id ?? "",
|
|
799
|
-
tenantId: user?.tenant_id ?? "",
|
|
800
|
-
userName: user?.user_name?.zh_cn ?? "",
|
|
801
|
-
csrfToken: parseCsrfToken(req2.headers.cookie),
|
|
802
|
-
environment: deriveEnvironment(req2.headers.host)
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
function appIdExpr(appId) {
|
|
806
|
-
if (appId) return JSON.stringify(appId);
|
|
807
|
-
return `(function () {
|
|
808
|
-
var p = location.pathname.match(/\\/app\\/(app_[a-z0-9]+)/i);
|
|
809
|
-
if (p) return p[1];
|
|
810
|
-
var h = location.hostname.match(/app_[a-z0-9]+/i);
|
|
811
|
-
return h ? h[0] : '';
|
|
812
|
-
})()`;
|
|
813
|
-
}
|
|
814
|
-
function buildScriptFromRequest(options, ctx) {
|
|
815
|
-
return `
|
|
816
|
-
window.appId = ${appIdExpr(options.appId)};
|
|
817
|
-
window.userId = ${JSON.stringify(ctx.userId)};
|
|
818
|
-
window.tenantId = ${JSON.stringify(ctx.tenantId)};
|
|
819
|
-
window.userName = ${JSON.stringify(ctx.userName)};
|
|
820
|
-
window.csrfToken = ${JSON.stringify(ctx.csrfToken)};
|
|
821
|
-
window.ENVIRONMENT = ${JSON.stringify(ctx.environment)};
|
|
822
|
-
`;
|
|
823
|
-
}
|
|
824
|
-
function buildScriptFallback(options) {
|
|
825
|
-
return `
|
|
826
|
-
window.appId = ${appIdExpr(options.appId)};
|
|
827
|
-
window.userId = '';
|
|
828
|
-
window.tenantId = '';
|
|
829
|
-
window.userName = '';
|
|
830
|
-
window.csrfToken = (function () {
|
|
831
|
-
var m = document.cookie.match(/(?:^|;\\s*)suda-csrf-token=([^;]+)/);
|
|
832
|
-
return m ? decodeURIComponent(m[1]) : '';
|
|
833
|
-
})();
|
|
834
|
-
window.ENVIRONMENT = (function () {
|
|
835
|
-
var h = location.hostname;
|
|
836
|
-
if (h.indexOf('boe') !== -1) return 'staging';
|
|
837
|
-
if (h.indexOf('feishu-pre') !== -1 || h.indexOf('-pre.') !== -1) return 'gray';
|
|
838
|
-
return 'online';
|
|
839
|
-
})();
|
|
840
|
-
`;
|
|
841
|
-
}
|
|
842
|
-
function viewContextPlugin(options = {}) {
|
|
843
|
-
return {
|
|
844
|
-
name: "miaoda-view-context",
|
|
845
|
-
/**
|
|
846
|
-
* dev 期前置 middleware:每个请求都把 render context 绑到 ALS。
|
|
847
|
-
* 后续 `transformIndexHtml` 在同一异步链里 `getStore()` 读出。
|
|
848
|
-
*/
|
|
849
|
-
configureServer(server) {
|
|
850
|
-
server.middlewares.use((req2, _res, next) => {
|
|
851
|
-
const ctx = buildRenderContext(req2);
|
|
852
|
-
requestContext.run(ctx, () => next());
|
|
853
|
-
});
|
|
854
|
-
},
|
|
855
|
-
transformIndexHtml() {
|
|
856
|
-
const ctx = requestContext.getStore();
|
|
857
|
-
const children = ctx ? buildScriptFromRequest(options, ctx) : buildScriptFallback(options);
|
|
858
|
-
const tags = [
|
|
859
|
-
{
|
|
860
|
-
tag: "script",
|
|
861
|
-
children,
|
|
862
|
-
// head-prepend:必须在业务代码 / runtime 之前注入,保证业务可同步读 window 全局
|
|
863
|
-
injectTo: "head-prepend"
|
|
864
|
-
}
|
|
865
|
-
];
|
|
866
|
-
return tags;
|
|
867
|
-
}
|
|
868
|
-
};
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
// src/plugins/module-alias.ts
|
|
872
|
-
import * as path7 from "path";
|
|
873
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
874
|
-
var __dirname3 = path7.dirname(fileURLToPath3(import.meta.url));
|
|
875
|
-
function moduleAliasPlugin() {
|
|
876
|
-
const aliasDir = [
|
|
877
|
-
path7.resolve(__dirname3, "../src/module-alias"),
|
|
878
|
-
path7.resolve(__dirname3, "../module-alias")
|
|
879
|
-
];
|
|
880
|
-
function resolveAlias(name) {
|
|
881
|
-
for (const dir of aliasDir) {
|
|
882
|
-
const p = path7.join(dir, `${name}.mjs`);
|
|
883
|
-
return p;
|
|
884
|
-
}
|
|
885
|
-
return null;
|
|
886
|
-
}
|
|
887
|
-
return {
|
|
888
|
-
name: "miaoda-module-alias",
|
|
889
|
-
enforce: "pre",
|
|
890
|
-
resolveId: {
|
|
891
|
-
filter: { id: /^(clsx|echarts|echarts-for-react)$/ },
|
|
892
|
-
handler(source, importer) {
|
|
893
|
-
if (importer && importer.includes("module-alias")) return null;
|
|
894
|
-
return resolveAlias(source);
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
// src/plugins/og-meta.ts
|
|
901
|
-
var DEFAULT_TAGS = [
|
|
902
|
-
{ property: "og:title", placeholder: "{{appName}}" },
|
|
903
|
-
{ property: "og:description", placeholder: "{{appDescription}}" },
|
|
904
|
-
{ property: "og:image", placeholder: "{{appAvatar}}" }
|
|
905
|
-
];
|
|
906
|
-
function ogMetaPlugin(options = {}) {
|
|
907
|
-
const {
|
|
908
|
-
customTags,
|
|
909
|
-
titlePlaceholder = "{{appName}}",
|
|
910
|
-
descriptionPlaceholder = "{{appDescription}}",
|
|
911
|
-
faviconPlaceholder = "{{appAvatar}}"
|
|
912
|
-
} = options;
|
|
913
|
-
const tags = customTags || DEFAULT_TAGS;
|
|
914
|
-
return {
|
|
915
|
-
name: "miaoda-og-meta",
|
|
916
|
-
transformIndexHtml(html) {
|
|
917
|
-
let result = html;
|
|
918
|
-
for (const { property, placeholder } of tags) {
|
|
919
|
-
const esc = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
920
|
-
const exists = new RegExp(
|
|
921
|
-
`<meta\\s+[^>]*property=["']?${esc}["']?[^>]*>`,
|
|
922
|
-
"i"
|
|
923
|
-
).test(result);
|
|
924
|
-
if (exists) {
|
|
925
|
-
result = result.replace(
|
|
926
|
-
new RegExp(
|
|
927
|
-
`(<meta\\s+[^>]*property=["']?${esc}["']?[^>]*content=)["'][^"']*["']([^>]*>)`,
|
|
928
|
-
"gi"
|
|
929
|
-
),
|
|
930
|
-
`$1"${placeholder}"$2`
|
|
931
|
-
);
|
|
932
|
-
} else {
|
|
933
|
-
result = result.replace(
|
|
934
|
-
"</head>",
|
|
935
|
-
`
|
|
936
|
-
<meta property="${property}" content="${placeholder}">
|
|
937
|
-
</head>`
|
|
938
|
-
);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
if (/<title>[^<]*<\/title>/i.test(result)) {
|
|
942
|
-
result = result.replace(/<title>[^<]*<\/title>/gi, `<title>${titlePlaceholder}</title>`);
|
|
943
|
-
} else {
|
|
944
|
-
result = result.replace("</head>", `
|
|
945
|
-
<title>${titlePlaceholder}</title>
|
|
946
|
-
</head>`);
|
|
947
|
-
}
|
|
948
|
-
const descRe = /<meta\b[^>]*\bname=["']description["'][^>]*>/i;
|
|
949
|
-
if (descRe.test(result)) {
|
|
950
|
-
result = result.replace(
|
|
951
|
-
/<meta\b[^>]*\bname=["']description["'][^>]*>/gi,
|
|
952
|
-
(tag) => {
|
|
953
|
-
if (/\bcontent=/.test(tag)) {
|
|
954
|
-
return tag.replace(
|
|
955
|
-
/\bcontent=(["'])([\s\S]*?)\1/i,
|
|
956
|
-
`content="${descriptionPlaceholder}"`
|
|
957
|
-
);
|
|
958
|
-
}
|
|
959
|
-
return tag.replace(/>$/, ` content="${descriptionPlaceholder}">`);
|
|
960
|
-
}
|
|
961
|
-
);
|
|
962
|
-
} else {
|
|
963
|
-
result = result.replace(
|
|
964
|
-
"</head>",
|
|
965
|
-
`
|
|
966
|
-
<meta name="description" content="${descriptionPlaceholder}">
|
|
967
|
-
</head>`
|
|
968
|
-
);
|
|
969
|
-
}
|
|
970
|
-
const iconRe = /<link\s+[^>]*rel=["']?(?:icon|shortcut icon)["']?[^>]*>/i;
|
|
971
|
-
if (iconRe.test(result)) {
|
|
972
|
-
result = result.replace(
|
|
973
|
-
/(<link\s+[^>]*rel=["']?(?:icon|shortcut icon)["']?[^>]*href=)["'][^"']*["']([^>]*>)/gi,
|
|
974
|
-
`$1"${faviconPlaceholder}"$2`
|
|
975
|
-
);
|
|
976
|
-
} else {
|
|
977
|
-
result = result.replace(
|
|
978
|
-
"</head>",
|
|
979
|
-
`
|
|
980
|
-
<link rel="icon" href="${faviconPlaceholder}">
|
|
981
|
-
</head>`
|
|
982
|
-
);
|
|
983
|
-
}
|
|
984
|
-
return result;
|
|
985
|
-
}
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
// src/plugins/fonts-mirror.ts
|
|
990
|
-
var DEFAULT_MIRROR = "https://miaoda.feishu.cn/fonts";
|
|
991
|
-
var GOOGLE_FONTS_RE = /https?:\/\/fonts\.(googleapis|gstatic)\.com/g;
|
|
992
|
-
var HAS_GOOGLE_FONTS = /fonts\.(googleapis|gstatic)\.com/;
|
|
993
|
-
var TRANSFORM_EXT = /\.(html|css|scss|sass|less|styl|js|jsx|ts|tsx|mjs|cjs|vue|svelte)(\?|$)/;
|
|
994
|
-
function fontsMirrorPlugin(options = {}) {
|
|
995
|
-
const envDisabled = process.env.MIAODA_FONTS_MIRROR_OFF === "1";
|
|
996
|
-
if (options.disabled || envDisabled) return false;
|
|
997
|
-
const mirror = options.mirror ?? DEFAULT_MIRROR;
|
|
998
|
-
const warn = options.warnOnMatch ?? false;
|
|
999
|
-
const exclude = options.exclude ?? [];
|
|
1000
|
-
const shouldExclude = (id) => exclude.some((p) => typeof p === "string" ? id.includes(p) : p.test(id));
|
|
1001
|
-
const stats = { files: 0, occurrences: 0 };
|
|
1002
|
-
return {
|
|
1003
|
-
name: "miaoda-fonts-mirror",
|
|
1004
|
-
enforce: "pre",
|
|
1005
|
-
buildStart() {
|
|
1006
|
-
stats.files = 0;
|
|
1007
|
-
stats.occurrences = 0;
|
|
1008
|
-
},
|
|
1009
|
-
transformIndexHtml(html) {
|
|
1010
|
-
if (!HAS_GOOGLE_FONTS.test(html)) return html;
|
|
1011
|
-
const matches = html.match(GOOGLE_FONTS_RE);
|
|
1012
|
-
const replaced = html.replace(GOOGLE_FONTS_RE, mirror);
|
|
1013
|
-
stats.files += 1;
|
|
1014
|
-
stats.occurrences += matches?.length ?? 0;
|
|
1015
|
-
if (warn) {
|
|
1016
|
-
console.warn(`[miaoda-fonts-mirror] rewrote ${matches?.length ?? 0} occurrence(s) in index.html`);
|
|
1017
|
-
}
|
|
1018
|
-
return replaced;
|
|
1019
|
-
},
|
|
1020
|
-
transform(code, id) {
|
|
1021
|
-
if (!TRANSFORM_EXT.test(id)) return null;
|
|
1022
|
-
if (shouldExclude(id)) return null;
|
|
1023
|
-
if (!HAS_GOOGLE_FONTS.test(code)) return null;
|
|
1024
|
-
const matches = code.match(GOOGLE_FONTS_RE);
|
|
1025
|
-
if (!matches) return null;
|
|
1026
|
-
stats.files += 1;
|
|
1027
|
-
stats.occurrences += matches.length;
|
|
1028
|
-
if (warn) {
|
|
1029
|
-
console.warn(`[miaoda-fonts-mirror] rewrote ${matches.length} occurrence(s) in ${id}`);
|
|
1030
|
-
}
|
|
1031
|
-
return { code: code.replace(GOOGLE_FONTS_RE, mirror), map: null };
|
|
1032
|
-
},
|
|
1033
|
-
closeBundle() {
|
|
1034
|
-
if (stats.files > 0) {
|
|
1035
|
-
console.log(
|
|
1036
|
-
`[miaoda-fonts-mirror] \u2713 rewrote ${stats.occurrences} occurrence(s) across ${stats.files} file(s) \u2192 ${mirror}`
|
|
1037
|
-
);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
// src/plugins/polyfill.ts
|
|
1044
|
-
import * as path8 from "path";
|
|
1045
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1046
|
-
var __dirname4 = path8.dirname(fileURLToPath4(import.meta.url));
|
|
1047
|
-
function polyfillPlugin() {
|
|
1048
|
-
let config;
|
|
1049
|
-
return {
|
|
1050
|
-
name: "miaoda-polyfill",
|
|
1051
|
-
apply: "build",
|
|
1052
|
-
enforce: "post",
|
|
1053
|
-
configResolved(c) {
|
|
1054
|
-
config = c;
|
|
1055
|
-
},
|
|
1056
|
-
async generateBundle(_options, bundle) {
|
|
1057
|
-
const entry = [
|
|
1058
|
-
path8.resolve(__dirname4, "../src/polyfills/index"),
|
|
1059
|
-
path8.resolve(__dirname4, "../polyfills/index")
|
|
1060
|
-
];
|
|
1061
|
-
const { build } = await import("vite");
|
|
1062
|
-
let polyfillCode = "";
|
|
1063
|
-
try {
|
|
1064
|
-
const result = await build({
|
|
1065
|
-
configFile: false,
|
|
1066
|
-
logLevel: "silent",
|
|
1067
|
-
build: {
|
|
1068
|
-
write: false,
|
|
1069
|
-
lib: {
|
|
1070
|
-
entry: entry[0],
|
|
1071
|
-
formats: ["iife"],
|
|
1072
|
-
name: "polyfills",
|
|
1073
|
-
fileName: "polyfills"
|
|
1074
|
-
},
|
|
1075
|
-
minify: true,
|
|
1076
|
-
rollupOptions: {
|
|
1077
|
-
output: { inlineDynamicImports: true }
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
});
|
|
1081
|
-
const output = Array.isArray(result) ? result[0] : result;
|
|
1082
|
-
polyfillCode = "output" in output ? output.output[0]?.code || "" : "";
|
|
1083
|
-
this.emitFile({
|
|
1084
|
-
type: "asset",
|
|
1085
|
-
fileName: "polyfills.js",
|
|
1086
|
-
source: polyfillCode
|
|
1087
|
-
});
|
|
1088
|
-
console.log(
|
|
1089
|
-
`[miaoda-polyfill] generated polyfills.js (${Math.round(polyfillCode.length / 1024)}KB)`
|
|
1090
|
-
);
|
|
1091
|
-
} catch (error) {
|
|
1092
|
-
console.error("[miaoda-polyfill] build failed:", error);
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
const base = config.base || "/";
|
|
1096
|
-
const polyfillUrl = `${base}polyfills.js`.replace(/\/+/g, "/").replace(":/", "://");
|
|
1097
|
-
const htmlFiles = Object.keys(bundle).filter((name) => name.endsWith(".html"));
|
|
1098
|
-
for (const file of htmlFiles) {
|
|
1099
|
-
const chunk = bundle[file];
|
|
1100
|
-
if (chunk.type !== "asset") continue;
|
|
1101
|
-
let htmlContent = typeof chunk.source === "string" ? chunk.source : Buffer.from(chunk.source).toString("utf-8");
|
|
1102
|
-
const detection = buildDetectionScript(polyfillUrl);
|
|
1103
|
-
htmlContent = htmlContent.replace(/<head([^>]*)>/i, `<head$1>
|
|
1104
|
-
${detection}`);
|
|
1105
|
-
chunk.source = htmlContent;
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
};
|
|
1109
|
-
}
|
|
1110
|
-
function buildDetectionScript(polyfillUrl) {
|
|
1111
|
-
return `<script>
|
|
1112
|
-
(function(){
|
|
1113
|
-
var needsPolyfill=(function(){
|
|
1114
|
-
try{
|
|
1115
|
-
if(typeof crypto==="undefined"||typeof crypto.randomUUID!=="function")return true;
|
|
1116
|
-
if(typeof Object.hasOwn!=="function")return true;
|
|
1117
|
-
if(typeof[].at!=="function")return true;
|
|
1118
|
-
if(typeof[].findLast!=="function")return true;
|
|
1119
|
-
return false;
|
|
1120
|
-
}catch(e){return true}
|
|
1121
|
-
})();
|
|
1122
|
-
if(needsPolyfill){
|
|
1123
|
-
document.write('<script src="${polyfillUrl}"><\\/script>');
|
|
1124
|
-
}
|
|
1125
|
-
})();
|
|
1126
|
-
</script>`;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
// src/plugins/vite-client-patch.ts
|
|
1130
|
-
var RECONNECT_RELOAD_PATTERN = /(await waitForSuccessfulPing\(url\.href\);)\r?\n(\s*)location\.reload\(\);/;
|
|
1131
|
-
function viteClientPatchPlugin() {
|
|
1132
|
-
return {
|
|
1133
|
-
name: "miaoda-vite-client-patch",
|
|
1134
|
-
enforce: "post",
|
|
1135
|
-
apply: "serve",
|
|
1136
|
-
transform(code, id) {
|
|
1137
|
-
if (!id.includes("vite/dist/client/client.mjs")) return;
|
|
1138
|
-
if (!RECONNECT_RELOAD_PATTERN.test(code)) {
|
|
1139
|
-
console.warn(
|
|
1140
|
-
"[miaoda-vite-client-patch] reconnect-reload pattern not found in @vite/client. patch NOT applied \u2014 Vite client.mjs may have changed."
|
|
1141
|
-
);
|
|
1142
|
-
return;
|
|
1143
|
-
}
|
|
1144
|
-
const patched = code.replace(RECONNECT_RELOAD_PATTERN, (_, pingLine) => pingLine);
|
|
1145
|
-
return { code: patched };
|
|
1146
|
-
}
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
// src/plugins/slardar.ts
|
|
1151
|
-
function buildSlardarScript(bid, globalName) {
|
|
1152
|
-
return `
|
|
1153
|
-
const slardarScript = document.createElement('script');
|
|
1154
|
-
slardarScript.src = 'https://lf3-short.ibytedapm.com/slardar/fe/sdk-web/browser.cn.js?bid=${bid}&globalName=${globalName}';
|
|
1155
|
-
slardarScript.crossOrigin = 'anonymous';
|
|
1156
|
-
slardarScript.onload = function() {
|
|
1157
|
-
if (window.${globalName}) {
|
|
1158
|
-
window.${globalName}('context.merge', {
|
|
1159
|
-
tenantId: window.tenantId ?? '',
|
|
1160
|
-
appId: window.appId ?? '',
|
|
1161
|
-
});
|
|
1162
|
-
window.${globalName}('init', {
|
|
1163
|
-
bid: '${bid}',
|
|
1164
|
-
env: window.ENVIRONMENT || 'online',
|
|
1165
|
-
userId: window.userId ?? '',
|
|
1166
|
-
});
|
|
1167
|
-
window.${globalName}('start');
|
|
1168
|
-
}
|
|
1169
|
-
};
|
|
1170
|
-
slardarScript.onerror = function() {
|
|
1171
|
-
console.warn('Failed to load Slardar script');
|
|
1172
|
-
};
|
|
1173
|
-
document.head.appendChild(slardarScript);
|
|
1174
|
-
`;
|
|
1175
|
-
}
|
|
1176
|
-
function buildTeaScript() {
|
|
1177
|
-
return `
|
|
1178
|
-
(function (win, export_obj) {
|
|
1179
|
-
win['LogAnalyticsObject'] = export_obj;
|
|
1180
|
-
if (!win[export_obj]) {
|
|
1181
|
-
function _collect() { _collect.q.push(arguments); }
|
|
1182
|
-
_collect.q = _collect.q || [];
|
|
1183
|
-
win[export_obj] = _collect;
|
|
1184
|
-
}
|
|
1185
|
-
win[export_obj].l = +new Date();
|
|
1186
|
-
})(window, 'collectEvent');
|
|
1187
|
-
|
|
1188
|
-
const teaScript = document.createElement('script');
|
|
1189
|
-
teaScript.src = 'https://lf3-cdn-tos.bytescm.com/obj/static/log-sdk/collect/5.1/collect.js';
|
|
1190
|
-
teaScript.crossOrigin = 'anonymous';
|
|
1191
|
-
teaScript.async = true;
|
|
1192
|
-
teaScript.onerror = function() {
|
|
1193
|
-
console.warn('Failed to load Tea script');
|
|
1194
|
-
};
|
|
1195
|
-
document.head.appendChild(teaScript);
|
|
1196
|
-
`;
|
|
1197
|
-
}
|
|
1198
|
-
function slardarPlugin(options = {}) {
|
|
1199
|
-
const { bid = "apaas_miaoda", globalName = "KSlardarWeb" } = options;
|
|
1200
|
-
return {
|
|
1201
|
-
name: "miaoda-slardar",
|
|
1202
|
-
transformIndexHtml(html) {
|
|
1203
|
-
const tags = [
|
|
1204
|
-
{ tag: "script", children: buildSlardarScript(bid, globalName), injectTo: "head-prepend" },
|
|
1205
|
-
{
|
|
1206
|
-
tag: "script",
|
|
1207
|
-
attrs: {
|
|
1208
|
-
src: "https://sf3-scmcdn-cn.feishucdn.com/obj/unpkg/byted/performance/0.1.2/dist/performance.iife.js",
|
|
1209
|
-
crossorigin: "anonymous",
|
|
1210
|
-
defer: true
|
|
1211
|
-
},
|
|
1212
|
-
injectTo: "head-prepend"
|
|
1213
|
-
},
|
|
1214
|
-
{ tag: "script", children: buildTeaScript(), injectTo: "head-prepend" }
|
|
1215
|
-
];
|
|
1216
|
-
return { html, tags };
|
|
1217
|
-
}
|
|
1218
|
-
};
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
// src/plugins/dev-logs.ts
|
|
1222
|
-
import * as crypto from "crypto";
|
|
1223
|
-
import * as fs8 from "fs";
|
|
1224
|
-
import * as path9 from "path";
|
|
1225
|
-
import { createInterface } from "readline";
|
|
1226
|
-
var DEFAULT_BODY_LIMIT = 20 * 1024 * 1024;
|
|
1227
|
-
function ensureDir(dir) {
|
|
1228
|
-
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
1229
|
-
}
|
|
1230
|
-
function resolveLogFilePath(baseDir, fileName) {
|
|
1231
|
-
const sanitized = fileName.replace(/\\/g, "/");
|
|
1232
|
-
const segments = sanitized.split("/").filter(Boolean);
|
|
1233
|
-
if (segments.some((seg) => seg === "..")) {
|
|
1234
|
-
throw new Error("Invalid log file path");
|
|
1235
|
-
}
|
|
1236
|
-
const resolved = path9.join(baseDir, segments.join("/"));
|
|
1237
|
-
const rel = path9.relative(baseDir, resolved);
|
|
1238
|
-
if (rel.startsWith("..")) {
|
|
1239
|
-
throw new Error("Access to the specified log file is denied");
|
|
1240
|
-
}
|
|
1241
|
-
return resolved;
|
|
1242
|
-
}
|
|
1243
|
-
function parsePositiveInt(value, fallback) {
|
|
1244
|
-
if (!value || !value.trim()) return fallback;
|
|
1245
|
-
const n = Number(value);
|
|
1246
|
-
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
1247
|
-
}
|
|
1248
|
-
function parseLimit(value, def, max) {
|
|
1249
|
-
if (!value || !value.trim()) return def;
|
|
1250
|
-
const n = Number(value);
|
|
1251
|
-
if (Number.isFinite(n) && n > 0) return Math.min(Math.floor(n), max);
|
|
1252
|
-
return def;
|
|
1253
|
-
}
|
|
1254
|
-
function extractLogLevel(text) {
|
|
1255
|
-
const lower = text.toLowerCase();
|
|
1256
|
-
if (lower.includes("fatal") || lower.includes("critical")) return "fatal";
|
|
1257
|
-
if (lower.includes("error") || lower.includes("<e>") || lower.includes("\u2716")) return "error";
|
|
1258
|
-
if (lower.includes("warn") || lower.includes("<w>") || lower.includes("\u26A0")) return "warn";
|
|
1259
|
-
if (lower.includes("debug") || lower.includes("<d>")) return "debug";
|
|
1260
|
-
if (lower.includes("verbose") || lower.includes("trace")) return "verbose";
|
|
1261
|
-
return "log";
|
|
1262
|
-
}
|
|
1263
|
-
function parseStdLog(line, source) {
|
|
1264
|
-
const id = crypto.randomUUID();
|
|
1265
|
-
const m = line.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] \[(server|client)\] (.*)$/);
|
|
1266
|
-
if (!m) {
|
|
1267
|
-
return {
|
|
1268
|
-
id,
|
|
1269
|
-
level: "log",
|
|
1270
|
-
timestamp: Date.now(),
|
|
1271
|
-
message: line,
|
|
1272
|
-
context: null,
|
|
1273
|
-
traceId: null,
|
|
1274
|
-
userId: null,
|
|
1275
|
-
appId: null,
|
|
1276
|
-
tenantId: null,
|
|
1277
|
-
stack: null,
|
|
1278
|
-
meta: null,
|
|
1279
|
-
tags: [source]
|
|
1280
|
-
};
|
|
1281
|
-
}
|
|
1282
|
-
const [, timeStr, , content] = m;
|
|
1283
|
-
let timestamp = new Date(timeStr.replace(" ", "T")).getTime();
|
|
1284
|
-
if (Number.isNaN(timestamp)) timestamp = Date.now();
|
|
1285
|
-
return {
|
|
1286
|
-
id,
|
|
1287
|
-
level: extractLogLevel(content),
|
|
1288
|
-
timestamp,
|
|
1289
|
-
message: content,
|
|
1290
|
-
context: null,
|
|
1291
|
-
traceId: null,
|
|
1292
|
-
userId: null,
|
|
1293
|
-
appId: null,
|
|
1294
|
-
tenantId: null,
|
|
1295
|
-
stack: null,
|
|
1296
|
-
meta: null,
|
|
1297
|
-
tags: [source]
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
var LOG_FILES = [{ fileName: "client.std.log", source: "client-std", parser: parseStdLog }];
|
|
1301
|
-
async function readLogsBySource(logDir, source) {
|
|
1302
|
-
const config = LOG_FILES.find((c) => c.source === source);
|
|
1303
|
-
if (!config) return [];
|
|
1304
|
-
const filePath = path9.join(logDir, config.fileName);
|
|
1305
|
-
if (!fs8.existsSync(filePath)) return [];
|
|
1306
|
-
const out = [];
|
|
1307
|
-
const stream = fs8.createReadStream(filePath, { encoding: "utf-8" });
|
|
1308
|
-
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
1309
|
-
try {
|
|
1310
|
-
for await (const line of rl) {
|
|
1311
|
-
if (!line.trim()) continue;
|
|
1312
|
-
try {
|
|
1313
|
-
const entry = config.parser(line, source);
|
|
1314
|
-
if (entry) out.push(entry);
|
|
1315
|
-
} catch {
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
} finally {
|
|
1319
|
-
rl.close();
|
|
1320
|
-
stream.close();
|
|
1321
|
-
}
|
|
1322
|
-
return out;
|
|
1323
|
-
}
|
|
1324
|
-
async function readServerLogs(logDir, opts = {}) {
|
|
1325
|
-
const limit = opts.limit ?? 100;
|
|
1326
|
-
const offset = opts.offset ?? 0;
|
|
1327
|
-
const sources = opts.sources ?? LOG_FILES.map((c) => c.source);
|
|
1328
|
-
const all = [];
|
|
1329
|
-
for (const source of sources) {
|
|
1330
|
-
try {
|
|
1331
|
-
const logs = await readLogsBySource(logDir, source);
|
|
1332
|
-
all.push(...logs);
|
|
1333
|
-
} catch (e) {
|
|
1334
|
-
console.warn(
|
|
1335
|
-
`[miaoda-dev-logs] read ${source} failed: ${e instanceof Error ? e.message : String(e)}`
|
|
1336
|
-
);
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
if (all.length === 0) return null;
|
|
1340
|
-
let filtered = all;
|
|
1341
|
-
if (opts.levels && opts.levels.length > 0) {
|
|
1342
|
-
filtered = all.filter((l) => opts.levels.includes(l.level));
|
|
1343
|
-
}
|
|
1344
|
-
filtered.sort((a, b) => b.timestamp - a.timestamp);
|
|
1345
|
-
return {
|
|
1346
|
-
logs: filtered.slice(offset, offset + limit),
|
|
1347
|
-
total: filtered.length,
|
|
1348
|
-
hasMore: offset + limit < filtered.length
|
|
1349
|
-
};
|
|
1350
|
-
}
|
|
1351
|
-
var LogWatcher = class {
|
|
1352
|
-
logDir = "";
|
|
1353
|
-
watchers = /* @__PURE__ */ new Map();
|
|
1354
|
-
positions = /* @__PURE__ */ new Map();
|
|
1355
|
-
subscribers = /* @__PURE__ */ new Set();
|
|
1356
|
-
running = false;
|
|
1357
|
-
start(logDir) {
|
|
1358
|
-
if (this.running) return;
|
|
1359
|
-
this.logDir = logDir;
|
|
1360
|
-
this.running = true;
|
|
1361
|
-
for (const cfg of LOG_FILES) this.watchFile(cfg);
|
|
1362
|
-
}
|
|
1363
|
-
stop() {
|
|
1364
|
-
if (!this.running) return;
|
|
1365
|
-
this.running = false;
|
|
1366
|
-
for (const w of this.watchers.values()) {
|
|
1367
|
-
try {
|
|
1368
|
-
w.close();
|
|
1369
|
-
} catch {
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
this.watchers.clear();
|
|
1373
|
-
this.positions.clear();
|
|
1374
|
-
}
|
|
1375
|
-
onLog(cb) {
|
|
1376
|
-
this.subscribers.add(cb);
|
|
1377
|
-
return () => this.subscribers.delete(cb);
|
|
1378
|
-
}
|
|
1379
|
-
getSubscriberCount() {
|
|
1380
|
-
return this.subscribers.size;
|
|
1381
|
-
}
|
|
1382
|
-
watchFile(cfg) {
|
|
1383
|
-
const filePath = path9.join(this.logDir, cfg.fileName);
|
|
1384
|
-
if (!fs8.existsSync(filePath)) return;
|
|
1385
|
-
try {
|
|
1386
|
-
this.positions.set(cfg.fileName, fs8.statSync(filePath).size);
|
|
1387
|
-
} catch {
|
|
1388
|
-
this.positions.set(cfg.fileName, 0);
|
|
1389
|
-
}
|
|
1390
|
-
try {
|
|
1391
|
-
const watcher = fs8.watch(filePath, (event) => {
|
|
1392
|
-
if (event === "change") this.handleChange(cfg);
|
|
1393
|
-
});
|
|
1394
|
-
watcher.on("error", () => this.restart(cfg));
|
|
1395
|
-
this.watchers.set(cfg.fileName, watcher);
|
|
1396
|
-
} catch {
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
restart(cfg) {
|
|
1400
|
-
const existing = this.watchers.get(cfg.fileName);
|
|
1401
|
-
if (existing) {
|
|
1402
|
-
try {
|
|
1403
|
-
existing.close();
|
|
1404
|
-
} catch {
|
|
1405
|
-
}
|
|
1406
|
-
this.watchers.delete(cfg.fileName);
|
|
1407
|
-
}
|
|
1408
|
-
setTimeout(() => {
|
|
1409
|
-
if (this.running) this.watchFile(cfg);
|
|
1410
|
-
}, 1e3);
|
|
1411
|
-
}
|
|
1412
|
-
handleChange(cfg) {
|
|
1413
|
-
const filePath = path9.join(this.logDir, cfg.fileName);
|
|
1414
|
-
const last = this.positions.get(cfg.fileName) ?? 0;
|
|
1415
|
-
let stats;
|
|
1416
|
-
try {
|
|
1417
|
-
stats = fs8.statSync(filePath);
|
|
1418
|
-
} catch {
|
|
1419
|
-
return;
|
|
1420
|
-
}
|
|
1421
|
-
const cur = stats.size;
|
|
1422
|
-
if (cur < last) {
|
|
1423
|
-
this.positions.set(cfg.fileName, 0);
|
|
1424
|
-
this.handleChange(cfg);
|
|
1425
|
-
return;
|
|
1426
|
-
}
|
|
1427
|
-
if (cur === last) return;
|
|
1428
|
-
const buf = Buffer.alloc(cur - last);
|
|
1429
|
-
const fd = fs8.openSync(filePath, "r");
|
|
1430
|
-
try {
|
|
1431
|
-
fs8.readSync(fd, buf, 0, cur - last, last);
|
|
1432
|
-
} finally {
|
|
1433
|
-
fs8.closeSync(fd);
|
|
1434
|
-
}
|
|
1435
|
-
this.positions.set(cfg.fileName, cur);
|
|
1436
|
-
for (const line of buf.toString("utf-8").split("\n")) {
|
|
1437
|
-
if (!line.trim()) continue;
|
|
1438
|
-
try {
|
|
1439
|
-
const entry = cfg.parser(line, cfg.source);
|
|
1440
|
-
if (entry) this.notify(entry);
|
|
1441
|
-
} catch {
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
notify(entry) {
|
|
1446
|
-
for (const sub of this.subscribers) {
|
|
1447
|
-
try {
|
|
1448
|
-
sub(entry);
|
|
1449
|
-
} catch {
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
};
|
|
1454
|
-
function formatSSE(event, data) {
|
|
1455
|
-
return `event: ${event}
|
|
1456
|
-
data: ${JSON.stringify(data)}
|
|
1457
|
-
|
|
1458
|
-
`;
|
|
1459
|
-
}
|
|
1460
|
-
async function readLogFilePage(filePath, page, pageSize) {
|
|
1461
|
-
if (!fs8.existsSync(filePath)) return null;
|
|
1462
|
-
const capacity = page * pageSize;
|
|
1463
|
-
const buffer = [];
|
|
1464
|
-
let totalLines = 0;
|
|
1465
|
-
const stream = fs8.createReadStream(filePath, { encoding: "utf-8" });
|
|
1466
|
-
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
1467
|
-
try {
|
|
1468
|
-
for await (const line of rl) {
|
|
1469
|
-
buffer.push(line);
|
|
1470
|
-
if (buffer.length > capacity) buffer.shift();
|
|
1471
|
-
totalLines += 1;
|
|
1472
|
-
}
|
|
1473
|
-
} finally {
|
|
1474
|
-
rl.close();
|
|
1475
|
-
stream.close();
|
|
1476
|
-
}
|
|
1477
|
-
const totalPages = totalLines === 0 ? 0 : Math.ceil(totalLines / pageSize);
|
|
1478
|
-
if (buffer.length === 0) return { page, pageSize, totalLines, totalPages, lines: [] };
|
|
1479
|
-
const startIndex = Math.max(totalLines - page * pageSize, 0);
|
|
1480
|
-
const endIndex = Math.max(totalLines - (page - 1) * pageSize, 0);
|
|
1481
|
-
const bufferStartIndex = totalLines - buffer.length;
|
|
1482
|
-
const lines = [];
|
|
1483
|
-
for (let i = buffer.length - 1; i >= 0; i -= 1) {
|
|
1484
|
-
const lineIndex = bufferStartIndex + i;
|
|
1485
|
-
if (lineIndex >= startIndex && lineIndex < endIndex) lines.push(buffer[i]);
|
|
1486
|
-
}
|
|
1487
|
-
return { page, pageSize, totalLines, totalPages, lines: lines.reverse() };
|
|
1488
|
-
}
|
|
1489
|
-
function readJsonBody(req2, limit) {
|
|
1490
|
-
return new Promise((resolve10, reject) => {
|
|
1491
|
-
let size = 0;
|
|
1492
|
-
const chunks = [];
|
|
1493
|
-
req2.on("data", (chunk) => {
|
|
1494
|
-
size += chunk.length;
|
|
1495
|
-
if (size > limit) {
|
|
1496
|
-
reject(new Error(`request body exceeded ${limit} bytes`));
|
|
1497
|
-
req2.destroy();
|
|
1498
|
-
return;
|
|
1499
|
-
}
|
|
1500
|
-
chunks.push(chunk);
|
|
1501
|
-
});
|
|
1502
|
-
req2.on("end", () => {
|
|
1503
|
-
try {
|
|
1504
|
-
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
1505
|
-
resolve10(raw ? JSON.parse(raw) : null);
|
|
1506
|
-
} catch (e) {
|
|
1507
|
-
reject(e);
|
|
1508
|
-
}
|
|
1509
|
-
});
|
|
1510
|
-
req2.on("error", reject);
|
|
1511
|
-
});
|
|
1512
|
-
}
|
|
1513
|
-
function writeJson(res, status, body) {
|
|
1514
|
-
res.statusCode = status;
|
|
1515
|
-
res.setHeader("Content-Type", "application/json");
|
|
1516
|
-
res.setHeader("Cache-Control", "no-store");
|
|
1517
|
-
res.end(JSON.stringify(body));
|
|
1518
|
-
}
|
|
1519
|
-
function devLogsPlugin(options = {}) {
|
|
1520
|
-
const {
|
|
1521
|
-
logDir = process.env.LOG_DIR || "./logs",
|
|
1522
|
-
fileName = "client.log",
|
|
1523
|
-
bodyLimit = DEFAULT_BODY_LIMIT,
|
|
1524
|
-
basePath = ""
|
|
1525
|
-
} = options;
|
|
1526
|
-
const absLogDir = path9.isAbsolute(logDir) ? logDir : path9.join(process.cwd(), logDir);
|
|
1527
|
-
const filePath = path9.join(absLogDir, fileName);
|
|
1528
|
-
const collectPath = `${basePath}/dev/logs/collect`;
|
|
1529
|
-
const collectBatchPath = `${basePath}/dev/logs/collect-batch`;
|
|
1530
|
-
const filesPrefix = `${basePath}/dev/logs/files/`;
|
|
1531
|
-
const serverLogsPath = `${basePath}/dev/logs/server-logs`;
|
|
1532
|
-
const serverLogsStreamPath = `${basePath}/dev/logs/server-logs/stream`;
|
|
1533
|
-
function appendOne(entry) {
|
|
1534
|
-
const line = JSON.stringify({ ...entry, server_time: (/* @__PURE__ */ new Date()).toISOString() }) + "\n";
|
|
1535
|
-
return fs8.promises.appendFile(filePath, line);
|
|
1536
|
-
}
|
|
1537
|
-
function appendMany(entries) {
|
|
1538
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1539
|
-
const lines = entries.map((e) => JSON.stringify({ ...e, server_time: ts }) + "\n").join("");
|
|
1540
|
-
return fs8.promises.appendFile(filePath, lines);
|
|
1541
|
-
}
|
|
1542
|
-
return {
|
|
1543
|
-
name: "miaoda-dev-logs",
|
|
1544
|
-
apply: "serve",
|
|
1545
|
-
configureServer(server) {
|
|
1546
|
-
ensureDir(absLogDir);
|
|
1547
|
-
const watcher = new LogWatcher();
|
|
1548
|
-
const sseClients = /* @__PURE__ */ new Set();
|
|
1549
|
-
server.middlewares.use(async (req2, res, next) => {
|
|
1550
|
-
const url = req2.url?.split("?")[0] ?? "";
|
|
1551
|
-
if (req2.method === "GET" && url === serverLogsStreamPath) {
|
|
1552
|
-
res.setHeader("Content-Type", "text/event-stream");
|
|
1553
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
1554
|
-
res.setHeader("Connection", "keep-alive");
|
|
1555
|
-
res.setHeader("X-Accel-Buffering", "no");
|
|
1556
|
-
res.socket?.setTimeout(0);
|
|
1557
|
-
const clientId = `sse_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
1558
|
-
sseClients.add(res);
|
|
1559
|
-
if (watcher.getSubscriberCount() === 0) watcher.start(absLogDir);
|
|
1560
|
-
const sendEvent = (event, data) => {
|
|
1561
|
-
try {
|
|
1562
|
-
res.write(formatSSE(event, data));
|
|
1563
|
-
return true;
|
|
1564
|
-
} catch {
|
|
1565
|
-
return false;
|
|
1566
|
-
}
|
|
1567
|
-
};
|
|
1568
|
-
const unsubscribe = watcher.onLog((entry) => sendEvent("log", entry));
|
|
1569
|
-
sendEvent("connected", { clientId, timestamp: Date.now() });
|
|
1570
|
-
const heartbeat = setInterval(() => {
|
|
1571
|
-
if (!sendEvent("heartbeat", { timestamp: Date.now() })) clearInterval(heartbeat);
|
|
1572
|
-
}, 3e4);
|
|
1573
|
-
const cleanup = () => {
|
|
1574
|
-
clearInterval(heartbeat);
|
|
1575
|
-
unsubscribe();
|
|
1576
|
-
sseClients.delete(res);
|
|
1577
|
-
if (watcher.getSubscriberCount() === 0) watcher.stop();
|
|
1578
|
-
};
|
|
1579
|
-
req2.on("close", cleanup);
|
|
1580
|
-
req2.on("error", cleanup);
|
|
1581
|
-
return;
|
|
1582
|
-
}
|
|
1583
|
-
if (req2.method === "GET" && url === serverLogsPath) {
|
|
1584
|
-
const qs = new URLSearchParams(req2.url?.split("?")[1] || "");
|
|
1585
|
-
const limit = parseLimit(qs.get("limit") ?? void 0, 100, 1e3);
|
|
1586
|
-
const offset = parsePositiveInt(qs.get("offset") ?? void 0, 0);
|
|
1587
|
-
const levels = qs.get("levels")?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1588
|
-
const sources = qs.get("sources")?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1589
|
-
try {
|
|
1590
|
-
const result = await readServerLogs(absLogDir, { limit, offset, levels, sources });
|
|
1591
|
-
if (!result) {
|
|
1592
|
-
writeJson(res, 404, {
|
|
1593
|
-
message: "No log files found",
|
|
1594
|
-
hint: `Expected files: ${LOG_FILES.map((c) => c.fileName).join(", ")}`
|
|
1595
|
-
});
|
|
1596
|
-
return;
|
|
1597
|
-
}
|
|
1598
|
-
writeJson(res, 200, result);
|
|
1599
|
-
} catch (e) {
|
|
1600
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
1601
|
-
writeJson(res, 500, { message: "Failed to read server logs", error: { message } });
|
|
1602
|
-
}
|
|
1603
|
-
return;
|
|
1604
|
-
}
|
|
1605
|
-
if (req2.method === "GET" && url.startsWith(filesPrefix)) {
|
|
1606
|
-
const fileName2 = decodeURIComponent(url.slice(filesPrefix.length));
|
|
1607
|
-
if (!fileName2) {
|
|
1608
|
-
writeJson(res, 400, { message: "fileName is required" });
|
|
1609
|
-
return;
|
|
1610
|
-
}
|
|
1611
|
-
const qs = new URLSearchParams(req2.url?.split("?")[1] || "");
|
|
1612
|
-
const page = parsePositiveInt(qs.get("page") ?? void 0, 1);
|
|
1613
|
-
const pageSize = parseLimit(qs.get("pageSize") ?? void 0, 200, 2e3);
|
|
1614
|
-
try {
|
|
1615
|
-
const filePath2 = resolveLogFilePath(absLogDir, fileName2);
|
|
1616
|
-
const result = await readLogFilePage(filePath2, page, pageSize);
|
|
1617
|
-
if (!result) {
|
|
1618
|
-
writeJson(res, 404, { message: "log file not found", file: fileName2 });
|
|
1619
|
-
return;
|
|
1620
|
-
}
|
|
1621
|
-
writeJson(res, 200, { file: path9.relative(absLogDir, filePath2), ...result });
|
|
1622
|
-
} catch (e) {
|
|
1623
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
1624
|
-
writeJson(res, 500, { message: "Failed to read log file", error: { message } });
|
|
1625
|
-
}
|
|
1626
|
-
return;
|
|
1627
|
-
}
|
|
1628
|
-
if (req2.method === "POST" && (url === collectPath || url === collectBatchPath)) {
|
|
1629
|
-
try {
|
|
1630
|
-
const body = await readJsonBody(req2, bodyLimit);
|
|
1631
|
-
if (url === collectPath) {
|
|
1632
|
-
const entry = body;
|
|
1633
|
-
if (!entry?.message) {
|
|
1634
|
-
writeJson(res, 400, { message: "message is required" });
|
|
1635
|
-
return;
|
|
1636
|
-
}
|
|
1637
|
-
await appendOne(entry);
|
|
1638
|
-
writeJson(res, 200, { success: true });
|
|
1639
|
-
} else {
|
|
1640
|
-
if (!Array.isArray(body)) {
|
|
1641
|
-
writeJson(res, 400, { message: "logContents must be an array" });
|
|
1642
|
-
return;
|
|
1643
|
-
}
|
|
1644
|
-
await appendMany(body);
|
|
1645
|
-
writeJson(res, 200, { success: true });
|
|
1646
|
-
}
|
|
1647
|
-
} catch (e) {
|
|
1648
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
1649
|
-
writeJson(res, 500, { message: "Failed to collect logs", error: { message } });
|
|
1650
|
-
}
|
|
1651
|
-
return;
|
|
1652
|
-
}
|
|
1653
|
-
next();
|
|
1654
|
-
});
|
|
1655
|
-
}
|
|
1656
|
-
};
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
// src/utils/snapdom-proxy.ts
|
|
1660
|
-
import * as path10 from "path";
|
|
1661
|
-
function registerSnapDomProxyMiddleware(middlewares, options = {}) {
|
|
1662
|
-
const { proxyUrl = "/dev/snapdom-proxy", baseUrl = "/" } = options;
|
|
1663
|
-
const targetUrl = path10.join(baseUrl, proxyUrl);
|
|
1664
|
-
middlewares.use(async (req2, res, next) => {
|
|
1665
|
-
if (req2.method !== "GET" || !req2.url?.startsWith(targetUrl)) {
|
|
1666
|
-
return next();
|
|
1667
|
-
}
|
|
1668
|
-
try {
|
|
1669
|
-
const url = new URL(req2.url, `http://${req2.headers.host}`);
|
|
1670
|
-
let targetParam = url.searchParams.get("url");
|
|
1671
|
-
if (!targetParam) {
|
|
1672
|
-
res.statusCode = 400;
|
|
1673
|
-
res.setHeader("Content-Type", "application/json");
|
|
1674
|
-
res.end(JSON.stringify({ error: "Missing url parameter" }));
|
|
1675
|
-
return;
|
|
1676
|
-
}
|
|
1677
|
-
targetParam = decodeURIComponent(targetParam);
|
|
1678
|
-
const response = await fetch(targetParam, {
|
|
1679
|
-
headers: { cookie: req2.headers.cookie || "" }
|
|
1680
|
-
});
|
|
1681
|
-
const contentType = response.headers.get("content-type");
|
|
1682
|
-
if (contentType) res.setHeader("Content-Type", contentType);
|
|
1683
|
-
const buffer = await response.arrayBuffer();
|
|
1684
|
-
res.end(Buffer.from(buffer));
|
|
1685
|
-
} catch (error) {
|
|
1686
|
-
res.statusCode = 500;
|
|
1687
|
-
res.setHeader("Content-Type", "application/json");
|
|
1688
|
-
res.end(
|
|
1689
|
-
JSON.stringify({
|
|
1690
|
-
error: "Proxy request failed",
|
|
1691
|
-
message: error.message
|
|
1692
|
-
})
|
|
1693
|
-
);
|
|
1694
|
-
}
|
|
1695
|
-
});
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
// src/index.ts
|
|
1699
|
-
var req = createRequire(import.meta.url);
|
|
1700
|
-
var __dirname5 = path11.dirname(fileURLToPath5(import.meta.url));
|
|
1701
|
-
function resolveClientPath(rootDir, rel) {
|
|
1702
|
-
const legacy = path11.resolve(rootDir, "client", rel);
|
|
1703
|
-
if (fs9.existsSync(legacy)) return legacy;
|
|
1704
|
-
return path11.resolve(rootDir, rel);
|
|
1705
|
-
}
|
|
1706
|
-
function readAppFlags(rootDir) {
|
|
1707
|
-
try {
|
|
1708
|
-
const pkg = JSON.parse(fs9.readFileSync(path11.resolve(rootDir, "package.json"), "utf-8"));
|
|
1709
|
-
return pkg.flags || {};
|
|
1710
|
-
} catch {
|
|
1711
|
-
return {};
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
function readRouteDefinitions(rootDir) {
|
|
1715
|
-
let pageRoutes = [];
|
|
1716
|
-
let apiRoutes = [];
|
|
1717
|
-
try {
|
|
1718
|
-
const p = path11.resolve(rootDir, "dist/page-routes.json");
|
|
1719
|
-
if (fs9.existsSync(p)) pageRoutes = JSON.parse(fs9.readFileSync(p, "utf-8"));
|
|
1720
|
-
} catch {
|
|
1721
|
-
}
|
|
1722
|
-
try {
|
|
1723
|
-
const p = path11.resolve(rootDir, "dist/api-routes.json");
|
|
1724
|
-
if (fs9.existsSync(p)) apiRoutes = JSON.parse(fs9.readFileSync(p, "utf-8"));
|
|
1725
|
-
} catch {
|
|
1726
|
-
}
|
|
1727
|
-
return { pageRoutes, apiRoutes };
|
|
1728
|
-
}
|
|
1729
|
-
function tryLoadInspector(enabled) {
|
|
1730
|
-
if (!enabled) return [];
|
|
1731
|
-
try {
|
|
1732
|
-
const mod = req("@lark-apaas/vite-inspector-plugin");
|
|
1733
|
-
return [mod.inspectorPlugin()];
|
|
1734
|
-
} catch {
|
|
1735
|
-
return [];
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
function defineConfig(overrides = {}) {
|
|
1739
|
-
const isDev = process.env.NODE_ENV !== "production";
|
|
1740
|
-
const rootDir = process.cwd();
|
|
1741
|
-
const clientBasePath = normalizeBasePath(process.env.CLIENT_BASE_PATH);
|
|
1742
|
-
const assetsCdnPath = normalizeBasePath(process.env.ASSETS_CDN_PATH);
|
|
1743
|
-
const publicPath = isDev ? clientBasePath : assetsCdnPath || clientBasePath;
|
|
1744
|
-
const base = publicPath ? `${publicPath}/` : "/";
|
|
1745
|
-
const isNewPathFormat = /^\/app\/[^/]/.test(clientBasePath);
|
|
1746
|
-
const appId = isNewPathFormat ? clientBasePath.replace(/\/+$/, "").split("/").pop() : void 0;
|
|
1747
|
-
const appFlags = readAppFlags(rootDir);
|
|
1748
|
-
const { pageRoutes, apiRoutes } = readRouteDefinitions(rootDir);
|
|
1749
|
-
const enableInspector = isDev && process.env.DISABLE_INSPECTOR !== "true";
|
|
1750
|
-
const baseConfig = {
|
|
1751
|
-
root: rootDir,
|
|
1752
|
-
base,
|
|
1753
|
-
plugins: [
|
|
1754
|
-
// dev only: HMR 重连不刷页面(沙箱链路下避免 ~18s 死连接超时)
|
|
1755
|
-
isDev && viteClientPatchPlugin(),
|
|
1756
|
-
// virtual:capabilities —— client-toolkit-lite / client-capability SDK 强依赖;
|
|
1757
|
-
// 即便业务工程没有 capability JSON 也要把虚拟模块注册成空 map,否则 build fail
|
|
1758
|
-
capabilitiesBundlePlugin(),
|
|
1759
|
-
// dev only: 自定义 HMR 错误浮层(替 Vite 原生)
|
|
1760
|
-
isDev && errorOverlayPlugin({ clientBasePath }),
|
|
1761
|
-
// @shared/static/* 路径解析 —— enforce: 'pre',早于 tsconfig paths 和 vite:json
|
|
1762
|
-
staticAssetsPlugin({ clientBasePath, rootDir }),
|
|
1763
|
-
// inspector 必须排在 react() 之前 —— 两者都 enforce:'pre',inspector 需要原 JSX
|
|
1764
|
-
// 还在的时候打 data-* 标记;react 转译完就剩 _jsxDEV() 调用了
|
|
1765
|
-
...tryLoadInspector(enableInspector),
|
|
1766
|
-
react(),
|
|
1767
|
-
tailwindcss(),
|
|
1768
|
-
// clsx / echarts / echarts-for-react alias —— 让所有 echarts 子路径指向 preset 自带版本
|
|
1769
|
-
moduleAliasPlugin(),
|
|
1770
|
-
// routes.json(dev 内存 + middleware;build emitFile)
|
|
1771
|
-
routesPlugin({
|
|
1772
|
-
appPath: fs9.existsSync(path11.resolve(rootDir, "client/src/app.tsx")) ? "./client/src/app.tsx" : "./src/app.tsx",
|
|
1773
|
-
serveBasePath: clientBasePath,
|
|
1774
|
-
// dev 显式空 base —— 沙箱网关把 prefix 剥掉了,直接 / 访问
|
|
1775
|
-
...isDev ? { basePath: "" } : {}
|
|
1776
|
-
}),
|
|
1777
|
-
// OG Meta tags + title + favicon 注入 HBS 占位符({{appName}} / {{appAvatar}} 等)
|
|
1778
|
-
// 由部署运行时(vefaas)调 platform API 拿 appInfo 后做 hbs replace;
|
|
1779
|
-
// dev 预览看到的是字面占位符,业务侧用 useAppInfo + useEffect setTitle 兜底
|
|
1780
|
-
ogMetaPlugin(),
|
|
1781
|
-
// head 顺序敏感:slardar 必须排在 viewContext **之前**——
|
|
1782
|
-
// Vite 同 bucket(head-prepend)是"后执行者插更前"。
|
|
1783
|
-
// 数组里 slardar 在前 → HTML 里 viewContext 在 slardar 之上 → onload 时拿得到 window.tenantId
|
|
1784
|
-
slardarPlugin({ bid: "apaas_miaoda" }),
|
|
1785
|
-
viewContextPlugin({ appId: isDev ? appId : void 0 }),
|
|
1786
|
-
// prod only: 老浏览器 polyfill(按需加载)
|
|
1787
|
-
!isDev && polyfillPlugin(),
|
|
1788
|
-
// build-time HTML minify(HBS 占位符保护)
|
|
1789
|
-
htmlMinifyPlugin(),
|
|
1790
|
-
// dev only DX 插件
|
|
1791
|
-
isDev && hmrTimingPlugin(),
|
|
1792
|
-
isDev && wsWatchdogPlugin({ clientBasePath }),
|
|
1793
|
-
isDev && healthMiddlewarePlugin(),
|
|
1794
|
-
// 客户端日志收集 endpoint(POST /dev/logs/collect{,-batch})
|
|
1795
|
-
isDev && devLogsPlugin({ basePath: clientBasePath }),
|
|
1796
|
-
// Google Fonts → 妙搭字体代理(编译时字符串 replace;dev / build 都有用)
|
|
1797
|
-
fontsMirrorPlugin()
|
|
1798
|
-
].filter(Boolean),
|
|
1799
|
-
resolve: {
|
|
1800
|
-
// Vite 8 原生 tsconfig paths 解析
|
|
1801
|
-
tsconfigPaths: true,
|
|
1802
|
-
// 跟 rspack-preset 对齐的 mainFields 顺序
|
|
1803
|
-
mainFields: ["module", "browser", "main"],
|
|
1804
|
-
extensions: [".js", ".jsx", ".ts", ".tsx", ".json"],
|
|
1805
|
-
alias: {
|
|
1806
|
-
// 所有 echarts 子路径解析到 preset 自带的 echarts,避免第三方扩展(如
|
|
1807
|
-
// echarts-wordcloud)拉独立 echarts 副本导致注册不到同一实例
|
|
1808
|
-
echarts: path11.dirname(req.resolve("echarts/package.json")),
|
|
1809
|
-
// Inspector dev-only CSS:dev 走 inspector 运行时注入,build 给空 stub
|
|
1810
|
-
"@/inspector.dev.css": path11.resolve(__dirname5, "../src/empty.css"),
|
|
1811
|
-
// prod:inspector 整包替成空 stub,避免进生产 bundle
|
|
1812
|
-
...!isDev ? {
|
|
1813
|
-
"@lark-apaas/miaoda-inspector": path11.resolve(__dirname5, "../src/inspector-stub.js")
|
|
1814
|
-
} : {}
|
|
1815
|
-
}
|
|
1816
|
-
},
|
|
1817
|
-
define: {
|
|
1818
|
-
// Node global polyfill(sockjs-client 等老库要)
|
|
1819
|
-
global: "globalThis",
|
|
1820
|
-
"process.env.NODE_ENV": JSON.stringify(isDev ? "development" : "production"),
|
|
1821
|
-
"process.env.runtimeMode": JSON.stringify("fullstack"),
|
|
1822
|
-
"process.env.CLIENT_BASE_PATH": JSON.stringify(clientBasePath),
|
|
1823
|
-
__APP_BASE_PATH__: JSON.stringify(clientBasePath),
|
|
1824
|
-
// 未设置时替换为字面量 `undefined`,让 `??` fallback 正确触发(`""`/`undefined` 行为不同)
|
|
1825
|
-
"process.env.FORCE_FRAMEWORK_DOMAIN_MAIN": process.env.FORCE_FRAMEWORK_DOMAIN_MAIN ? JSON.stringify(process.env.FORCE_FRAMEWORK_DOMAIN_MAIN) : "undefined",
|
|
1826
|
-
"process.env.CWD": JSON.stringify(""),
|
|
1827
|
-
"process.env.__RUNTIME_INJECTED__": JSON.stringify("true"),
|
|
1828
|
-
"process.env.BUILD_TOOL": JSON.stringify("vite"),
|
|
1829
|
-
"process.env.APP_FLAGS": JSON.stringify(appFlags),
|
|
1830
|
-
// 路由定义(jsPage 用空数组,业务代码引用时不至于 undefined)
|
|
1831
|
-
"process.env.__PAGE_ROUTE_DEFINITIONS__": JSON.stringify(JSON.stringify(pageRoutes)),
|
|
1832
|
-
"process.env.__API_ROUTE_DEFINITIONS__": JSON.stringify(JSON.stringify(apiRoutes)),
|
|
1833
|
-
"process.env.MIAODA_APP_TYPE": process.env.MIAODA_APP_TYPE ? JSON.stringify(process.env.MIAODA_APP_TYPE) : "undefined"
|
|
1834
|
-
},
|
|
1835
|
-
server: {
|
|
1836
|
-
port: Number(process.env.CLIENT_DEV_PORT) || 8080,
|
|
1837
|
-
host: process.env.CLIENT_DEV_HOST || "localhost",
|
|
1838
|
-
strictPort: true,
|
|
1839
|
-
cors: true,
|
|
1840
|
-
allowedHosts: true,
|
|
1841
|
-
headers: {
|
|
1842
|
-
...process.env.FORCE_FRAMEWORK_DOMAIN_MAIN ? { "Access-Control-Allow-Origin": process.env.FORCE_FRAMEWORK_DOMAIN_MAIN } : {}
|
|
1843
|
-
},
|
|
1844
|
-
watch: {
|
|
1845
|
-
usePolling: true,
|
|
1846
|
-
interval: 500,
|
|
1847
|
-
ignored: ["**/node_modules/**", "**/dist/**"]
|
|
1848
|
-
},
|
|
1849
|
-
hmr: process.env.FORCE_FRAMEWORK_DOMAIN_MAIN ? { protocol: "wss", path: "/ws" } : { path: "/ws" }
|
|
1850
|
-
},
|
|
1851
|
-
build: {
|
|
1852
|
-
outDir: path11.resolve(rootDir, "dist/client"),
|
|
1853
|
-
// build.sh 已经在 vite build 前 `rm -rf dist`,这里不再让 vite 自己清
|
|
1854
|
-
emptyOutDir: false,
|
|
1855
|
-
sourcemap: isDev ? true : "hidden",
|
|
1856
|
-
minify: !isDev,
|
|
1857
|
-
cssTarget: ["ios12", "safari12", "chrome80"],
|
|
1858
|
-
rollupOptions: {
|
|
1859
|
-
input: resolveClientPath(rootDir, "index.html"),
|
|
1860
|
-
output: {
|
|
1861
|
-
// 按依赖拆 chunk,优化缓存和并行加载
|
|
1862
|
-
manualChunks(id) {
|
|
1863
|
-
if (id.includes("@lark-apaas/client-toolkit-lite")) return "toolkit";
|
|
1864
|
-
if (id.includes("@radix-ui")) return "radix";
|
|
1865
|
-
if (id.includes("@data-loom")) return "dataloom";
|
|
1866
|
-
return void 0;
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
},
|
|
1870
|
-
copyPublicDir: true
|
|
1871
|
-
},
|
|
1872
|
-
publicDir: resolveClientPath(rootDir, "public"),
|
|
1873
|
-
optimizeDeps: {
|
|
1874
|
-
include: [
|
|
1875
|
-
"react",
|
|
1876
|
-
"react-dom",
|
|
1877
|
-
"react/jsx-runtime",
|
|
1878
|
-
"react/jsx-dev-runtime",
|
|
1879
|
-
"clsx",
|
|
1880
|
-
"echarts",
|
|
1881
|
-
"echarts-for-react",
|
|
1882
|
-
...enableInspector ? ["@lark-apaas/miaoda-inspector"] : []
|
|
1883
|
-
]
|
|
1884
|
-
}
|
|
1885
|
-
};
|
|
1886
|
-
if (isDev) {
|
|
1887
|
-
baseConfig.plugins.push({
|
|
1888
|
-
name: "miaoda-dev-middlewares",
|
|
1889
|
-
async configureServer(server) {
|
|
1890
|
-
const baseWithSlash = clientBasePath.endsWith("/") ? clientBasePath : clientBasePath + "/";
|
|
1891
|
-
const baseWithoutSlash = baseWithSlash.slice(0, -1);
|
|
1892
|
-
if (baseWithoutSlash) {
|
|
1893
|
-
const stack = server.middlewares.stack;
|
|
1894
|
-
if (Array.isArray(stack)) {
|
|
1895
|
-
stack.unshift({
|
|
1896
|
-
route: "",
|
|
1897
|
-
handle: (req2, _res, next) => {
|
|
1898
|
-
const url = req2.url || "";
|
|
1899
|
-
const qIdx = url.indexOf("?");
|
|
1900
|
-
const pathname = qIdx >= 0 ? url.slice(0, qIdx) : url;
|
|
1901
|
-
const search = qIdx >= 0 ? url.slice(qIdx) : "";
|
|
1902
|
-
if (pathname === baseWithoutSlash) {
|
|
1903
|
-
req2.url = baseWithoutSlash + "/" + search;
|
|
1904
|
-
}
|
|
1905
|
-
next();
|
|
1906
|
-
}
|
|
1907
|
-
});
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
registerSnapDomProxyMiddleware(server.middlewares, { baseUrl: clientBasePath });
|
|
1911
|
-
}
|
|
1912
|
-
});
|
|
1913
|
-
}
|
|
1914
|
-
return mergeConfig(baseConfig, overrides);
|
|
1915
|
-
}
|
|
1916
|
-
var index_default = defineConfig;
|
|
1917
|
-
export {
|
|
1918
|
-
capabilitiesBundlePlugin,
|
|
1919
|
-
index_default as default,
|
|
1920
|
-
defineConfig,
|
|
1921
|
-
devLogsPlugin,
|
|
1922
|
-
errorOverlayPlugin,
|
|
1923
|
-
fontsMirrorPlugin,
|
|
1924
|
-
healthMiddlewarePlugin,
|
|
1925
|
-
hmrTimingPlugin,
|
|
1926
|
-
htmlMinifyPlugin,
|
|
1927
|
-
minifyHtmlWithHbsProtection,
|
|
1928
|
-
moduleAliasPlugin,
|
|
1929
|
-
normalizeBasePath,
|
|
1930
|
-
ogMetaPlugin,
|
|
1931
|
-
parseRoutes,
|
|
1932
|
-
polyfillPlugin,
|
|
1933
|
-
registerSnapDomProxyMiddleware,
|
|
1934
|
-
routesPlugin,
|
|
1935
|
-
slardarPlugin,
|
|
1936
|
-
staticAssetsPlugin,
|
|
1937
|
-
viewContextPlugin,
|
|
1938
|
-
viteClientPatchPlugin,
|
|
1939
|
-
wsWatchdogPlugin
|
|
1940
|
-
};
|
|
1941
|
-
//# sourceMappingURL=index.js.map
|