@open-wxa/wxa-runner-core 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +22 -9
- package/dist/index.d.ts +22 -9
- package/dist/index.js +358 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +354 -50
- 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 +357 -161
- 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");
|
|
@@ -481,6 +607,22 @@ function readJsonFile(file) {
|
|
|
481
607
|
}
|
|
482
608
|
}
|
|
483
609
|
|
|
610
|
+
// src/runtime/networkPanel.ts
|
|
611
|
+
var PROXY_PREFIX = "/__wxa__/proxy/";
|
|
612
|
+
var MOCK_PREFIX = "/__wxa__/mock/";
|
|
613
|
+
function fromServerPath(prefix, serverPath) {
|
|
614
|
+
if (!serverPath.startsWith(prefix)) return null;
|
|
615
|
+
const match = /^(https?)\/([^/?#]+)(.*)$/.exec(serverPath.slice(prefix.length));
|
|
616
|
+
if (!match) return null;
|
|
617
|
+
const url = `${match[1]}://${match[2]}${match[3]}`;
|
|
618
|
+
try {
|
|
619
|
+
new URL(url);
|
|
620
|
+
return url;
|
|
621
|
+
} catch {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
484
626
|
// src/server/plugins.ts
|
|
485
627
|
var import_wxa_runner_extractor2 = require("@open-wxa/wxa-runner-extractor");
|
|
486
628
|
|
|
@@ -758,7 +900,7 @@ ${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</
|
|
|
758
900
|
}
|
|
759
901
|
function workerScript(build2, baseLib) {
|
|
760
902
|
const modules = build2.workerModules.map((id) => `/__wxa__/module/${encodeURI(id)}?v=${build2.version}`);
|
|
761
|
-
const prelude = `var __wxConfig = ${toJs(build2.wxConfig)};
|
|
903
|
+
const prelude = `var __wxConfig = ${toJs(build2.wxConfig)}; __wxConfig.env.USER_DATA_PATH = location.origin + "/__wxa__/usr/" + new URL(location.href).searchParams.get("session");
|
|
762
904
|
importScripts('/__wxa__/runtime/worker.js');
|
|
763
905
|
`;
|
|
764
906
|
if (!baseLib.hasWorker) {
|
|
@@ -840,13 +982,62 @@ function vlq(value) {
|
|
|
840
982
|
return out;
|
|
841
983
|
}
|
|
842
984
|
|
|
985
|
+
// src/server/forwardConsole.ts
|
|
986
|
+
var MAX_LENGTH = 4e3;
|
|
987
|
+
var LIMIT_PER_SECOND = 20;
|
|
988
|
+
var CONTROL_CHARS = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
989
|
+
function printBrowserReports(socket, logger) {
|
|
990
|
+
let windowStart = 0;
|
|
991
|
+
let printed = 0;
|
|
992
|
+
let dropped = 0;
|
|
993
|
+
socket.on("message", (data) => {
|
|
994
|
+
const report = parseReport(data);
|
|
995
|
+
if (!report) return;
|
|
996
|
+
const now = Date.now();
|
|
997
|
+
if (now - windowStart >= 1e3) {
|
|
998
|
+
windowStart = now;
|
|
999
|
+
printed = 0;
|
|
1000
|
+
}
|
|
1001
|
+
if (printed >= LIMIT_PER_SECOND) {
|
|
1002
|
+
if (dropped++ === 0) {
|
|
1003
|
+
setTimeout(() => {
|
|
1004
|
+
logger.warn(`[wxa-runner] \u6D4F\u89C8\u5668 1 \u79D2\u5185\u8F6C\u53D1\u7684\u62A5\u9519\u592A\u591A\uFF0C\u7701\u7565\u4E86 ${dropped} \u6761`);
|
|
1005
|
+
dropped = 0;
|
|
1006
|
+
}, windowStart + 1e3 - now).unref();
|
|
1007
|
+
}
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
printed++;
|
|
1011
|
+
logger[report.level](`[wxa-runner] ${clean(report.text)}`);
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
function parseReport(data) {
|
|
1015
|
+
try {
|
|
1016
|
+
const report = JSON.parse(data.toString());
|
|
1017
|
+
if (report?.type === "console" && (report.level === "warn" || report.level === "error") && typeof report.text === "string") return report;
|
|
1018
|
+
} catch {
|
|
1019
|
+
}
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
function clean(text) {
|
|
1023
|
+
const printable = text.replace(CONTROL_CHARS, "");
|
|
1024
|
+
return printable.length > MAX_LENGTH ? `${printable.slice(0, MAX_LENGTH)}\u2026\uFF08\u5DF2\u622A\u65AD\uFF09` : printable;
|
|
1025
|
+
}
|
|
1026
|
+
function ignoreConsoleHook(sourceMap) {
|
|
1027
|
+
const map = JSON.parse(sourceMap);
|
|
1028
|
+
map.x_google_ignoreList = map.sources.flatMap((source, index) => source.endsWith("consoleHook.ts") ? [index] : []);
|
|
1029
|
+
return JSON.stringify(map);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
843
1032
|
// src/server/proxy.ts
|
|
1033
|
+
var import_node_stream = require("stream");
|
|
1034
|
+
var import_promises = require("stream/promises");
|
|
844
1035
|
var DROPPED_REQUEST_HEADERS = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding", "keep-alive", "upgrade", "expect"]);
|
|
845
1036
|
var DROPPED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding", "connection", "set-cookie"]);
|
|
846
1037
|
async function handleProxy(req, res) {
|
|
847
|
-
const target =
|
|
848
|
-
if (
|
|
849
|
-
res.status(400).set("x-wxa-proxy-error", "1").send(`invalid url "${
|
|
1038
|
+
const target = fromServerPath(PROXY_PREFIX, req.originalUrl);
|
|
1039
|
+
if (!target) {
|
|
1040
|
+
res.status(400).set("x-wxa-proxy-error", "1").send(`invalid url "${req.originalUrl}"`);
|
|
850
1041
|
return;
|
|
851
1042
|
}
|
|
852
1043
|
let requestHeaders = {};
|
|
@@ -868,31 +1059,47 @@ async function handleProxy(req, res) {
|
|
|
868
1059
|
headers["content-type"] = browserContentType;
|
|
869
1060
|
}
|
|
870
1061
|
const hasBody = !["GET", "HEAD"].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;
|
|
871
|
-
|
|
1062
|
+
const controller = new AbortController();
|
|
1063
|
+
const cancel = () => controller.abort();
|
|
1064
|
+
res.once("close", cancel);
|
|
872
1065
|
try {
|
|
873
|
-
upstream
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1066
|
+
let upstream;
|
|
1067
|
+
try {
|
|
1068
|
+
upstream = await fetch(target, {
|
|
1069
|
+
method: req.method,
|
|
1070
|
+
headers,
|
|
1071
|
+
body: hasBody ? req.body : void 0,
|
|
1072
|
+
redirect: "follow",
|
|
1073
|
+
signal: controller.signal
|
|
1074
|
+
});
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
if (res.destroyed || controller.signal.aborted) return;
|
|
1077
|
+
const cause = error?.cause?.message;
|
|
1078
|
+
res.status(502).set("x-wxa-proxy-error", "1").send(cause || error.message);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
const responseHeaders = {};
|
|
1082
|
+
upstream.headers.forEach((value, key) => {
|
|
1083
|
+
responseHeaders[key] = value;
|
|
878
1084
|
});
|
|
1085
|
+
const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
|
|
1086
|
+
res.status(upstream.status);
|
|
1087
|
+
upstream.headers.forEach((value, key) => {
|
|
1088
|
+
if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
|
|
1089
|
+
});
|
|
1090
|
+
res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
|
|
1091
|
+
res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
|
|
1092
|
+
res.flushHeaders();
|
|
1093
|
+
if (!upstream.body) {
|
|
1094
|
+
res.end();
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(upstream.body), res);
|
|
879
1098
|
} catch (error) {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1099
|
+
if (!res.destroyed) res.destroy(error);
|
|
1100
|
+
} finally {
|
|
1101
|
+
res.off("close", cancel);
|
|
883
1102
|
}
|
|
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
1103
|
}
|
|
897
1104
|
|
|
898
1105
|
// src/server/hmr.ts
|
|
@@ -910,8 +1117,8 @@ function planHotUpdate(files, units) {
|
|
|
910
1117
|
|
|
911
1118
|
// src/server/developerTicket.ts
|
|
912
1119
|
var import_child_process = require("child_process");
|
|
913
|
-
var
|
|
914
|
-
var
|
|
1120
|
+
var import_crypto2 = require("crypto");
|
|
1121
|
+
var import_promises2 = require("timers/promises");
|
|
915
1122
|
var import_ws = require("ws");
|
|
916
1123
|
var import_wxa_runner_extractor3 = require("@open-wxa/wxa-runner-extractor");
|
|
917
1124
|
var AUTO_PORT = 9420;
|
|
@@ -960,7 +1167,7 @@ async function requestTicketOnceWindowReady(port, projectPath) {
|
|
|
960
1167
|
} catch (error) {
|
|
961
1168
|
if (!(error instanceof WindowNotFound)) throw error;
|
|
962
1169
|
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,
|
|
1170
|
+
await (0, import_promises2.setTimeout)(WINDOW_RETRY_INTERVAL);
|
|
964
1171
|
}
|
|
965
1172
|
}
|
|
966
1173
|
}
|
|
@@ -1002,7 +1209,7 @@ function cliErrorMessage(stdout) {
|
|
|
1002
1209
|
}
|
|
1003
1210
|
function requestTicket(port) {
|
|
1004
1211
|
return new Promise((resolve, reject) => {
|
|
1005
|
-
const id = `wxa-runner-${(0,
|
|
1212
|
+
const id = `wxa-runner-${(0, import_crypto2.randomUUID)()}`;
|
|
1006
1213
|
const socket = new import_ws.WebSocket(`ws://127.0.0.1:${port}`);
|
|
1007
1214
|
const settle = (finish) => {
|
|
1008
1215
|
clearTimeout(timer);
|
|
@@ -1153,7 +1360,7 @@ function builtinSetupModule() {
|
|
|
1153
1360
|
}
|
|
1154
1361
|
|
|
1155
1362
|
// src/server/watcher.ts
|
|
1156
|
-
var
|
|
1363
|
+
var import_crypto3 = require("crypto");
|
|
1157
1364
|
var import_fs3 = __toESM(require("fs"));
|
|
1158
1365
|
var import_path3 = __toESM(require("path"));
|
|
1159
1366
|
var import_chokidar = __toESM(require("chokidar"));
|
|
@@ -1197,7 +1404,7 @@ function updateFingerprint(fingerprints, file, event) {
|
|
|
1197
1404
|
if (event !== "add" && event !== "change" || !FINGERPRINTED.test(file)) return false;
|
|
1198
1405
|
let next;
|
|
1199
1406
|
try {
|
|
1200
|
-
next = (0,
|
|
1407
|
+
next = (0, import_crypto3.createHash)("sha1").update(import_fs3.default.readFileSync(file)).digest("hex");
|
|
1201
1408
|
} catch {
|
|
1202
1409
|
return false;
|
|
1203
1410
|
}
|
|
@@ -1224,8 +1431,9 @@ var DevServer = class {
|
|
|
1224
1431
|
this.sockets = null;
|
|
1225
1432
|
this.watcher = null;
|
|
1226
1433
|
this.address = "";
|
|
1434
|
+
this.lanAccess = false;
|
|
1227
1435
|
/** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
|
|
1228
|
-
this.
|
|
1436
|
+
this.fileSessions = /* @__PURE__ */ new Map();
|
|
1229
1437
|
this.logger = options.logger || console;
|
|
1230
1438
|
this.project = new MiniProgram(import_path4.default.resolve(options.projectPath));
|
|
1231
1439
|
this.baseLib = (0, import_wxa_runner_extractor5.resolveBaseLib)({ version: options.baseLibVersion, cacheDir: options.baseLibCacheDir });
|
|
@@ -1244,9 +1452,16 @@ var DevServer = class {
|
|
|
1244
1452
|
this.setupEnv = collectSetupEnv(process.env);
|
|
1245
1453
|
this.app = this.createApp();
|
|
1246
1454
|
}
|
|
1455
|
+
/** 本机访问的地址 */
|
|
1247
1456
|
get url() {
|
|
1248
1457
|
return this.address;
|
|
1249
1458
|
}
|
|
1459
|
+
/** 局域网里其他设备访问的地址,每块网卡一个;只监听本机时为空 */
|
|
1460
|
+
get networkUrls() {
|
|
1461
|
+
if (!this.lanAccess) return [];
|
|
1462
|
+
const { port } = new URL(this.address);
|
|
1463
|
+
return Object.values(import_os.default.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
|
|
1464
|
+
}
|
|
1250
1465
|
async start() {
|
|
1251
1466
|
try {
|
|
1252
1467
|
const build2 = await this.builder.build();
|
|
@@ -1256,7 +1471,11 @@ var DevServer = class {
|
|
|
1256
1471
|
${error.message}`);
|
|
1257
1472
|
}
|
|
1258
1473
|
const server = import_http.default.createServer(this.app);
|
|
1259
|
-
this.sockets = new import_ws2.WebSocketServer({ noServer: true });
|
|
1474
|
+
this.sockets = new import_ws2.WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });
|
|
1475
|
+
this.sockets.on("connection", (socket) => {
|
|
1476
|
+
socket.on("error", () => void 0);
|
|
1477
|
+
if (this.options.forwardConsole) printBrowserReports(socket, this.logger);
|
|
1478
|
+
});
|
|
1260
1479
|
server.on("upgrade", (req, socket, head) => {
|
|
1261
1480
|
if (req.url?.startsWith("/__wxa__/ws")) {
|
|
1262
1481
|
this.sockets.handleUpgrade(req, socket, head, (ws) => this.sockets.emit("connection", ws, req));
|
|
@@ -1265,13 +1484,10 @@ ${error.message}`);
|
|
|
1265
1484
|
}
|
|
1266
1485
|
});
|
|
1267
1486
|
const host = this.options.host || "localhost";
|
|
1268
|
-
await
|
|
1269
|
-
server.once("error", reject);
|
|
1270
|
-
server.listen(this.options.port ?? 3e3, host, () => resolve());
|
|
1271
|
-
});
|
|
1487
|
+
const port = await listen(server, this.options.port, host, this.logger);
|
|
1272
1488
|
this.server = server;
|
|
1273
|
-
|
|
1274
|
-
this.address = `http://${host}:${port}`;
|
|
1489
|
+
this.lanAccess = LOCAL_HOST_OF_ANY_ADDRESS.has(host);
|
|
1490
|
+
this.address = `http://${LOCAL_HOST_OF_ANY_ADDRESS.get(host) ?? host}:${port}`;
|
|
1275
1491
|
if (this.options.watch !== false) this.startWatching();
|
|
1276
1492
|
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
1493
|
return this.address;
|
|
@@ -1286,6 +1502,7 @@ ${error.message}`);
|
|
|
1286
1502
|
server.closeAllConnections();
|
|
1287
1503
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
1288
1504
|
}
|
|
1505
|
+
this.fileSessions.clear();
|
|
1289
1506
|
}
|
|
1290
1507
|
createApp() {
|
|
1291
1508
|
const app = (0, import_express2.default)();
|
|
@@ -1300,7 +1517,13 @@ ${error.message}`);
|
|
|
1300
1517
|
});
|
|
1301
1518
|
app.get("/__wxa__/app-info.json", async (_req, res) => {
|
|
1302
1519
|
noStore(res);
|
|
1303
|
-
const base = {
|
|
1520
|
+
const base = {
|
|
1521
|
+
appid: this.project.appid,
|
|
1522
|
+
baseLibVersion: this.baseLib.version,
|
|
1523
|
+
device: this.device,
|
|
1524
|
+
devtoolsLogin: this.devtoolsLogin,
|
|
1525
|
+
forwardConsole: this.options.forwardConsole ?? false
|
|
1526
|
+
};
|
|
1304
1527
|
try {
|
|
1305
1528
|
const build2 = await this.plugins.refresh(await this.builder.build());
|
|
1306
1529
|
const info = { ...base, version: build2.version, wxConfig: build2.wxConfig, pages: build2.pages };
|
|
@@ -1379,7 +1602,18 @@ ${error.message}`);
|
|
|
1379
1602
|
app.get("/__wxa__/page-frame.js", this.withBuild((build2, _req, res) => {
|
|
1380
1603
|
res.type("js").send(build2.pageFrame);
|
|
1381
1604
|
}));
|
|
1382
|
-
app.
|
|
1605
|
+
app.get("/__wxa__/runtime/frame.js.map", (_req, res, next) => {
|
|
1606
|
+
const file = import_path4.default.join(this.runtimeDir(), "frame.js.map");
|
|
1607
|
+
if (!import_fs4.default.existsSync(file)) return next();
|
|
1608
|
+
res.type("json").send(ignoreConsoleHook(import_fs4.default.readFileSync(file, "utf8")));
|
|
1609
|
+
});
|
|
1610
|
+
app.use("/__wxa__/runtime", import_express2.default.static(this.runtimeDir(), {
|
|
1611
|
+
cacheControl: false,
|
|
1612
|
+
// Service Worker 默认只能接管自己所在目录下的页面,宿主页面在根路径
|
|
1613
|
+
setHeaders: (res, file) => {
|
|
1614
|
+
if (import_path4.default.basename(file) === "sw.js") res.setHeader("Service-Worker-Allowed", "/");
|
|
1615
|
+
}
|
|
1616
|
+
}));
|
|
1383
1617
|
const sourceMaps = /* @__PURE__ */ new Map();
|
|
1384
1618
|
app.get(/^\/__baselib__\/(.+\.js)\.map$/, (req, res, next) => {
|
|
1385
1619
|
const file = import_path4.default.resolve(this.baseLib.dir, req.params[0]);
|
|
@@ -1393,16 +1627,45 @@ ${error.message}`);
|
|
|
1393
1627
|
if (file.endsWith(".js")) res.setHeader("SourceMap", `${import_path4.default.basename(file)}.map`);
|
|
1394
1628
|
}
|
|
1395
1629
|
}));
|
|
1396
|
-
app.
|
|
1397
|
-
app.
|
|
1630
|
+
app.use(PROXY_PREFIX, import_express2.default.raw({ type: () => true, limit: "100mb" }), handleProxy);
|
|
1631
|
+
app.use(MOCK_PREFIX, (_req, res) => {
|
|
1632
|
+
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");
|
|
1633
|
+
});
|
|
1634
|
+
const sessionFiles = (session) => {
|
|
1635
|
+
if (!/^[a-z0-9-]{1,80}$/i.test(session)) return null;
|
|
1636
|
+
let files = this.fileSessions.get(session);
|
|
1637
|
+
if (!files) {
|
|
1638
|
+
files = new FileStore([`/__wxa__/usr/${session}`, `/__wxa__/tmp/${session}`]);
|
|
1639
|
+
this.fileSessions.set(session, files);
|
|
1640
|
+
}
|
|
1641
|
+
return files;
|
|
1642
|
+
};
|
|
1643
|
+
app.post("/__wxa__/fs/:session", import_express2.default.json({ limit: "150mb" }), (req, res) => {
|
|
1644
|
+
const files = sessionFiles(req.params.session);
|
|
1645
|
+
if (!files) {
|
|
1646
|
+
res.status(400).json({ error: `invalid filesystem session ${req.params.session}` });
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
try {
|
|
1650
|
+
res.json({ result: files.execute(req.body.operation, req.body.args || {}) });
|
|
1651
|
+
} catch (error) {
|
|
1652
|
+
res.json({ error: error.message });
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
app.post("/__wxa__/tmp/:session", import_express2.default.raw({ type: () => true, limit: "100mb" }), (req, res) => {
|
|
1398
1656
|
const type = String(req.query.type || "png");
|
|
1399
1657
|
if (!/^[a-z0-9]+$/i.test(type)) {
|
|
1400
1658
|
res.status(400).type("text").send(`\u975E\u6CD5\u7684\u6587\u4EF6\u7C7B\u578B ${type}`);
|
|
1401
1659
|
return;
|
|
1402
1660
|
}
|
|
1403
|
-
const
|
|
1404
|
-
|
|
1405
|
-
|
|
1661
|
+
const files = sessionFiles(req.params.session);
|
|
1662
|
+
if (!files) {
|
|
1663
|
+
res.status(400).type("text").send(`\u975E\u6CD5\u7684\u4F1A\u8BDD\u6807\u8BC6 ${req.params.session}`);
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
const filePath = `/__wxa__/tmp/${req.params.session}/${(0, import_crypto4.randomUUID)()}.${type}`;
|
|
1667
|
+
files.write(filePath, Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0));
|
|
1668
|
+
res.json({ url: `${req.protocol}://${req.get("host")}${filePath}` });
|
|
1406
1669
|
});
|
|
1407
1670
|
app.get("/__wxa__/region/:kind", (req, res) => {
|
|
1408
1671
|
const kind = req.params.kind;
|
|
@@ -1413,14 +1676,16 @@ ${error.message}`);
|
|
|
1413
1676
|
}
|
|
1414
1677
|
res.type("text/plain; charset=utf-8").set("Cache-Control", "public, max-age=86400").send(data);
|
|
1415
1678
|
});
|
|
1416
|
-
app.get("/__wxa__
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1679
|
+
app.get("/__wxa__/:kind(usr|tmp)/:session/*", (req, res) => {
|
|
1680
|
+
const files = this.fileSessions.get(req.params.session);
|
|
1681
|
+
const node = files?.stat(`/__wxa__/${req.params.kind}/${req.params.session}/` + req.params[0]);
|
|
1682
|
+
if (!node || node.dir) {
|
|
1419
1683
|
res.status(404).end();
|
|
1420
1684
|
return;
|
|
1421
1685
|
}
|
|
1422
|
-
|
|
1423
|
-
res.
|
|
1686
|
+
const data = node.data;
|
|
1687
|
+
res.type(import_path4.default.extname(req.params[0]));
|
|
1688
|
+
res.setHeader("ETag", `"${node.etag}"`);
|
|
1424
1689
|
res.setHeader("Accept-Ranges", "bytes");
|
|
1425
1690
|
const ranges = req.range(data.length);
|
|
1426
1691
|
if (ranges === -1) {
|
|
@@ -1436,8 +1701,15 @@ ${error.message}`);
|
|
|
1436
1701
|
}
|
|
1437
1702
|
res.send(data);
|
|
1438
1703
|
});
|
|
1439
|
-
app.delete("/__wxa__/tmp/:
|
|
1440
|
-
|
|
1704
|
+
app.delete("/__wxa__/tmp/:session/*", (req, res) => {
|
|
1705
|
+
try {
|
|
1706
|
+
const files = this.fileSessions.get(req.params.session);
|
|
1707
|
+
if (!files) throw new Error("file not exist");
|
|
1708
|
+
files.remove(`/__wxa__/tmp/${req.params.session}/` + req.params[0], { dir: false });
|
|
1709
|
+
res.status(204).end();
|
|
1710
|
+
} catch {
|
|
1711
|
+
res.status(404).end();
|
|
1712
|
+
}
|
|
1441
1713
|
});
|
|
1442
1714
|
if (this.ticketSource) app.use("/__wxa__/api", devtoolsLoginRouter(this.project.appid, this.ticketSource));
|
|
1443
1715
|
app.use("/__wxa__/webview", import_express2.default.static(root));
|
|
@@ -1526,6 +1798,38 @@ ${message}`);
|
|
|
1526
1798
|
});
|
|
1527
1799
|
}
|
|
1528
1800
|
};
|
|
1801
|
+
var DEFAULT_PORT = 3e3;
|
|
1802
|
+
var LOCAL_HOST_OF_ANY_ADDRESS = /* @__PURE__ */ new Map([["0.0.0.0", "127.0.0.1"], ["::", "localhost"]]);
|
|
1803
|
+
async function listen(server, port, host, logger) {
|
|
1804
|
+
for (let candidate = port ?? DEFAULT_PORT; ; candidate++) {
|
|
1805
|
+
const taken = LOCAL_HOST_OF_ANY_ADDRESS.has(host) && await takenOnLoopback(candidate) || !await bind(server, candidate, host);
|
|
1806
|
+
if (!taken) {
|
|
1807
|
+
const actual = server.address().port;
|
|
1808
|
+
if (port === void 0 && actual !== DEFAULT_PORT) logger.warn(`[wxa-runner] \u7AEF\u53E3 ${DEFAULT_PORT} \u5DF2\u88AB\u5360\u7528\uFF0C\u6539\u7528 ${actual}`);
|
|
1809
|
+
return actual;
|
|
1810
|
+
}
|
|
1811
|
+
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`);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
function bind(server, port, host) {
|
|
1815
|
+
return new Promise((resolve, reject) => {
|
|
1816
|
+
const onError = (error) => error.code === "EADDRINUSE" ? resolve(false) : reject(error);
|
|
1817
|
+
server.once("error", onError);
|
|
1818
|
+
server.listen(port, host, () => {
|
|
1819
|
+
server.off("error", onError);
|
|
1820
|
+
resolve(true);
|
|
1821
|
+
});
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
async function takenOnLoopback(port) {
|
|
1825
|
+
for (const address of ["127.0.0.1", "::1"]) {
|
|
1826
|
+
const probe = import_net.default.createServer();
|
|
1827
|
+
const free = await bind(probe, port, address).catch(() => true);
|
|
1828
|
+
if (!free) return true;
|
|
1829
|
+
await new Promise((resolve) => probe.close(resolve));
|
|
1830
|
+
}
|
|
1831
|
+
return false;
|
|
1832
|
+
}
|
|
1529
1833
|
function resolveSetupFile(option, projectPath) {
|
|
1530
1834
|
if (option === false) return null;
|
|
1531
1835
|
if (!option) return findSetupFile(projectPath);
|