@open-wxa/wxa-runner-core 0.7.0 → 0.8.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/dist/index.d.mts +27 -10
- package/dist/index.d.ts +27 -10
- package/dist/index.js +380 -59
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +376 -55
- package/dist/index.mjs.map +1 -1
- package/dist/runtime/frame.js +44 -2
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/host.js +443 -175
- package/dist/runtime/host.js.map +1 -1
- package/dist/runtime/sw.js +56 -0
- package/dist/runtime/sw.js.map +1 -0
- package/dist/{setup-DZ5FeKay.d.mts → setup-Bac8XdhR.d.mts} +5 -1
- package/dist/{setup-DZ5FeKay.d.ts → setup-Bac8XdhR.d.ts} +5 -1
- package/dist/setup.d.mts +1 -1
- package/dist/setup.d.ts +1 -1
- package/dist/test-api.d.mts +2 -2
- package/dist/test-api.d.ts +2 -2
- package/dist/{testApi-BLozOgCt.d.ts → testApi-BOZSXSnV.d.ts} +3 -3
- package/dist/{testApi-CnZOLyWw.d.mts → testApi-DHuEW5Cl.d.mts} +3 -3
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -39,9 +39,135 @@ __export(src_exports, {
|
|
|
39
39
|
module.exports = __toCommonJS(src_exports);
|
|
40
40
|
|
|
41
41
|
// src/server/DevServer.ts
|
|
42
|
-
var
|
|
42
|
+
var import_crypto4 = require("crypto");
|
|
43
|
+
|
|
44
|
+
// src/server/FileStore.ts
|
|
45
|
+
var import_crypto = require("crypto");
|
|
46
|
+
var FileStore = class {
|
|
47
|
+
/** @param roots 预先创建的目录(如用户目录)。rmdir 不能删除它们和它们的上级目录:删掉之后再写 USER_DATA_PATH 都会失败 */
|
|
48
|
+
constructor(roots = []) {
|
|
49
|
+
this.etagPrefix = (0, import_crypto.randomUUID)();
|
|
50
|
+
this.version = 0;
|
|
51
|
+
this.nodes = /* @__PURE__ */ new Map([["/", { dir: true, data: Buffer.alloc(0), mtime: Date.now(), etag: "" }]]);
|
|
52
|
+
this.roots = roots.map((root) => this.normalize(root));
|
|
53
|
+
for (const root of this.roots) this.mkdir(root, true);
|
|
54
|
+
}
|
|
55
|
+
normalize(filePath) {
|
|
56
|
+
const stripped = String(filePath || "").replace(/^[a-z]+:\/\/[^/]*/i, "");
|
|
57
|
+
const parts = [];
|
|
58
|
+
for (const part of stripped.split("/")) {
|
|
59
|
+
if (!part || part === ".") continue;
|
|
60
|
+
if (part === "..") parts.pop();
|
|
61
|
+
else parts.push(part);
|
|
62
|
+
}
|
|
63
|
+
return "/" + parts.join("/");
|
|
64
|
+
}
|
|
65
|
+
stat(filePath) {
|
|
66
|
+
return this.nodes.get(this.normalize(filePath));
|
|
67
|
+
}
|
|
68
|
+
mkdir(dirPath, recursive = false) {
|
|
69
|
+
const target = this.normalize(dirPath);
|
|
70
|
+
if (this.nodes.has(target)) throw new Error(`file already exists ${dirPath}`);
|
|
71
|
+
const parent = target.slice(0, target.lastIndexOf("/")) || "/";
|
|
72
|
+
if (!this.nodes.get(parent)?.dir) {
|
|
73
|
+
if (!recursive) throw new Error(`no such file or directory ${dirPath}`);
|
|
74
|
+
this.mkdir(parent, true);
|
|
75
|
+
}
|
|
76
|
+
this.nodes.set(target, { dir: true, data: Buffer.alloc(0), mtime: Date.now(), etag: "" });
|
|
77
|
+
}
|
|
78
|
+
write(filePath, input, append = false) {
|
|
79
|
+
const data = Buffer.from(input);
|
|
80
|
+
const target = this.normalize(filePath);
|
|
81
|
+
const parent = target.slice(0, target.lastIndexOf("/")) || "/";
|
|
82
|
+
if (!this.nodes.get(parent)?.dir) throw new Error(`no such file or directory ${filePath}`);
|
|
83
|
+
const existing = this.nodes.get(target);
|
|
84
|
+
if (existing?.dir) throw new Error(`illegal operation on a directory ${filePath}`);
|
|
85
|
+
const contents = append && existing ? Buffer.concat([existing.data, data]) : data;
|
|
86
|
+
this.nodes.set(target, { dir: false, data: contents, mtime: Date.now(), etag: `${this.etagPrefix}-${++this.version}` });
|
|
87
|
+
}
|
|
88
|
+
read(filePath) {
|
|
89
|
+
const node = this.stat(filePath);
|
|
90
|
+
if (!node) throw new Error(`no such file or directory, open ${filePath}`);
|
|
91
|
+
if (node.dir) throw new Error(`illegal operation on a directory ${filePath}`);
|
|
92
|
+
return node.data;
|
|
93
|
+
}
|
|
94
|
+
readdir(dirPath) {
|
|
95
|
+
const target = this.normalize(dirPath);
|
|
96
|
+
if (!this.nodes.get(target)?.dir) throw new Error(`no such file or directory ${dirPath}`);
|
|
97
|
+
const prefix = target === "/" ? "/" : target + "/";
|
|
98
|
+
return [...this.nodes.keys()].filter((key) => key.startsWith(prefix) && key !== target && !key.slice(prefix.length).includes("/")).map((key) => key.slice(prefix.length));
|
|
99
|
+
}
|
|
100
|
+
remove(filePath, options) {
|
|
101
|
+
const target = this.normalize(filePath);
|
|
102
|
+
const node = this.nodes.get(target);
|
|
103
|
+
if (!node) throw new Error(`no such file or directory ${filePath}`);
|
|
104
|
+
if (node.dir !== options.dir) throw new Error(options.dir ? `not a directory ${filePath}` : `operation not permitted ${filePath}`);
|
|
105
|
+
if (node.dir) {
|
|
106
|
+
const children = this.readdir(target);
|
|
107
|
+
if (children.length && !options.recursive) throw new Error(`directory not empty ${filePath}`);
|
|
108
|
+
for (const key of [...this.nodes.keys()]) if (key.startsWith(target + "/")) this.nodes.delete(key);
|
|
109
|
+
}
|
|
110
|
+
this.nodes.delete(target);
|
|
111
|
+
}
|
|
112
|
+
execute(operation, args) {
|
|
113
|
+
switch (operation) {
|
|
114
|
+
case "readFile": {
|
|
115
|
+
const data = this.read(args.filePath);
|
|
116
|
+
return args.encoding === void 0 ? { data: data.toString("base64"), binary: true } : { data: data.toString(args.encoding) };
|
|
117
|
+
}
|
|
118
|
+
case "writeFile":
|
|
119
|
+
case "appendFile": {
|
|
120
|
+
const data = Buffer.from(args.data ?? "", args.binary ? "base64" : args.encoding || "utf8");
|
|
121
|
+
this.write(args.filePath, data, operation === "appendFile");
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
case "copyFile":
|
|
125
|
+
this.write(args.destPath, this.read(args.srcPath));
|
|
126
|
+
return {};
|
|
127
|
+
case "access":
|
|
128
|
+
if (!this.stat(args.path)) throw new Error(`no such file or directory ${args.path}`);
|
|
129
|
+
return {};
|
|
130
|
+
case "stat": {
|
|
131
|
+
const node = this.stat(args.path);
|
|
132
|
+
if (!node) throw new Error(`no such file or directory ${args.path}`);
|
|
133
|
+
const stats = {
|
|
134
|
+
mode: node.dir ? 16877 : 33188,
|
|
135
|
+
size: node.data.length,
|
|
136
|
+
lastAccessedTime: Math.floor(node.mtime / 1e3),
|
|
137
|
+
lastModifiedTime: Math.floor(node.mtime / 1e3)
|
|
138
|
+
};
|
|
139
|
+
return { stats, ...stats };
|
|
140
|
+
}
|
|
141
|
+
case "getFileInfo":
|
|
142
|
+
return { size: this.read(args.filePath).length };
|
|
143
|
+
case "mkdir":
|
|
144
|
+
this.mkdir(args.dirPath, !!args.recursive);
|
|
145
|
+
return {};
|
|
146
|
+
case "readdir":
|
|
147
|
+
return { files: this.readdir(args.dirPath) };
|
|
148
|
+
case "unlink":
|
|
149
|
+
this.remove(args.filePath, { dir: false });
|
|
150
|
+
return {};
|
|
151
|
+
case "rmdir": {
|
|
152
|
+
const target = this.normalize(args.dirPath);
|
|
153
|
+
const prefix = target === "/" ? "/" : target + "/";
|
|
154
|
+
if (this.roots.some((root) => root === target || root.startsWith(prefix))) {
|
|
155
|
+
throw new Error(`operation not permitted ${args.dirPath}`);
|
|
156
|
+
}
|
|
157
|
+
this.remove(args.dirPath, { dir: true, recursive: !!args.recursive });
|
|
158
|
+
return {};
|
|
159
|
+
}
|
|
160
|
+
default:
|
|
161
|
+
throw new Error(`unsupported filesystem operation ${operation}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// src/server/DevServer.ts
|
|
43
167
|
var import_fs4 = __toESM(require("fs"));
|
|
44
168
|
var import_http = __toESM(require("http"));
|
|
169
|
+
var import_net = __toESM(require("net"));
|
|
170
|
+
var import_os = __toESM(require("os"));
|
|
45
171
|
var import_path4 = __toESM(require("path"));
|
|
46
172
|
var import_express2 = __toESM(require("express"));
|
|
47
173
|
var import_ws2 = require("ws");
|
|
@@ -374,15 +500,32 @@ var MiniProgram = class {
|
|
|
374
500
|
}
|
|
375
501
|
return pages;
|
|
376
502
|
}
|
|
377
|
-
/**
|
|
503
|
+
/**
|
|
504
|
+
* 读取并预处理页面/组件 json,id 不带扩展名。
|
|
505
|
+
* 基础库运行时只把组件引用和当前路径拼在一起,不查找 miniprogram_npm、不补 /index(开发者工具编译时已解析好),
|
|
506
|
+
* 所以能解析到的引用都换成组件的绝对路径
|
|
507
|
+
*/
|
|
378
508
|
readUnitJson(id, app) {
|
|
379
509
|
const json = { ...readJsonFile(import_path.default.join(this.root, id + ".json")) };
|
|
380
|
-
|
|
381
|
-
|
|
510
|
+
const resolve = (fromId, ref) => {
|
|
511
|
+
const resolved = this.resolveComponent(fromId, ref);
|
|
512
|
+
return resolved ? "/" + resolved : null;
|
|
513
|
+
};
|
|
514
|
+
json.usingComponents = Object.fromEntries(
|
|
515
|
+
Object.entries(json.usingComponents || {}).map(([tag, ref]) => [tag, resolve(id, ref) ?? ref])
|
|
516
|
+
);
|
|
517
|
+
if (json.componentGenerics) {
|
|
518
|
+
json.componentGenerics = Object.fromEntries(Object.entries(json.componentGenerics).map(([name, generic]) => [
|
|
519
|
+
name,
|
|
520
|
+
typeof generic === "object" && generic.default ? { ...generic, default: resolve(id, generic.default) ?? generic.default } : generic
|
|
521
|
+
]));
|
|
522
|
+
}
|
|
523
|
+
if (!/(^|\/)miniprogram_npm\//.test(id) && !this.isInIndependentSubPackage(id, app)) {
|
|
382
524
|
for (const [tag, ref] of Object.entries(app.usingComponents || {})) {
|
|
383
525
|
if (json.usingComponents[tag]) continue;
|
|
384
|
-
|
|
385
|
-
|
|
526
|
+
const resolved = resolve("app", ref);
|
|
527
|
+
if (resolved || ref.startsWith("/") || /^plugin(-private)?:\/\//.test(ref)) {
|
|
528
|
+
json.usingComponents[tag] = resolved ?? ref;
|
|
386
529
|
} else {
|
|
387
530
|
const relative = import_path.default.posix.relative(import_path.default.posix.dirname(id), ref);
|
|
388
531
|
json.usingComponents[tag] = relative.startsWith(".") ? relative : "./" + relative;
|
|
@@ -481,6 +624,22 @@ function readJsonFile(file) {
|
|
|
481
624
|
}
|
|
482
625
|
}
|
|
483
626
|
|
|
627
|
+
// src/runtime/networkPanel.ts
|
|
628
|
+
var PROXY_PREFIX = "/__wxa__/proxy/";
|
|
629
|
+
var MOCK_PREFIX = "/__wxa__/mock/";
|
|
630
|
+
function fromServerPath(prefix, serverPath) {
|
|
631
|
+
if (!serverPath.startsWith(prefix)) return null;
|
|
632
|
+
const match = /^(https?)\/([^/?#]+)(.*)$/.exec(serverPath.slice(prefix.length));
|
|
633
|
+
if (!match) return null;
|
|
634
|
+
const url = `${match[1]}://${match[2]}${match[3]}`;
|
|
635
|
+
try {
|
|
636
|
+
new URL(url);
|
|
637
|
+
return url;
|
|
638
|
+
} catch {
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
484
643
|
// src/server/plugins.ts
|
|
485
644
|
var import_wxa_runner_extractor2 = require("@open-wxa/wxa-runner-extractor");
|
|
486
645
|
|
|
@@ -758,7 +917,7 @@ ${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</
|
|
|
758
917
|
}
|
|
759
918
|
function workerScript(build2, baseLib) {
|
|
760
919
|
const modules = build2.workerModules.map((id) => `/__wxa__/module/${encodeURI(id)}?v=${build2.version}`);
|
|
761
|
-
const prelude = `var __wxConfig = ${toJs(build2.wxConfig)};
|
|
920
|
+
const prelude = `var __wxConfig = ${toJs(build2.wxConfig)}; __wxConfig.env.USER_DATA_PATH = location.origin + "/__wxa__/usr/" + new URL(location.href).searchParams.get("session");
|
|
762
921
|
importScripts('/__wxa__/runtime/worker.js');
|
|
763
922
|
`;
|
|
764
923
|
if (!baseLib.hasWorker) {
|
|
@@ -840,13 +999,62 @@ function vlq(value) {
|
|
|
840
999
|
return out;
|
|
841
1000
|
}
|
|
842
1001
|
|
|
1002
|
+
// src/server/forwardConsole.ts
|
|
1003
|
+
var MAX_LENGTH = 4e3;
|
|
1004
|
+
var LIMIT_PER_SECOND = 20;
|
|
1005
|
+
var CONTROL_CHARS = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
1006
|
+
function printBrowserReports(socket, logger) {
|
|
1007
|
+
let windowStart = 0;
|
|
1008
|
+
let printed = 0;
|
|
1009
|
+
let dropped = 0;
|
|
1010
|
+
socket.on("message", (data) => {
|
|
1011
|
+
const report = parseReport(data);
|
|
1012
|
+
if (!report) return;
|
|
1013
|
+
const now = Date.now();
|
|
1014
|
+
if (now - windowStart >= 1e3) {
|
|
1015
|
+
windowStart = now;
|
|
1016
|
+
printed = 0;
|
|
1017
|
+
}
|
|
1018
|
+
if (printed >= LIMIT_PER_SECOND) {
|
|
1019
|
+
if (dropped++ === 0) {
|
|
1020
|
+
setTimeout(() => {
|
|
1021
|
+
logger.warn(`[wxa-runner] \u6D4F\u89C8\u5668 1 \u79D2\u5185\u8F6C\u53D1\u7684\u62A5\u9519\u592A\u591A\uFF0C\u7701\u7565\u4E86 ${dropped} \u6761`);
|
|
1022
|
+
dropped = 0;
|
|
1023
|
+
}, windowStart + 1e3 - now).unref();
|
|
1024
|
+
}
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
printed++;
|
|
1028
|
+
logger[report.level](`[wxa-runner] ${clean(report.text)}`);
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
function parseReport(data) {
|
|
1032
|
+
try {
|
|
1033
|
+
const report = JSON.parse(data.toString());
|
|
1034
|
+
if (report?.type === "console" && (report.level === "warn" || report.level === "error") && typeof report.text === "string") return report;
|
|
1035
|
+
} catch {
|
|
1036
|
+
}
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
function clean(text) {
|
|
1040
|
+
const printable = text.replace(CONTROL_CHARS, "");
|
|
1041
|
+
return printable.length > MAX_LENGTH ? `${printable.slice(0, MAX_LENGTH)}\u2026\uFF08\u5DF2\u622A\u65AD\uFF09` : printable;
|
|
1042
|
+
}
|
|
1043
|
+
function ignoreConsoleHook(sourceMap) {
|
|
1044
|
+
const map = JSON.parse(sourceMap);
|
|
1045
|
+
map.x_google_ignoreList = map.sources.flatMap((source, index) => source.endsWith("consoleHook.ts") ? [index] : []);
|
|
1046
|
+
return JSON.stringify(map);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
843
1049
|
// src/server/proxy.ts
|
|
1050
|
+
var import_node_stream = require("stream");
|
|
1051
|
+
var import_promises = require("stream/promises");
|
|
844
1052
|
var DROPPED_REQUEST_HEADERS = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding", "keep-alive", "upgrade", "expect"]);
|
|
845
1053
|
var DROPPED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding", "connection", "set-cookie"]);
|
|
846
1054
|
async function handleProxy(req, res) {
|
|
847
|
-
const target =
|
|
848
|
-
if (
|
|
849
|
-
res.status(400).set("x-wxa-proxy-error", "1").send(`invalid url "${
|
|
1055
|
+
const target = fromServerPath(PROXY_PREFIX, req.originalUrl);
|
|
1056
|
+
if (!target) {
|
|
1057
|
+
res.status(400).set("x-wxa-proxy-error", "1").send(`invalid url "${req.originalUrl}"`);
|
|
850
1058
|
return;
|
|
851
1059
|
}
|
|
852
1060
|
let requestHeaders = {};
|
|
@@ -868,31 +1076,47 @@ async function handleProxy(req, res) {
|
|
|
868
1076
|
headers["content-type"] = browserContentType;
|
|
869
1077
|
}
|
|
870
1078
|
const hasBody = !["GET", "HEAD"].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;
|
|
871
|
-
|
|
1079
|
+
const controller = new AbortController();
|
|
1080
|
+
const cancel = () => controller.abort();
|
|
1081
|
+
res.once("close", cancel);
|
|
872
1082
|
try {
|
|
873
|
-
upstream
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1083
|
+
let upstream;
|
|
1084
|
+
try {
|
|
1085
|
+
upstream = await fetch(target, {
|
|
1086
|
+
method: req.method,
|
|
1087
|
+
headers,
|
|
1088
|
+
body: hasBody ? req.body : void 0,
|
|
1089
|
+
redirect: "follow",
|
|
1090
|
+
signal: controller.signal
|
|
1091
|
+
});
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
if (res.destroyed || controller.signal.aborted) return;
|
|
1094
|
+
const cause = error?.cause?.message;
|
|
1095
|
+
res.status(502).set("x-wxa-proxy-error", "1").send(cause || error.message);
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
const responseHeaders = {};
|
|
1099
|
+
upstream.headers.forEach((value, key) => {
|
|
1100
|
+
responseHeaders[key] = value;
|
|
1101
|
+
});
|
|
1102
|
+
const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
|
|
1103
|
+
res.status(upstream.status);
|
|
1104
|
+
upstream.headers.forEach((value, key) => {
|
|
1105
|
+
if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
|
|
878
1106
|
});
|
|
1107
|
+
res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
|
|
1108
|
+
res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
|
|
1109
|
+
res.flushHeaders();
|
|
1110
|
+
if (!upstream.body) {
|
|
1111
|
+
res.end();
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(upstream.body), res);
|
|
879
1115
|
} catch (error) {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1116
|
+
if (!res.destroyed) res.destroy(error);
|
|
1117
|
+
} finally {
|
|
1118
|
+
res.off("close", cancel);
|
|
883
1119
|
}
|
|
884
|
-
const responseHeaders = {};
|
|
885
|
-
upstream.headers.forEach((value, key) => {
|
|
886
|
-
responseHeaders[key] = value;
|
|
887
|
-
});
|
|
888
|
-
const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
|
|
889
|
-
res.status(upstream.status);
|
|
890
|
-
upstream.headers.forEach((value, key) => {
|
|
891
|
-
if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
|
|
892
|
-
});
|
|
893
|
-
res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
|
|
894
|
-
res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
|
|
895
|
-
res.send(Buffer.from(await upstream.arrayBuffer()));
|
|
896
1120
|
}
|
|
897
1121
|
|
|
898
1122
|
// src/server/hmr.ts
|
|
@@ -910,8 +1134,8 @@ function planHotUpdate(files, units) {
|
|
|
910
1134
|
|
|
911
1135
|
// src/server/developerTicket.ts
|
|
912
1136
|
var import_child_process = require("child_process");
|
|
913
|
-
var
|
|
914
|
-
var
|
|
1137
|
+
var import_crypto2 = require("crypto");
|
|
1138
|
+
var import_promises2 = require("timers/promises");
|
|
915
1139
|
var import_ws = require("ws");
|
|
916
1140
|
var import_wxa_runner_extractor3 = require("@open-wxa/wxa-runner-extractor");
|
|
917
1141
|
var AUTO_PORT = 9420;
|
|
@@ -960,7 +1184,7 @@ async function requestTicketOnceWindowReady(port, projectPath) {
|
|
|
960
1184
|
} catch (error) {
|
|
961
1185
|
if (!(error instanceof WindowNotFound)) throw error;
|
|
962
1186
|
if (Date.now() >= deadline) throw new Error(`\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u627E\u4E0D\u5230\u9879\u76EE\u7A97\u53E3\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u6253\u5F00\u9879\u76EE ${projectPath}`);
|
|
963
|
-
await (0,
|
|
1187
|
+
await (0, import_promises2.setTimeout)(WINDOW_RETRY_INTERVAL);
|
|
964
1188
|
}
|
|
965
1189
|
}
|
|
966
1190
|
}
|
|
@@ -1002,7 +1226,7 @@ function cliErrorMessage(stdout) {
|
|
|
1002
1226
|
}
|
|
1003
1227
|
function requestTicket(port) {
|
|
1004
1228
|
return new Promise((resolve, reject) => {
|
|
1005
|
-
const id = `wxa-runner-${(0,
|
|
1229
|
+
const id = `wxa-runner-${(0, import_crypto2.randomUUID)()}`;
|
|
1006
1230
|
const socket = new import_ws.WebSocket(`ws://127.0.0.1:${port}`);
|
|
1007
1231
|
const settle = (finish) => {
|
|
1008
1232
|
clearTimeout(timer);
|
|
@@ -1153,7 +1377,7 @@ function builtinSetupModule() {
|
|
|
1153
1377
|
}
|
|
1154
1378
|
|
|
1155
1379
|
// src/server/watcher.ts
|
|
1156
|
-
var
|
|
1380
|
+
var import_crypto3 = require("crypto");
|
|
1157
1381
|
var import_fs3 = __toESM(require("fs"));
|
|
1158
1382
|
var import_path3 = __toESM(require("path"));
|
|
1159
1383
|
var import_chokidar = __toESM(require("chokidar"));
|
|
@@ -1197,7 +1421,7 @@ function updateFingerprint(fingerprints, file, event) {
|
|
|
1197
1421
|
if (event !== "add" && event !== "change" || !FINGERPRINTED.test(file)) return false;
|
|
1198
1422
|
let next;
|
|
1199
1423
|
try {
|
|
1200
|
-
next = (0,
|
|
1424
|
+
next = (0, import_crypto3.createHash)("sha1").update(import_fs3.default.readFileSync(file)).digest("hex");
|
|
1201
1425
|
} catch {
|
|
1202
1426
|
return false;
|
|
1203
1427
|
}
|
|
@@ -1224,8 +1448,9 @@ var DevServer = class {
|
|
|
1224
1448
|
this.sockets = null;
|
|
1225
1449
|
this.watcher = null;
|
|
1226
1450
|
this.address = "";
|
|
1451
|
+
this.lanAccess = false;
|
|
1227
1452
|
/** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
|
|
1228
|
-
this.
|
|
1453
|
+
this.fileSessions = /* @__PURE__ */ new Map();
|
|
1229
1454
|
this.logger = options.logger || console;
|
|
1230
1455
|
this.project = new MiniProgram(import_path4.default.resolve(options.projectPath));
|
|
1231
1456
|
this.baseLib = (0, import_wxa_runner_extractor5.resolveBaseLib)({ version: options.baseLibVersion, cacheDir: options.baseLibCacheDir });
|
|
@@ -1244,9 +1469,16 @@ var DevServer = class {
|
|
|
1244
1469
|
this.setupEnv = collectSetupEnv(process.env);
|
|
1245
1470
|
this.app = this.createApp();
|
|
1246
1471
|
}
|
|
1472
|
+
/** 本机访问的地址 */
|
|
1247
1473
|
get url() {
|
|
1248
1474
|
return this.address;
|
|
1249
1475
|
}
|
|
1476
|
+
/** 局域网里其他设备访问的地址,每块网卡一个;只监听本机时为空 */
|
|
1477
|
+
get networkUrls() {
|
|
1478
|
+
if (!this.lanAccess) return [];
|
|
1479
|
+
const { port } = new URL(this.address);
|
|
1480
|
+
return Object.values(import_os.default.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
|
|
1481
|
+
}
|
|
1250
1482
|
async start() {
|
|
1251
1483
|
try {
|
|
1252
1484
|
const build2 = await this.builder.build();
|
|
@@ -1256,7 +1488,11 @@ var DevServer = class {
|
|
|
1256
1488
|
${error.message}`);
|
|
1257
1489
|
}
|
|
1258
1490
|
const server = import_http.default.createServer(this.app);
|
|
1259
|
-
this.sockets = new import_ws2.WebSocketServer({ noServer: true });
|
|
1491
|
+
this.sockets = new import_ws2.WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });
|
|
1492
|
+
this.sockets.on("connection", (socket) => {
|
|
1493
|
+
socket.on("error", () => void 0);
|
|
1494
|
+
if (this.options.forwardConsole) printBrowserReports(socket, this.logger);
|
|
1495
|
+
});
|
|
1260
1496
|
server.on("upgrade", (req, socket, head) => {
|
|
1261
1497
|
if (req.url?.startsWith("/__wxa__/ws")) {
|
|
1262
1498
|
this.sockets.handleUpgrade(req, socket, head, (ws) => this.sockets.emit("connection", ws, req));
|
|
@@ -1265,13 +1501,10 @@ ${error.message}`);
|
|
|
1265
1501
|
}
|
|
1266
1502
|
});
|
|
1267
1503
|
const host = this.options.host || "localhost";
|
|
1268
|
-
await
|
|
1269
|
-
server.once("error", reject);
|
|
1270
|
-
server.listen(this.options.port ?? 3e3, host, () => resolve());
|
|
1271
|
-
});
|
|
1504
|
+
const port = await listen(server, this.options.port, host, this.logger);
|
|
1272
1505
|
this.server = server;
|
|
1273
|
-
|
|
1274
|
-
this.address = `http://${host}:${port}`;
|
|
1506
|
+
this.lanAccess = LOCAL_HOST_OF_ANY_ADDRESS.has(host);
|
|
1507
|
+
this.address = `http://${LOCAL_HOST_OF_ANY_ADDRESS.get(host) ?? host}:${port}`;
|
|
1275
1508
|
if (this.options.watch !== false) this.startWatching();
|
|
1276
1509
|
this.ticketSource?.().then(requireUsableTicket).catch((error) => this.logger.warn(`[wxa-runner] \u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\u6682\u4E0D\u53EF\u7528\uFF08--devtools-login\uFF09\uFF1A${error.message}`));
|
|
1277
1510
|
return this.address;
|
|
@@ -1286,6 +1519,7 @@ ${error.message}`);
|
|
|
1286
1519
|
server.closeAllConnections();
|
|
1287
1520
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
1288
1521
|
}
|
|
1522
|
+
this.fileSessions.clear();
|
|
1289
1523
|
}
|
|
1290
1524
|
createApp() {
|
|
1291
1525
|
const app = (0, import_express2.default)();
|
|
@@ -1300,7 +1534,13 @@ ${error.message}`);
|
|
|
1300
1534
|
});
|
|
1301
1535
|
app.get("/__wxa__/app-info.json", async (_req, res) => {
|
|
1302
1536
|
noStore(res);
|
|
1303
|
-
const base = {
|
|
1537
|
+
const base = {
|
|
1538
|
+
appid: this.project.appid,
|
|
1539
|
+
baseLibVersion: this.baseLib.version,
|
|
1540
|
+
device: this.device,
|
|
1541
|
+
devtoolsLogin: this.devtoolsLogin,
|
|
1542
|
+
forwardConsole: this.options.forwardConsole ?? false
|
|
1543
|
+
};
|
|
1304
1544
|
try {
|
|
1305
1545
|
const build2 = await this.plugins.refresh(await this.builder.build());
|
|
1306
1546
|
const info = { ...base, version: build2.version, wxConfig: build2.wxConfig, pages: build2.pages };
|
|
@@ -1379,7 +1619,18 @@ ${error.message}`);
|
|
|
1379
1619
|
app.get("/__wxa__/page-frame.js", this.withBuild((build2, _req, res) => {
|
|
1380
1620
|
res.type("js").send(build2.pageFrame);
|
|
1381
1621
|
}));
|
|
1382
|
-
app.
|
|
1622
|
+
app.get("/__wxa__/runtime/frame.js.map", (_req, res, next) => {
|
|
1623
|
+
const file = import_path4.default.join(this.runtimeDir(), "frame.js.map");
|
|
1624
|
+
if (!import_fs4.default.existsSync(file)) return next();
|
|
1625
|
+
res.type("json").send(ignoreConsoleHook(import_fs4.default.readFileSync(file, "utf8")));
|
|
1626
|
+
});
|
|
1627
|
+
app.use("/__wxa__/runtime", import_express2.default.static(this.runtimeDir(), {
|
|
1628
|
+
cacheControl: false,
|
|
1629
|
+
// Service Worker 默认只能接管自己所在目录下的页面,宿主页面在根路径
|
|
1630
|
+
setHeaders: (res, file) => {
|
|
1631
|
+
if (import_path4.default.basename(file) === "sw.js") res.setHeader("Service-Worker-Allowed", "/");
|
|
1632
|
+
}
|
|
1633
|
+
}));
|
|
1383
1634
|
const sourceMaps = /* @__PURE__ */ new Map();
|
|
1384
1635
|
app.get(/^\/__baselib__\/(.+\.js)\.map$/, (req, res, next) => {
|
|
1385
1636
|
const file = import_path4.default.resolve(this.baseLib.dir, req.params[0]);
|
|
@@ -1393,16 +1644,45 @@ ${error.message}`);
|
|
|
1393
1644
|
if (file.endsWith(".js")) res.setHeader("SourceMap", `${import_path4.default.basename(file)}.map`);
|
|
1394
1645
|
}
|
|
1395
1646
|
}));
|
|
1396
|
-
app.
|
|
1397
|
-
app.
|
|
1647
|
+
app.use(PROXY_PREFIX, import_express2.default.raw({ type: () => true, limit: "100mb" }), handleProxy);
|
|
1648
|
+
app.use(MOCK_PREFIX, (_req, res) => {
|
|
1649
|
+
res.status(503).type("text").send("\u8FD9\u4E2A\u8BF7\u6C42\u5DF2\u88AB\u62E6\u622A\u89C4\u5219\u62E6\u4E0B\uFF0C\u8FD9\u91CC\u53EA\u7528\u4E8E\u5728 Network \u9762\u677F\u91CC\u663E\u793A\u5B83\u3002Service Worker \u6CA1\u6709\u751F\u6548\uFF08\u5982 DevTools \u52FE\u9009\u4E86 Bypass for network\uFF09\uFF0C\u770B\u4E0D\u5230\u62E6\u622A\u89C4\u5219\u7ED9\u51FA\u7684\u54CD\u5E94");
|
|
1650
|
+
});
|
|
1651
|
+
const sessionFiles = (session) => {
|
|
1652
|
+
if (!/^[a-z0-9-]{1,80}$/i.test(session)) return null;
|
|
1653
|
+
let files = this.fileSessions.get(session);
|
|
1654
|
+
if (!files) {
|
|
1655
|
+
files = new FileStore([`/__wxa__/usr/${session}`, `/__wxa__/tmp/${session}`]);
|
|
1656
|
+
this.fileSessions.set(session, files);
|
|
1657
|
+
}
|
|
1658
|
+
return files;
|
|
1659
|
+
};
|
|
1660
|
+
app.post("/__wxa__/fs/:session", import_express2.default.json({ limit: "150mb" }), (req, res) => {
|
|
1661
|
+
const files = sessionFiles(req.params.session);
|
|
1662
|
+
if (!files) {
|
|
1663
|
+
res.status(400).json({ error: `invalid filesystem session ${req.params.session}` });
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
try {
|
|
1667
|
+
res.json({ result: files.execute(req.body.operation, req.body.args || {}) });
|
|
1668
|
+
} catch (error) {
|
|
1669
|
+
res.json({ error: error.message });
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
app.post("/__wxa__/tmp/:session", import_express2.default.raw({ type: () => true, limit: "100mb" }), (req, res) => {
|
|
1398
1673
|
const type = String(req.query.type || "png");
|
|
1399
1674
|
if (!/^[a-z0-9]+$/i.test(type)) {
|
|
1400
1675
|
res.status(400).type("text").send(`\u975E\u6CD5\u7684\u6587\u4EF6\u7C7B\u578B ${type}`);
|
|
1401
1676
|
return;
|
|
1402
1677
|
}
|
|
1403
|
-
const
|
|
1404
|
-
|
|
1405
|
-
|
|
1678
|
+
const files = sessionFiles(req.params.session);
|
|
1679
|
+
if (!files) {
|
|
1680
|
+
res.status(400).type("text").send(`\u975E\u6CD5\u7684\u4F1A\u8BDD\u6807\u8BC6 ${req.params.session}`);
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
const filePath = `/__wxa__/tmp/${req.params.session}/${(0, import_crypto4.randomUUID)()}.${type}`;
|
|
1684
|
+
files.write(filePath, Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0));
|
|
1685
|
+
res.json({ url: `${req.protocol}://${req.get("host")}${filePath}` });
|
|
1406
1686
|
});
|
|
1407
1687
|
app.get("/__wxa__/region/:kind", (req, res) => {
|
|
1408
1688
|
const kind = req.params.kind;
|
|
@@ -1413,14 +1693,16 @@ ${error.message}`);
|
|
|
1413
1693
|
}
|
|
1414
1694
|
res.type("text/plain; charset=utf-8").set("Cache-Control", "public, max-age=86400").send(data);
|
|
1415
1695
|
});
|
|
1416
|
-
app.get("/__wxa__
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1696
|
+
app.get("/__wxa__/:kind(usr|tmp)/:session/*", (req, res) => {
|
|
1697
|
+
const files = this.fileSessions.get(req.params.session);
|
|
1698
|
+
const node = files?.stat(`/__wxa__/${req.params.kind}/${req.params.session}/` + req.params[0]);
|
|
1699
|
+
if (!node || node.dir) {
|
|
1419
1700
|
res.status(404).end();
|
|
1420
1701
|
return;
|
|
1421
1702
|
}
|
|
1422
|
-
|
|
1423
|
-
res.
|
|
1703
|
+
const data = node.data;
|
|
1704
|
+
res.type(import_path4.default.extname(req.params[0]));
|
|
1705
|
+
res.setHeader("ETag", `"${node.etag}"`);
|
|
1424
1706
|
res.setHeader("Accept-Ranges", "bytes");
|
|
1425
1707
|
const ranges = req.range(data.length);
|
|
1426
1708
|
if (ranges === -1) {
|
|
@@ -1436,8 +1718,15 @@ ${error.message}`);
|
|
|
1436
1718
|
}
|
|
1437
1719
|
res.send(data);
|
|
1438
1720
|
});
|
|
1439
|
-
app.delete("/__wxa__/tmp/:
|
|
1440
|
-
|
|
1721
|
+
app.delete("/__wxa__/tmp/:session/*", (req, res) => {
|
|
1722
|
+
try {
|
|
1723
|
+
const files = this.fileSessions.get(req.params.session);
|
|
1724
|
+
if (!files) throw new Error("file not exist");
|
|
1725
|
+
files.remove(`/__wxa__/tmp/${req.params.session}/` + req.params[0], { dir: false });
|
|
1726
|
+
res.status(204).end();
|
|
1727
|
+
} catch {
|
|
1728
|
+
res.status(404).end();
|
|
1729
|
+
}
|
|
1441
1730
|
});
|
|
1442
1731
|
if (this.ticketSource) app.use("/__wxa__/api", devtoolsLoginRouter(this.project.appid, this.ticketSource));
|
|
1443
1732
|
app.use("/__wxa__/webview", import_express2.default.static(root));
|
|
@@ -1526,6 +1815,38 @@ ${message}`);
|
|
|
1526
1815
|
});
|
|
1527
1816
|
}
|
|
1528
1817
|
};
|
|
1818
|
+
var DEFAULT_PORT = 3e3;
|
|
1819
|
+
var LOCAL_HOST_OF_ANY_ADDRESS = /* @__PURE__ */ new Map([["0.0.0.0", "127.0.0.1"], ["::", "localhost"]]);
|
|
1820
|
+
async function listen(server, port, host, logger) {
|
|
1821
|
+
for (let candidate = port ?? DEFAULT_PORT; ; candidate++) {
|
|
1822
|
+
const taken = LOCAL_HOST_OF_ANY_ADDRESS.has(host) && await takenOnLoopback(candidate) || !await bind(server, candidate, host);
|
|
1823
|
+
if (!taken) {
|
|
1824
|
+
const actual = server.address().port;
|
|
1825
|
+
if (port === void 0 && actual !== DEFAULT_PORT) logger.warn(`[wxa-runner] \u7AEF\u53E3 ${DEFAULT_PORT} \u5DF2\u88AB\u5360\u7528\uFF0C\u6539\u7528 ${actual}`);
|
|
1826
|
+
return actual;
|
|
1827
|
+
}
|
|
1828
|
+
if (port !== void 0) throw new Error(`\u7AEF\u53E3 ${port} \u5DF2\u88AB\u5360\u7528\uFF1A\u5148\u5173\u95ED\u5360\u7528\u5B83\u7684\u8FDB\u7A0B\uFF0C\u6216\u7528 --port \u6362\u4E00\u4E2A\u7AEF\u53E3`);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
function bind(server, port, host) {
|
|
1832
|
+
return new Promise((resolve, reject) => {
|
|
1833
|
+
const onError = (error) => error.code === "EADDRINUSE" ? resolve(false) : reject(error);
|
|
1834
|
+
server.once("error", onError);
|
|
1835
|
+
server.listen(port, host, () => {
|
|
1836
|
+
server.off("error", onError);
|
|
1837
|
+
resolve(true);
|
|
1838
|
+
});
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
async function takenOnLoopback(port) {
|
|
1842
|
+
for (const address of ["127.0.0.1", "::1"]) {
|
|
1843
|
+
const probe = import_net.default.createServer();
|
|
1844
|
+
const free = await bind(probe, port, address).catch(() => true);
|
|
1845
|
+
if (!free) return true;
|
|
1846
|
+
await new Promise((resolve) => probe.close(resolve));
|
|
1847
|
+
}
|
|
1848
|
+
return false;
|
|
1849
|
+
}
|
|
1529
1850
|
function resolveSetupFile(option, projectPath) {
|
|
1530
1851
|
if (option === false) return null;
|
|
1531
1852
|
if (!option) return findSetupFile(projectPath);
|