@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.mjs
CHANGED
|
@@ -3,9 +3,135 @@ import {
|
|
|
3
3
|
} from "./chunk-NGXYLLYS.mjs";
|
|
4
4
|
|
|
5
5
|
// src/server/DevServer.ts
|
|
6
|
-
import { randomUUID as
|
|
6
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
7
|
+
|
|
8
|
+
// src/server/FileStore.ts
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
|
+
var FileStore = class {
|
|
11
|
+
/** @param roots 预先创建的目录(如用户目录)。rmdir 不能删除它们和它们的上级目录:删掉之后再写 USER_DATA_PATH 都会失败 */
|
|
12
|
+
constructor(roots = []) {
|
|
13
|
+
this.etagPrefix = randomUUID();
|
|
14
|
+
this.version = 0;
|
|
15
|
+
this.nodes = /* @__PURE__ */ new Map([["/", { dir: true, data: Buffer.alloc(0), mtime: Date.now(), etag: "" }]]);
|
|
16
|
+
this.roots = roots.map((root) => this.normalize(root));
|
|
17
|
+
for (const root of this.roots) this.mkdir(root, true);
|
|
18
|
+
}
|
|
19
|
+
normalize(filePath) {
|
|
20
|
+
const stripped = String(filePath || "").replace(/^[a-z]+:\/\/[^/]*/i, "");
|
|
21
|
+
const parts = [];
|
|
22
|
+
for (const part of stripped.split("/")) {
|
|
23
|
+
if (!part || part === ".") continue;
|
|
24
|
+
if (part === "..") parts.pop();
|
|
25
|
+
else parts.push(part);
|
|
26
|
+
}
|
|
27
|
+
return "/" + parts.join("/");
|
|
28
|
+
}
|
|
29
|
+
stat(filePath) {
|
|
30
|
+
return this.nodes.get(this.normalize(filePath));
|
|
31
|
+
}
|
|
32
|
+
mkdir(dirPath, recursive = false) {
|
|
33
|
+
const target = this.normalize(dirPath);
|
|
34
|
+
if (this.nodes.has(target)) throw new Error(`file already exists ${dirPath}`);
|
|
35
|
+
const parent = target.slice(0, target.lastIndexOf("/")) || "/";
|
|
36
|
+
if (!this.nodes.get(parent)?.dir) {
|
|
37
|
+
if (!recursive) throw new Error(`no such file or directory ${dirPath}`);
|
|
38
|
+
this.mkdir(parent, true);
|
|
39
|
+
}
|
|
40
|
+
this.nodes.set(target, { dir: true, data: Buffer.alloc(0), mtime: Date.now(), etag: "" });
|
|
41
|
+
}
|
|
42
|
+
write(filePath, input, append = false) {
|
|
43
|
+
const data = Buffer.from(input);
|
|
44
|
+
const target = this.normalize(filePath);
|
|
45
|
+
const parent = target.slice(0, target.lastIndexOf("/")) || "/";
|
|
46
|
+
if (!this.nodes.get(parent)?.dir) throw new Error(`no such file or directory ${filePath}`);
|
|
47
|
+
const existing = this.nodes.get(target);
|
|
48
|
+
if (existing?.dir) throw new Error(`illegal operation on a directory ${filePath}`);
|
|
49
|
+
const contents = append && existing ? Buffer.concat([existing.data, data]) : data;
|
|
50
|
+
this.nodes.set(target, { dir: false, data: contents, mtime: Date.now(), etag: `${this.etagPrefix}-${++this.version}` });
|
|
51
|
+
}
|
|
52
|
+
read(filePath) {
|
|
53
|
+
const node = this.stat(filePath);
|
|
54
|
+
if (!node) throw new Error(`no such file or directory, open ${filePath}`);
|
|
55
|
+
if (node.dir) throw new Error(`illegal operation on a directory ${filePath}`);
|
|
56
|
+
return node.data;
|
|
57
|
+
}
|
|
58
|
+
readdir(dirPath) {
|
|
59
|
+
const target = this.normalize(dirPath);
|
|
60
|
+
if (!this.nodes.get(target)?.dir) throw new Error(`no such file or directory ${dirPath}`);
|
|
61
|
+
const prefix = target === "/" ? "/" : target + "/";
|
|
62
|
+
return [...this.nodes.keys()].filter((key) => key.startsWith(prefix) && key !== target && !key.slice(prefix.length).includes("/")).map((key) => key.slice(prefix.length));
|
|
63
|
+
}
|
|
64
|
+
remove(filePath, options) {
|
|
65
|
+
const target = this.normalize(filePath);
|
|
66
|
+
const node = this.nodes.get(target);
|
|
67
|
+
if (!node) throw new Error(`no such file or directory ${filePath}`);
|
|
68
|
+
if (node.dir !== options.dir) throw new Error(options.dir ? `not a directory ${filePath}` : `operation not permitted ${filePath}`);
|
|
69
|
+
if (node.dir) {
|
|
70
|
+
const children = this.readdir(target);
|
|
71
|
+
if (children.length && !options.recursive) throw new Error(`directory not empty ${filePath}`);
|
|
72
|
+
for (const key of [...this.nodes.keys()]) if (key.startsWith(target + "/")) this.nodes.delete(key);
|
|
73
|
+
}
|
|
74
|
+
this.nodes.delete(target);
|
|
75
|
+
}
|
|
76
|
+
execute(operation, args) {
|
|
77
|
+
switch (operation) {
|
|
78
|
+
case "readFile": {
|
|
79
|
+
const data = this.read(args.filePath);
|
|
80
|
+
return args.encoding === void 0 ? { data: data.toString("base64"), binary: true } : { data: data.toString(args.encoding) };
|
|
81
|
+
}
|
|
82
|
+
case "writeFile":
|
|
83
|
+
case "appendFile": {
|
|
84
|
+
const data = Buffer.from(args.data ?? "", args.binary ? "base64" : args.encoding || "utf8");
|
|
85
|
+
this.write(args.filePath, data, operation === "appendFile");
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
case "copyFile":
|
|
89
|
+
this.write(args.destPath, this.read(args.srcPath));
|
|
90
|
+
return {};
|
|
91
|
+
case "access":
|
|
92
|
+
if (!this.stat(args.path)) throw new Error(`no such file or directory ${args.path}`);
|
|
93
|
+
return {};
|
|
94
|
+
case "stat": {
|
|
95
|
+
const node = this.stat(args.path);
|
|
96
|
+
if (!node) throw new Error(`no such file or directory ${args.path}`);
|
|
97
|
+
const stats = {
|
|
98
|
+
mode: node.dir ? 16877 : 33188,
|
|
99
|
+
size: node.data.length,
|
|
100
|
+
lastAccessedTime: Math.floor(node.mtime / 1e3),
|
|
101
|
+
lastModifiedTime: Math.floor(node.mtime / 1e3)
|
|
102
|
+
};
|
|
103
|
+
return { stats, ...stats };
|
|
104
|
+
}
|
|
105
|
+
case "getFileInfo":
|
|
106
|
+
return { size: this.read(args.filePath).length };
|
|
107
|
+
case "mkdir":
|
|
108
|
+
this.mkdir(args.dirPath, !!args.recursive);
|
|
109
|
+
return {};
|
|
110
|
+
case "readdir":
|
|
111
|
+
return { files: this.readdir(args.dirPath) };
|
|
112
|
+
case "unlink":
|
|
113
|
+
this.remove(args.filePath, { dir: false });
|
|
114
|
+
return {};
|
|
115
|
+
case "rmdir": {
|
|
116
|
+
const target = this.normalize(args.dirPath);
|
|
117
|
+
const prefix = target === "/" ? "/" : target + "/";
|
|
118
|
+
if (this.roots.some((root) => root === target || root.startsWith(prefix))) {
|
|
119
|
+
throw new Error(`operation not permitted ${args.dirPath}`);
|
|
120
|
+
}
|
|
121
|
+
this.remove(args.dirPath, { dir: true, recursive: !!args.recursive });
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
default:
|
|
125
|
+
throw new Error(`unsupported filesystem operation ${operation}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// src/server/DevServer.ts
|
|
7
131
|
import fs4 from "fs";
|
|
8
132
|
import http from "http";
|
|
133
|
+
import net from "net";
|
|
134
|
+
import os from "os";
|
|
9
135
|
import path4 from "path";
|
|
10
136
|
import express2 from "express";
|
|
11
137
|
import { WebSocket as WebSocket2, WebSocketServer } from "ws";
|
|
@@ -445,6 +571,22 @@ function readJsonFile(file) {
|
|
|
445
571
|
}
|
|
446
572
|
}
|
|
447
573
|
|
|
574
|
+
// src/runtime/networkPanel.ts
|
|
575
|
+
var PROXY_PREFIX = "/__wxa__/proxy/";
|
|
576
|
+
var MOCK_PREFIX = "/__wxa__/mock/";
|
|
577
|
+
function fromServerPath(prefix, serverPath) {
|
|
578
|
+
if (!serverPath.startsWith(prefix)) return null;
|
|
579
|
+
const match = /^(https?)\/([^/?#]+)(.*)$/.exec(serverPath.slice(prefix.length));
|
|
580
|
+
if (!match) return null;
|
|
581
|
+
const url = `${match[1]}://${match[2]}${match[3]}`;
|
|
582
|
+
try {
|
|
583
|
+
new URL(url);
|
|
584
|
+
return url;
|
|
585
|
+
} catch {
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
448
590
|
// src/server/plugins.ts
|
|
449
591
|
import { readWxvpkgFile, readWxvpkgIndex } from "@open-wxa/wxa-runner-extractor";
|
|
450
592
|
|
|
@@ -722,7 +864,7 @@ ${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</
|
|
|
722
864
|
}
|
|
723
865
|
function workerScript(build2, baseLib) {
|
|
724
866
|
const modules = build2.workerModules.map((id) => `/__wxa__/module/${encodeURI(id)}?v=${build2.version}`);
|
|
725
|
-
const prelude = `var __wxConfig = ${toJs(build2.wxConfig)};
|
|
867
|
+
const prelude = `var __wxConfig = ${toJs(build2.wxConfig)}; __wxConfig.env.USER_DATA_PATH = location.origin + "/__wxa__/usr/" + new URL(location.href).searchParams.get("session");
|
|
726
868
|
importScripts('/__wxa__/runtime/worker.js');
|
|
727
869
|
`;
|
|
728
870
|
if (!baseLib.hasWorker) {
|
|
@@ -804,13 +946,62 @@ function vlq(value) {
|
|
|
804
946
|
return out;
|
|
805
947
|
}
|
|
806
948
|
|
|
949
|
+
// src/server/forwardConsole.ts
|
|
950
|
+
var MAX_LENGTH = 4e3;
|
|
951
|
+
var LIMIT_PER_SECOND = 20;
|
|
952
|
+
var CONTROL_CHARS = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
953
|
+
function printBrowserReports(socket, logger) {
|
|
954
|
+
let windowStart = 0;
|
|
955
|
+
let printed = 0;
|
|
956
|
+
let dropped = 0;
|
|
957
|
+
socket.on("message", (data) => {
|
|
958
|
+
const report = parseReport(data);
|
|
959
|
+
if (!report) return;
|
|
960
|
+
const now = Date.now();
|
|
961
|
+
if (now - windowStart >= 1e3) {
|
|
962
|
+
windowStart = now;
|
|
963
|
+
printed = 0;
|
|
964
|
+
}
|
|
965
|
+
if (printed >= LIMIT_PER_SECOND) {
|
|
966
|
+
if (dropped++ === 0) {
|
|
967
|
+
setTimeout(() => {
|
|
968
|
+
logger.warn(`[wxa-runner] \u6D4F\u89C8\u5668 1 \u79D2\u5185\u8F6C\u53D1\u7684\u62A5\u9519\u592A\u591A\uFF0C\u7701\u7565\u4E86 ${dropped} \u6761`);
|
|
969
|
+
dropped = 0;
|
|
970
|
+
}, windowStart + 1e3 - now).unref();
|
|
971
|
+
}
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
printed++;
|
|
975
|
+
logger[report.level](`[wxa-runner] ${clean(report.text)}`);
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
function parseReport(data) {
|
|
979
|
+
try {
|
|
980
|
+
const report = JSON.parse(data.toString());
|
|
981
|
+
if (report?.type === "console" && (report.level === "warn" || report.level === "error") && typeof report.text === "string") return report;
|
|
982
|
+
} catch {
|
|
983
|
+
}
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
function clean(text) {
|
|
987
|
+
const printable = text.replace(CONTROL_CHARS, "");
|
|
988
|
+
return printable.length > MAX_LENGTH ? `${printable.slice(0, MAX_LENGTH)}\u2026\uFF08\u5DF2\u622A\u65AD\uFF09` : printable;
|
|
989
|
+
}
|
|
990
|
+
function ignoreConsoleHook(sourceMap) {
|
|
991
|
+
const map = JSON.parse(sourceMap);
|
|
992
|
+
map.x_google_ignoreList = map.sources.flatMap((source, index) => source.endsWith("consoleHook.ts") ? [index] : []);
|
|
993
|
+
return JSON.stringify(map);
|
|
994
|
+
}
|
|
995
|
+
|
|
807
996
|
// src/server/proxy.ts
|
|
997
|
+
import { Readable } from "stream";
|
|
998
|
+
import { pipeline } from "stream/promises";
|
|
808
999
|
var DROPPED_REQUEST_HEADERS = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding", "keep-alive", "upgrade", "expect"]);
|
|
809
1000
|
var DROPPED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding", "connection", "set-cookie"]);
|
|
810
1001
|
async function handleProxy(req, res) {
|
|
811
|
-
const target =
|
|
812
|
-
if (
|
|
813
|
-
res.status(400).set("x-wxa-proxy-error", "1").send(`invalid url "${
|
|
1002
|
+
const target = fromServerPath(PROXY_PREFIX, req.originalUrl);
|
|
1003
|
+
if (!target) {
|
|
1004
|
+
res.status(400).set("x-wxa-proxy-error", "1").send(`invalid url "${req.originalUrl}"`);
|
|
814
1005
|
return;
|
|
815
1006
|
}
|
|
816
1007
|
let requestHeaders = {};
|
|
@@ -832,31 +1023,47 @@ async function handleProxy(req, res) {
|
|
|
832
1023
|
headers["content-type"] = browserContentType;
|
|
833
1024
|
}
|
|
834
1025
|
const hasBody = !["GET", "HEAD"].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;
|
|
835
|
-
|
|
1026
|
+
const controller = new AbortController();
|
|
1027
|
+
const cancel = () => controller.abort();
|
|
1028
|
+
res.once("close", cancel);
|
|
836
1029
|
try {
|
|
837
|
-
upstream
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1030
|
+
let upstream;
|
|
1031
|
+
try {
|
|
1032
|
+
upstream = await fetch(target, {
|
|
1033
|
+
method: req.method,
|
|
1034
|
+
headers,
|
|
1035
|
+
body: hasBody ? req.body : void 0,
|
|
1036
|
+
redirect: "follow",
|
|
1037
|
+
signal: controller.signal
|
|
1038
|
+
});
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
if (res.destroyed || controller.signal.aborted) return;
|
|
1041
|
+
const cause = error?.cause?.message;
|
|
1042
|
+
res.status(502).set("x-wxa-proxy-error", "1").send(cause || error.message);
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const responseHeaders = {};
|
|
1046
|
+
upstream.headers.forEach((value, key) => {
|
|
1047
|
+
responseHeaders[key] = value;
|
|
1048
|
+
});
|
|
1049
|
+
const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
|
|
1050
|
+
res.status(upstream.status);
|
|
1051
|
+
upstream.headers.forEach((value, key) => {
|
|
1052
|
+
if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
|
|
842
1053
|
});
|
|
1054
|
+
res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
|
|
1055
|
+
res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
|
|
1056
|
+
res.flushHeaders();
|
|
1057
|
+
if (!upstream.body) {
|
|
1058
|
+
res.end();
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
await pipeline(Readable.fromWeb(upstream.body), res);
|
|
843
1062
|
} catch (error) {
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1063
|
+
if (!res.destroyed) res.destroy(error);
|
|
1064
|
+
} finally {
|
|
1065
|
+
res.off("close", cancel);
|
|
847
1066
|
}
|
|
848
|
-
const responseHeaders = {};
|
|
849
|
-
upstream.headers.forEach((value, key) => {
|
|
850
|
-
responseHeaders[key] = value;
|
|
851
|
-
});
|
|
852
|
-
const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
|
|
853
|
-
res.status(upstream.status);
|
|
854
|
-
upstream.headers.forEach((value, key) => {
|
|
855
|
-
if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
|
|
856
|
-
});
|
|
857
|
-
res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
|
|
858
|
-
res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
|
|
859
|
-
res.send(Buffer.from(await upstream.arrayBuffer()));
|
|
860
1067
|
}
|
|
861
1068
|
|
|
862
1069
|
// src/server/hmr.ts
|
|
@@ -874,7 +1081,7 @@ function planHotUpdate(files, units) {
|
|
|
874
1081
|
|
|
875
1082
|
// src/server/developerTicket.ts
|
|
876
1083
|
import { execFile } from "child_process";
|
|
877
|
-
import { randomUUID } from "crypto";
|
|
1084
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
878
1085
|
import { setTimeout as sleep } from "timers/promises";
|
|
879
1086
|
import { WebSocket } from "ws";
|
|
880
1087
|
import { findDeveloperTicket, findElectronDevtoolsCli, TICKET_ENV as TICKET_ENV2 } from "@open-wxa/wxa-runner-extractor";
|
|
@@ -966,7 +1173,7 @@ function cliErrorMessage(stdout) {
|
|
|
966
1173
|
}
|
|
967
1174
|
function requestTicket(port) {
|
|
968
1175
|
return new Promise((resolve, reject) => {
|
|
969
|
-
const id = `wxa-runner-${
|
|
1176
|
+
const id = `wxa-runner-${randomUUID2()}`;
|
|
970
1177
|
const socket = new WebSocket(`ws://127.0.0.1:${port}`);
|
|
971
1178
|
const settle = (finish) => {
|
|
972
1179
|
clearTimeout(timer);
|
|
@@ -1188,8 +1395,9 @@ var DevServer = class {
|
|
|
1188
1395
|
this.sockets = null;
|
|
1189
1396
|
this.watcher = null;
|
|
1190
1397
|
this.address = "";
|
|
1398
|
+
this.lanAccess = false;
|
|
1191
1399
|
/** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
|
|
1192
|
-
this.
|
|
1400
|
+
this.fileSessions = /* @__PURE__ */ new Map();
|
|
1193
1401
|
this.logger = options.logger || console;
|
|
1194
1402
|
this.project = new MiniProgram(path4.resolve(options.projectPath));
|
|
1195
1403
|
this.baseLib = resolveBaseLib({ version: options.baseLibVersion, cacheDir: options.baseLibCacheDir });
|
|
@@ -1208,9 +1416,16 @@ var DevServer = class {
|
|
|
1208
1416
|
this.setupEnv = collectSetupEnv(process.env);
|
|
1209
1417
|
this.app = this.createApp();
|
|
1210
1418
|
}
|
|
1419
|
+
/** 本机访问的地址 */
|
|
1211
1420
|
get url() {
|
|
1212
1421
|
return this.address;
|
|
1213
1422
|
}
|
|
1423
|
+
/** 局域网里其他设备访问的地址,每块网卡一个;只监听本机时为空 */
|
|
1424
|
+
get networkUrls() {
|
|
1425
|
+
if (!this.lanAccess) return [];
|
|
1426
|
+
const { port } = new URL(this.address);
|
|
1427
|
+
return Object.values(os.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
|
|
1428
|
+
}
|
|
1214
1429
|
async start() {
|
|
1215
1430
|
try {
|
|
1216
1431
|
const build2 = await this.builder.build();
|
|
@@ -1220,7 +1435,11 @@ var DevServer = class {
|
|
|
1220
1435
|
${error.message}`);
|
|
1221
1436
|
}
|
|
1222
1437
|
const server = http.createServer(this.app);
|
|
1223
|
-
this.sockets = new WebSocketServer({ noServer: true });
|
|
1438
|
+
this.sockets = new WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });
|
|
1439
|
+
this.sockets.on("connection", (socket) => {
|
|
1440
|
+
socket.on("error", () => void 0);
|
|
1441
|
+
if (this.options.forwardConsole) printBrowserReports(socket, this.logger);
|
|
1442
|
+
});
|
|
1224
1443
|
server.on("upgrade", (req, socket, head) => {
|
|
1225
1444
|
if (req.url?.startsWith("/__wxa__/ws")) {
|
|
1226
1445
|
this.sockets.handleUpgrade(req, socket, head, (ws) => this.sockets.emit("connection", ws, req));
|
|
@@ -1229,13 +1448,10 @@ ${error.message}`);
|
|
|
1229
1448
|
}
|
|
1230
1449
|
});
|
|
1231
1450
|
const host = this.options.host || "localhost";
|
|
1232
|
-
await
|
|
1233
|
-
server.once("error", reject);
|
|
1234
|
-
server.listen(this.options.port ?? 3e3, host, () => resolve());
|
|
1235
|
-
});
|
|
1451
|
+
const port = await listen(server, this.options.port, host, this.logger);
|
|
1236
1452
|
this.server = server;
|
|
1237
|
-
|
|
1238
|
-
this.address = `http://${host}:${port}`;
|
|
1453
|
+
this.lanAccess = LOCAL_HOST_OF_ANY_ADDRESS.has(host);
|
|
1454
|
+
this.address = `http://${LOCAL_HOST_OF_ANY_ADDRESS.get(host) ?? host}:${port}`;
|
|
1239
1455
|
if (this.options.watch !== false) this.startWatching();
|
|
1240
1456
|
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}`));
|
|
1241
1457
|
return this.address;
|
|
@@ -1250,6 +1466,7 @@ ${error.message}`);
|
|
|
1250
1466
|
server.closeAllConnections();
|
|
1251
1467
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
1252
1468
|
}
|
|
1469
|
+
this.fileSessions.clear();
|
|
1253
1470
|
}
|
|
1254
1471
|
createApp() {
|
|
1255
1472
|
const app = express2();
|
|
@@ -1264,7 +1481,13 @@ ${error.message}`);
|
|
|
1264
1481
|
});
|
|
1265
1482
|
app.get("/__wxa__/app-info.json", async (_req, res) => {
|
|
1266
1483
|
noStore(res);
|
|
1267
|
-
const base = {
|
|
1484
|
+
const base = {
|
|
1485
|
+
appid: this.project.appid,
|
|
1486
|
+
baseLibVersion: this.baseLib.version,
|
|
1487
|
+
device: this.device,
|
|
1488
|
+
devtoolsLogin: this.devtoolsLogin,
|
|
1489
|
+
forwardConsole: this.options.forwardConsole ?? false
|
|
1490
|
+
};
|
|
1268
1491
|
try {
|
|
1269
1492
|
const build2 = await this.plugins.refresh(await this.builder.build());
|
|
1270
1493
|
const info = { ...base, version: build2.version, wxConfig: build2.wxConfig, pages: build2.pages };
|
|
@@ -1343,7 +1566,18 @@ ${error.message}`);
|
|
|
1343
1566
|
app.get("/__wxa__/page-frame.js", this.withBuild((build2, _req, res) => {
|
|
1344
1567
|
res.type("js").send(build2.pageFrame);
|
|
1345
1568
|
}));
|
|
1346
|
-
app.
|
|
1569
|
+
app.get("/__wxa__/runtime/frame.js.map", (_req, res, next) => {
|
|
1570
|
+
const file = path4.join(this.runtimeDir(), "frame.js.map");
|
|
1571
|
+
if (!fs4.existsSync(file)) return next();
|
|
1572
|
+
res.type("json").send(ignoreConsoleHook(fs4.readFileSync(file, "utf8")));
|
|
1573
|
+
});
|
|
1574
|
+
app.use("/__wxa__/runtime", express2.static(this.runtimeDir(), {
|
|
1575
|
+
cacheControl: false,
|
|
1576
|
+
// Service Worker 默认只能接管自己所在目录下的页面,宿主页面在根路径
|
|
1577
|
+
setHeaders: (res, file) => {
|
|
1578
|
+
if (path4.basename(file) === "sw.js") res.setHeader("Service-Worker-Allowed", "/");
|
|
1579
|
+
}
|
|
1580
|
+
}));
|
|
1347
1581
|
const sourceMaps = /* @__PURE__ */ new Map();
|
|
1348
1582
|
app.get(/^\/__baselib__\/(.+\.js)\.map$/, (req, res, next) => {
|
|
1349
1583
|
const file = path4.resolve(this.baseLib.dir, req.params[0]);
|
|
@@ -1357,16 +1591,45 @@ ${error.message}`);
|
|
|
1357
1591
|
if (file.endsWith(".js")) res.setHeader("SourceMap", `${path4.basename(file)}.map`);
|
|
1358
1592
|
}
|
|
1359
1593
|
}));
|
|
1360
|
-
app.
|
|
1361
|
-
app.
|
|
1594
|
+
app.use(PROXY_PREFIX, express2.raw({ type: () => true, limit: "100mb" }), handleProxy);
|
|
1595
|
+
app.use(MOCK_PREFIX, (_req, res) => {
|
|
1596
|
+
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");
|
|
1597
|
+
});
|
|
1598
|
+
const sessionFiles = (session) => {
|
|
1599
|
+
if (!/^[a-z0-9-]{1,80}$/i.test(session)) return null;
|
|
1600
|
+
let files = this.fileSessions.get(session);
|
|
1601
|
+
if (!files) {
|
|
1602
|
+
files = new FileStore([`/__wxa__/usr/${session}`, `/__wxa__/tmp/${session}`]);
|
|
1603
|
+
this.fileSessions.set(session, files);
|
|
1604
|
+
}
|
|
1605
|
+
return files;
|
|
1606
|
+
};
|
|
1607
|
+
app.post("/__wxa__/fs/:session", express2.json({ limit: "150mb" }), (req, res) => {
|
|
1608
|
+
const files = sessionFiles(req.params.session);
|
|
1609
|
+
if (!files) {
|
|
1610
|
+
res.status(400).json({ error: `invalid filesystem session ${req.params.session}` });
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
try {
|
|
1614
|
+
res.json({ result: files.execute(req.body.operation, req.body.args || {}) });
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
res.json({ error: error.message });
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
app.post("/__wxa__/tmp/:session", express2.raw({ type: () => true, limit: "100mb" }), (req, res) => {
|
|
1362
1620
|
const type = String(req.query.type || "png");
|
|
1363
1621
|
if (!/^[a-z0-9]+$/i.test(type)) {
|
|
1364
1622
|
res.status(400).type("text").send(`\u975E\u6CD5\u7684\u6587\u4EF6\u7C7B\u578B ${type}`);
|
|
1365
1623
|
return;
|
|
1366
1624
|
}
|
|
1367
|
-
const
|
|
1368
|
-
|
|
1369
|
-
|
|
1625
|
+
const files = sessionFiles(req.params.session);
|
|
1626
|
+
if (!files) {
|
|
1627
|
+
res.status(400).type("text").send(`\u975E\u6CD5\u7684\u4F1A\u8BDD\u6807\u8BC6 ${req.params.session}`);
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
const filePath = `/__wxa__/tmp/${req.params.session}/${randomUUID3()}.${type}`;
|
|
1631
|
+
files.write(filePath, Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0));
|
|
1632
|
+
res.json({ url: `${req.protocol}://${req.get("host")}${filePath}` });
|
|
1370
1633
|
});
|
|
1371
1634
|
app.get("/__wxa__/region/:kind", (req, res) => {
|
|
1372
1635
|
const kind = req.params.kind;
|
|
@@ -1377,14 +1640,16 @@ ${error.message}`);
|
|
|
1377
1640
|
}
|
|
1378
1641
|
res.type("text/plain; charset=utf-8").set("Cache-Control", "public, max-age=86400").send(data);
|
|
1379
1642
|
});
|
|
1380
|
-
app.get("/__wxa__
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1643
|
+
app.get("/__wxa__/:kind(usr|tmp)/:session/*", (req, res) => {
|
|
1644
|
+
const files = this.fileSessions.get(req.params.session);
|
|
1645
|
+
const node = files?.stat(`/__wxa__/${req.params.kind}/${req.params.session}/` + req.params[0]);
|
|
1646
|
+
if (!node || node.dir) {
|
|
1383
1647
|
res.status(404).end();
|
|
1384
1648
|
return;
|
|
1385
1649
|
}
|
|
1386
|
-
|
|
1387
|
-
res.
|
|
1650
|
+
const data = node.data;
|
|
1651
|
+
res.type(path4.extname(req.params[0]));
|
|
1652
|
+
res.setHeader("ETag", `"${node.etag}"`);
|
|
1388
1653
|
res.setHeader("Accept-Ranges", "bytes");
|
|
1389
1654
|
const ranges = req.range(data.length);
|
|
1390
1655
|
if (ranges === -1) {
|
|
@@ -1400,8 +1665,15 @@ ${error.message}`);
|
|
|
1400
1665
|
}
|
|
1401
1666
|
res.send(data);
|
|
1402
1667
|
});
|
|
1403
|
-
app.delete("/__wxa__/tmp/:
|
|
1404
|
-
|
|
1668
|
+
app.delete("/__wxa__/tmp/:session/*", (req, res) => {
|
|
1669
|
+
try {
|
|
1670
|
+
const files = this.fileSessions.get(req.params.session);
|
|
1671
|
+
if (!files) throw new Error("file not exist");
|
|
1672
|
+
files.remove(`/__wxa__/tmp/${req.params.session}/` + req.params[0], { dir: false });
|
|
1673
|
+
res.status(204).end();
|
|
1674
|
+
} catch {
|
|
1675
|
+
res.status(404).end();
|
|
1676
|
+
}
|
|
1405
1677
|
});
|
|
1406
1678
|
if (this.ticketSource) app.use("/__wxa__/api", devtoolsLoginRouter(this.project.appid, this.ticketSource));
|
|
1407
1679
|
app.use("/__wxa__/webview", express2.static(root));
|
|
@@ -1490,6 +1762,38 @@ ${message}`);
|
|
|
1490
1762
|
});
|
|
1491
1763
|
}
|
|
1492
1764
|
};
|
|
1765
|
+
var DEFAULT_PORT = 3e3;
|
|
1766
|
+
var LOCAL_HOST_OF_ANY_ADDRESS = /* @__PURE__ */ new Map([["0.0.0.0", "127.0.0.1"], ["::", "localhost"]]);
|
|
1767
|
+
async function listen(server, port, host, logger) {
|
|
1768
|
+
for (let candidate = port ?? DEFAULT_PORT; ; candidate++) {
|
|
1769
|
+
const taken = LOCAL_HOST_OF_ANY_ADDRESS.has(host) && await takenOnLoopback(candidate) || !await bind(server, candidate, host);
|
|
1770
|
+
if (!taken) {
|
|
1771
|
+
const actual = server.address().port;
|
|
1772
|
+
if (port === void 0 && actual !== DEFAULT_PORT) logger.warn(`[wxa-runner] \u7AEF\u53E3 ${DEFAULT_PORT} \u5DF2\u88AB\u5360\u7528\uFF0C\u6539\u7528 ${actual}`);
|
|
1773
|
+
return actual;
|
|
1774
|
+
}
|
|
1775
|
+
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`);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
function bind(server, port, host) {
|
|
1779
|
+
return new Promise((resolve, reject) => {
|
|
1780
|
+
const onError = (error) => error.code === "EADDRINUSE" ? resolve(false) : reject(error);
|
|
1781
|
+
server.once("error", onError);
|
|
1782
|
+
server.listen(port, host, () => {
|
|
1783
|
+
server.off("error", onError);
|
|
1784
|
+
resolve(true);
|
|
1785
|
+
});
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
async function takenOnLoopback(port) {
|
|
1789
|
+
for (const address of ["127.0.0.1", "::1"]) {
|
|
1790
|
+
const probe = net.createServer();
|
|
1791
|
+
const free = await bind(probe, port, address).catch(() => true);
|
|
1792
|
+
if (!free) return true;
|
|
1793
|
+
await new Promise((resolve) => probe.close(resolve));
|
|
1794
|
+
}
|
|
1795
|
+
return false;
|
|
1796
|
+
}
|
|
1493
1797
|
function resolveSetupFile(option, projectPath) {
|
|
1494
1798
|
if (option === false) return null;
|
|
1495
1799
|
if (!option) return findSetupFile(projectPath);
|