@open-wxa/wxa-runner-core 0.12.1 → 0.12.3

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.mjs CHANGED
@@ -98,6 +98,26 @@ var FileStore = class {
98
98
  this.write(destPath, this.read(srcPath));
99
99
  return {};
100
100
  },
101
+ rename: ({ oldPath, newPath }) => {
102
+ const source = this.normalize(oldPath);
103
+ const target = this.normalize(newPath);
104
+ const node = this.existing(oldPath);
105
+ this.assertNotRoot(source);
106
+ this.assertNotRoot(target);
107
+ if (source === target) return {};
108
+ const parent = target.slice(0, target.lastIndexOf("/")) || "/";
109
+ if (!this.nodes.get(parent)?.dir) throw new Error(`no such file or directory ${newPath}`);
110
+ if (node.dir && target.startsWith(source + "/")) throw new Error("invalid argument: directory moved into itself");
111
+ const destination = this.nodes.get(target);
112
+ if (destination) {
113
+ if (destination.dir !== node.dir) throw new Error(`incompatible file type ${newPath}`);
114
+ if (destination.dir && this.readdir(target).length) throw new Error(`directory not empty ${newPath}`);
115
+ }
116
+ const entries = [...this.nodes].filter(([key]) => key === source || node.dir && key.startsWith(source + "/"));
117
+ for (const [key] of entries) this.nodes.delete(key);
118
+ for (const [key, value] of entries) this.nodes.set(target + key.slice(source.length), value);
119
+ return {};
120
+ },
101
121
  access: ({ path: path7 }) => {
102
122
  this.existing(path7);
103
123
  return {};
@@ -225,7 +245,7 @@ import net from "net";
225
245
  import os3 from "os";
226
246
  import path6 from "path";
227
247
  import express2 from "express";
228
- import { WebSocket as WebSocket2, WebSocketServer } from "ws";
248
+ import { WebSocket as WebSocket3, WebSocketServer as WebSocketServer2 } from "ws";
229
249
  import { readRegionData, resolveBaseLib, usesIsolatedContext } from "@open-wxa/wxa-runner-extractor";
230
250
 
231
251
  // src/project/AppBuilder.ts
@@ -580,8 +600,8 @@ var AppBuilder = class {
580
600
  this.pending = { covers: this.changes, result: result2 };
581
601
  }
582
602
  const { covers, result } = this.pending;
583
- const built = await result;
584
- if (covers >= wanted) return built;
603
+ if (covers >= wanted) return result;
604
+ await result.catch(() => void 0);
585
605
  }
586
606
  }
587
607
  /**
@@ -1411,6 +1431,71 @@ async function handleProxy(req, res) {
1411
1431
  }
1412
1432
  }
1413
1433
 
1434
+ // src/server/socketProxy.ts
1435
+ import { WebSocket, WebSocketServer } from "ws";
1436
+ function mirrorClose(to, code, reason) {
1437
+ if (code === 1005) to.close();
1438
+ else if (code === 1006 || code === 1015) to.terminate();
1439
+ else to.close(code, reason);
1440
+ }
1441
+ function createSocketProxy() {
1442
+ const server = new WebSocketServer({
1443
+ noServer: true,
1444
+ // 不能让其他网站借本地开发服务器建立任意带凭据的连接。
1445
+ verifyClient: ({ origin, req }) => origin === `http://${req.headers.host}`
1446
+ });
1447
+ server.on("connection", (client) => {
1448
+ let upstream;
1449
+ const timer = setTimeout(() => client.close(1008, "Missing socket request"), 1e4);
1450
+ timer.unref();
1451
+ client.on("error", () => void 0);
1452
+ client.once("message", (data, binary) => {
1453
+ clearTimeout(timer);
1454
+ try {
1455
+ if (binary || data.toString().length > 64 * 1024) throw new Error("Invalid socket request");
1456
+ const { url, header: header2, protocols, timeout } = JSON.parse(data.toString());
1457
+ const target = new URL(url);
1458
+ if (!["ws:", "wss:"].includes(target.protocol) || target.username || target.password) throw new Error("Invalid socket URL");
1459
+ upstream = new WebSocket(target, protocols || [], {
1460
+ headers: header2 || {},
1461
+ handshakeTimeout: typeof timeout === "number" && timeout > 0 ? timeout : 6e4
1462
+ });
1463
+ } catch {
1464
+ client.close(1008, "Invalid socket request");
1465
+ return;
1466
+ }
1467
+ const socket = upstream;
1468
+ let header = {};
1469
+ socket.on("upgrade", (response) => {
1470
+ header = response.headers;
1471
+ });
1472
+ socket.on("open", () => {
1473
+ if (client.readyState !== WebSocket.OPEN) {
1474
+ socket.close();
1475
+ return;
1476
+ }
1477
+ client.send(JSON.stringify({ ready: true, header }));
1478
+ });
1479
+ socket.on("message", (message, isBinary) => {
1480
+ if (client.readyState === WebSocket.OPEN) client.send(message, { binary: isBinary });
1481
+ });
1482
+ client.on("message", (message, isBinary) => {
1483
+ if (socket.readyState === WebSocket.OPEN) socket.send(message, { binary: isBinary });
1484
+ });
1485
+ socket.on("error", () => client.terminate());
1486
+ socket.on("close", (code, reason) => {
1487
+ if (client.readyState === WebSocket.OPEN) mirrorClose(client, code, reason);
1488
+ });
1489
+ });
1490
+ client.on("close", (code, reason) => {
1491
+ clearTimeout(timer);
1492
+ if (upstream?.readyState === WebSocket.OPEN) mirrorClose(upstream, code, reason);
1493
+ else if (upstream) upstream.terminate();
1494
+ });
1495
+ });
1496
+ return server;
1497
+ }
1498
+
1414
1499
  // src/server/hmr.ts
1415
1500
  var STYLE_FILE = /\.(wxss|less|scss|sass|css)$/;
1416
1501
  function planHotUpdate(files, units) {
@@ -1429,7 +1514,7 @@ function planHotUpdate(files, units) {
1429
1514
  import { execFile } from "child_process";
1430
1515
  import { randomUUID } from "crypto";
1431
1516
  import { setTimeout as sleep } from "timers/promises";
1432
- import { WebSocket } from "ws";
1517
+ import { WebSocket as WebSocket2 } from "ws";
1433
1518
  import { findDeveloperTicket, findElectronDevtoolsCli, TICKET_ENV } from "@open-wxa/wxa-runner-extractor";
1434
1519
  var AUTO_PORT = 9420;
1435
1520
  var CLI_TIMEOUT = 6e4;
@@ -1520,7 +1605,7 @@ function cliErrorMessage(stdout) {
1520
1605
  function requestTicket(port) {
1521
1606
  return new Promise((resolve, reject) => {
1522
1607
  const id = `wxa-runner-${randomUUID()}`;
1523
- const socket = new WebSocket(`ws://127.0.0.1:${port}`);
1608
+ const socket = new WebSocket2(`ws://127.0.0.1:${port}`);
1524
1609
  const settle = (finish) => {
1525
1610
  clearTimeout(timer);
1526
1611
  socket.removeAllListeners().on("error", () => void 0).terminate();
@@ -1906,8 +1991,11 @@ var DevServer = class {
1906
1991
  constructor(options) {
1907
1992
  this.options = options;
1908
1993
  this.server = null;
1994
+ this.socketProxy = null;
1909
1995
  this.sockets = null;
1910
1996
  this.watcher = null;
1997
+ /** 上次编译失败时页面上显示着错误,修好后整页刷新才能清掉 */
1998
+ this.lastBuildFailed = false;
1911
1999
  this.address = "";
1912
2000
  this.lanAccess = false;
1913
2001
  /** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
@@ -1951,20 +2039,27 @@ var DevServer = class {
1951
2039
  return Object.values(os3.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
1952
2040
  }
1953
2041
  async start() {
2042
+ if (this.options.watch !== false) await this.startWatching();
1954
2043
  try {
1955
2044
  const build2 = await this.builder.build();
1956
2045
  this.logger.log(`[wxa-runner] \u7F16\u8BD1\u5B8C\u6210\uFF1A${build2.pages.length} \u4E2A\u9875\u9762\uFF0C${build2.components.length} \u4E2A\u7EC4\u4EF6\uFF0C${build2.durationMs}ms`);
1957
2046
  } catch (error) {
2047
+ this.lastBuildFailed = true;
1958
2048
  this.logger.error(`[wxa-runner] \u7F16\u8BD1\u5931\u8D25\uFF1A
1959
2049
  ${error.message}`);
1960
2050
  }
1961
2051
  const server = http.createServer(this.app);
1962
- this.sockets = new WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });
2052
+ this.sockets = new WebSocketServer2({ noServer: true, maxPayload: 64 * 1024 });
1963
2053
  this.sockets.on("connection", (socket) => {
1964
2054
  socket.on("error", () => void 0);
1965
2055
  if (this.options.forwardConsole) printBrowserReports(socket, this.logger);
1966
2056
  });
2057
+ this.socketProxy = createSocketProxy();
1967
2058
  server.on("upgrade", (req, socket, head) => {
2059
+ if (req.url === "/__wxa__/socket") {
2060
+ this.socketProxy.handleUpgrade(req, socket, head, (ws) => this.socketProxy.emit("connection", ws, req));
2061
+ return;
2062
+ }
1968
2063
  if (req.url?.startsWith("/__wxa__/ws")) {
1969
2064
  this.sockets.handleUpgrade(req, socket, head, (ws) => this.sockets.emit("connection", ws, req));
1970
2065
  } else {
@@ -1972,11 +2067,17 @@ ${error.message}`);
1972
2067
  }
1973
2068
  });
1974
2069
  const host = this.options.host || "localhost";
1975
- const port = await listen(server, this.options.port, host, this.logger);
2070
+ let port;
2071
+ try {
2072
+ port = await listen(server, this.options.port, host, this.logger);
2073
+ } catch (error) {
2074
+ await this.watcher?.close();
2075
+ this.watcher = null;
2076
+ throw error;
2077
+ }
1976
2078
  this.server = server;
1977
2079
  this.lanAccess = LOCAL_HOST_OF_ANY_ADDRESS.has(host);
1978
2080
  this.address = `http://${LOCAL_HOST_OF_ANY_ADDRESS.get(host) ?? host}:${port}`;
1979
- if (this.options.watch !== false) this.startWatching();
1980
2081
  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}`));
1981
2082
  return this.address;
1982
2083
  }
@@ -1984,6 +2085,8 @@ ${error.message}`);
1984
2085
  await this.watcher?.close();
1985
2086
  this.sockets?.clients.forEach((client) => client.terminate());
1986
2087
  this.sockets?.close();
2088
+ this.socketProxy?.clients.forEach((client) => client.terminate());
2089
+ this.socketProxy?.close();
1987
2090
  const server = this.server;
1988
2091
  this.server = null;
1989
2092
  if (server) {
@@ -2014,6 +2117,7 @@ ${error.message}`);
2014
2117
  baseLibVersion: this.baseLib.version,
2015
2118
  device: this.device,
2016
2119
  devtoolsLogin: this.devtoolsLogin,
2120
+ socketProxy: true,
2017
2121
  forwardConsole: this.options.forwardConsole ?? false,
2018
2122
  webViewWindow: this.options.webViewWindow ?? false
2019
2123
  };
@@ -2210,36 +2314,37 @@ ${error.message}`);
2210
2314
  get hotEnabled() {
2211
2315
  return this.options.watch !== false;
2212
2316
  }
2317
+ /** 返回初次扫描完成的时刻,此前已存在的文件不会作为改动报告 */
2213
2318
  startWatching() {
2214
- let lastBuildFailed = false;
2215
2319
  const watched = [this.project.root];
2216
2320
  const outsideRoot = (file) => path6.relative(this.project.root, file).startsWith("..");
2217
2321
  if (this.setupFile && outsideRoot(this.setupFile)) watched.push(this.setupFile);
2218
2322
  const projectConfig = path6.join(this.project.projectPath, "project.config.json");
2219
2323
  if (outsideRoot(projectConfig) && fs6.existsSync(projectConfig)) watched.push(projectConfig);
2220
2324
  this.watcher = watchProject(this.project.root, watched, async ({ files, structural }) => {
2221
- const reloadOnly = structural || lastBuildFailed;
2325
+ const reloadOnly = structural || this.lastBuildFailed;
2222
2326
  this.builder.invalidate(files);
2223
2327
  const label = files.length > 3 ? `${files.slice(0, 3).join(", ")} \u7B49 ${files.length} \u4E2A\u6587\u4EF6` : files.join(", ");
2224
2328
  try {
2225
2329
  const build2 = await this.builder.build();
2226
- lastBuildFailed = false;
2330
+ this.lastBuildFailed = false;
2227
2331
  const plan = reloadOnly ? null : planHotUpdate(files, build2);
2228
2332
  this.logger.log(`[wxa-runner] ${label} \u5DF2\u66F4\u65B0\uFF0C\u91CD\u65B0\u7F16\u8BD1 ${build2.durationMs}ms${plan ? "\uFF0C\u70ED\u66F4\u65B0" : ""}`);
2229
2333
  this.broadcast(plan ? { type: "hot", version: build2.version, ...plan } : { type: "reload" });
2230
2334
  } catch (error) {
2231
- lastBuildFailed = true;
2335
+ this.lastBuildFailed = true;
2232
2336
  const message = error.message;
2233
2337
  this.logger.error(`[wxa-runner] \u7F16\u8BD1\u5931\u8D25\uFF1A
2234
2338
  ${message}`);
2235
2339
  this.broadcast({ type: "error", message });
2236
2340
  }
2237
2341
  });
2342
+ return this.watcher.ready;
2238
2343
  }
2239
2344
  broadcast(message) {
2240
2345
  const data = JSON.stringify(message);
2241
2346
  this.sockets?.clients.forEach((client) => {
2242
- if (client.readyState === WebSocket2.OPEN) client.send(data);
2347
+ if (client.readyState === WebSocket3.OPEN) client.send(data);
2243
2348
  });
2244
2349
  }
2245
2350
  };