@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.d.mts CHANGED
@@ -1,9 +1,9 @@
1
1
  import http, { IncomingMessage, ServerResponse } from 'http';
2
2
  import { ResolvedBaseLib } from '@open-wxa/wxa-runner-extractor';
3
- import { D as DeviceProfile, F as ForwardConsole } from './setup-D0ubZt2C.mjs';
4
- export { A as ApiContext, a as ApiHandler, b as ApiResult, c as AppInfo, M as MockConfig, d as MockRequest, e as MockResponse, f as MockValue, N as NetworkConfig, R as RouteHandler, S as SetupApi, g as SetupConfig, h as SetupContext, i as SetupDefinition } from './setup-D0ubZt2C.mjs';
3
+ import { D as DeviceProfile, F as ForwardConsole } from './setup--EIOgQvG.mjs';
4
+ export { A as ApiContext, a as ApiHandler, b as ApiResult, c as AppInfo, M as MockConfig, d as MockRequest, e as MockResponse, f as MockValue, N as NetworkConfig, R as RouteHandler, S as SetupApi, g as SetupConfig, h as SetupContext, i as SetupDefinition } from './setup--EIOgQvG.mjs';
5
5
  import { Request, Response } from 'express';
6
- export { F as FrameError, P as PageSnapshot, R as RecordedRequest, W as WebViewInfo, a as WxaTestApi } from './testApi-x2Yph6M2.mjs';
6
+ export { F as FrameError, P as PageSnapshot, R as RecordedRequest, W as WebViewInfo, a as WxaTestApi } from './testApi-CoYw0cPm.mjs';
7
7
 
8
8
  /**
9
9
  * 样式预处理:project.config.json 的 setting.useCompilerPlugins 开启 less / sass 时,
@@ -266,6 +266,7 @@ interface ExtensionEntry {
266
266
  * /__wxa__/usr/<会话>/<路径> 用户目录(USER_DATA_PATH)下的文件,image 等组件和 uploadFile 按地址读取
267
267
  * /__wxa__/region/<kind> 地区选择器用的行政区划数据(districts / streets),取自本机开发者工具
268
268
  * /__wxa__/api/* --devtools-login:wx.login 的 code、手机号,服务市场、插件的请求转发,只允许本机访问
269
+ * /__wxa__/socket 带自定义请求头的 WebSocket 转发,只接受同源连接
269
270
  * /__wxa__/ws 文件变化通知:热更新(hot)或整页刷新(reload);开启 forwardConsole 时浏览器从这里发来报错
270
271
  * middlewares / 扩展挂载的路径 排在 runner 自己的路由之前
271
272
  * 其余路径 小程序目录下的静态资源
@@ -355,8 +356,11 @@ declare class DevServer {
355
356
  private readonly logger;
356
357
  private readonly app;
357
358
  private server;
359
+ private socketProxy;
358
360
  private sockets;
359
361
  private watcher;
362
+ /** 上次编译失败时页面上显示着错误,修好后整页刷新才能清掉 */
363
+ private lastBuildFailed;
360
364
  private address;
361
365
  private lanAccess;
362
366
  /** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
@@ -386,6 +390,7 @@ declare class DevServer {
386
390
  private runtimeDir;
387
391
  /** 只在监听文件变化时打开基础库的热更新开关,跑测试(watch: false)时运行环境与真机一致 */
388
392
  private get hotEnabled();
393
+ /** 返回初次扫描完成的时刻,此前已存在的文件不会作为改动报告 */
389
394
  private startWatching;
390
395
  private broadcast;
391
396
  }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import http, { IncomingMessage, ServerResponse } from 'http';
2
2
  import { ResolvedBaseLib } from '@open-wxa/wxa-runner-extractor';
3
- import { D as DeviceProfile, F as ForwardConsole } from './setup-D0ubZt2C.js';
4
- export { A as ApiContext, a as ApiHandler, b as ApiResult, c as AppInfo, M as MockConfig, d as MockRequest, e as MockResponse, f as MockValue, N as NetworkConfig, R as RouteHandler, S as SetupApi, g as SetupConfig, h as SetupContext, i as SetupDefinition } from './setup-D0ubZt2C.js';
3
+ import { D as DeviceProfile, F as ForwardConsole } from './setup--EIOgQvG.js';
4
+ export { A as ApiContext, a as ApiHandler, b as ApiResult, c as AppInfo, M as MockConfig, d as MockRequest, e as MockResponse, f as MockValue, N as NetworkConfig, R as RouteHandler, S as SetupApi, g as SetupConfig, h as SetupContext, i as SetupDefinition } from './setup--EIOgQvG.js';
5
5
  import { Request, Response } from 'express';
6
- export { F as FrameError, P as PageSnapshot, R as RecordedRequest, W as WebViewInfo, a as WxaTestApi } from './testApi-Df0ltBI6.js';
6
+ export { F as FrameError, P as PageSnapshot, R as RecordedRequest, W as WebViewInfo, a as WxaTestApi } from './testApi-BfzCTOhg.js';
7
7
 
8
8
  /**
9
9
  * 样式预处理:project.config.json 的 setting.useCompilerPlugins 开启 less / sass 时,
@@ -266,6 +266,7 @@ interface ExtensionEntry {
266
266
  * /__wxa__/usr/<会话>/<路径> 用户目录(USER_DATA_PATH)下的文件,image 等组件和 uploadFile 按地址读取
267
267
  * /__wxa__/region/<kind> 地区选择器用的行政区划数据(districts / streets),取自本机开发者工具
268
268
  * /__wxa__/api/* --devtools-login:wx.login 的 code、手机号,服务市场、插件的请求转发,只允许本机访问
269
+ * /__wxa__/socket 带自定义请求头的 WebSocket 转发,只接受同源连接
269
270
  * /__wxa__/ws 文件变化通知:热更新(hot)或整页刷新(reload);开启 forwardConsole 时浏览器从这里发来报错
270
271
  * middlewares / 扩展挂载的路径 排在 runner 自己的路由之前
271
272
  * 其余路径 小程序目录下的静态资源
@@ -355,8 +356,11 @@ declare class DevServer {
355
356
  private readonly logger;
356
357
  private readonly app;
357
358
  private server;
359
+ private socketProxy;
358
360
  private sockets;
359
361
  private watcher;
362
+ /** 上次编译失败时页面上显示着错误,修好后整页刷新才能清掉 */
363
+ private lastBuildFailed;
360
364
  private address;
361
365
  private lanAccess;
362
366
  /** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
@@ -386,6 +390,7 @@ declare class DevServer {
386
390
  private runtimeDir;
387
391
  /** 只在监听文件变化时打开基础库的热更新开关,跑测试(watch: false)时运行环境与真机一致 */
388
392
  private get hotEnabled();
393
+ /** 返回初次扫描完成的时刻,此前已存在的文件不会作为改动报告 */
389
394
  private startWatching;
390
395
  private broadcast;
391
396
  }
package/dist/index.js CHANGED
@@ -140,6 +140,26 @@ var FileStore = class {
140
140
  this.write(destPath, this.read(srcPath));
141
141
  return {};
142
142
  },
143
+ rename: ({ oldPath, newPath }) => {
144
+ const source = this.normalize(oldPath);
145
+ const target = this.normalize(newPath);
146
+ const node = this.existing(oldPath);
147
+ this.assertNotRoot(source);
148
+ this.assertNotRoot(target);
149
+ if (source === target) return {};
150
+ const parent = target.slice(0, target.lastIndexOf("/")) || "/";
151
+ if (!this.nodes.get(parent)?.dir) throw new Error(`no such file or directory ${newPath}`);
152
+ if (node.dir && target.startsWith(source + "/")) throw new Error("invalid argument: directory moved into itself");
153
+ const destination = this.nodes.get(target);
154
+ if (destination) {
155
+ if (destination.dir !== node.dir) throw new Error(`incompatible file type ${newPath}`);
156
+ if (destination.dir && this.readdir(target).length) throw new Error(`directory not empty ${newPath}`);
157
+ }
158
+ const entries = [...this.nodes].filter(([key]) => key === source || node.dir && key.startsWith(source + "/"));
159
+ for (const [key] of entries) this.nodes.delete(key);
160
+ for (const [key, value] of entries) this.nodes.set(target + key.slice(source.length), value);
161
+ return {};
162
+ },
143
163
  access: ({ path: path7 }) => {
144
164
  this.existing(path7);
145
165
  return {};
@@ -267,7 +287,7 @@ var import_net = __toESM(require("net"));
267
287
  var import_os3 = __toESM(require("os"));
268
288
  var import_path6 = __toESM(require("path"));
269
289
  var import_express2 = __toESM(require("express"));
270
- var import_ws2 = require("ws");
290
+ var import_ws3 = require("ws");
271
291
  var import_wxa_runner_extractor5 = require("@open-wxa/wxa-runner-extractor");
272
292
 
273
293
  // src/project/AppBuilder.ts
@@ -622,8 +642,8 @@ var AppBuilder = class {
622
642
  this.pending = { covers: this.changes, result: result2 };
623
643
  }
624
644
  const { covers, result } = this.pending;
625
- const built = await result;
626
- if (covers >= wanted) return built;
645
+ if (covers >= wanted) return result;
646
+ await result.catch(() => void 0);
627
647
  }
628
648
  }
629
649
  /**
@@ -1453,6 +1473,71 @@ async function handleProxy(req, res) {
1453
1473
  }
1454
1474
  }
1455
1475
 
1476
+ // src/server/socketProxy.ts
1477
+ var import_ws = require("ws");
1478
+ function mirrorClose(to, code, reason) {
1479
+ if (code === 1005) to.close();
1480
+ else if (code === 1006 || code === 1015) to.terminate();
1481
+ else to.close(code, reason);
1482
+ }
1483
+ function createSocketProxy() {
1484
+ const server = new import_ws.WebSocketServer({
1485
+ noServer: true,
1486
+ // 不能让其他网站借本地开发服务器建立任意带凭据的连接。
1487
+ verifyClient: ({ origin, req }) => origin === `http://${req.headers.host}`
1488
+ });
1489
+ server.on("connection", (client) => {
1490
+ let upstream;
1491
+ const timer = setTimeout(() => client.close(1008, "Missing socket request"), 1e4);
1492
+ timer.unref();
1493
+ client.on("error", () => void 0);
1494
+ client.once("message", (data, binary) => {
1495
+ clearTimeout(timer);
1496
+ try {
1497
+ if (binary || data.toString().length > 64 * 1024) throw new Error("Invalid socket request");
1498
+ const { url, header: header2, protocols, timeout } = JSON.parse(data.toString());
1499
+ const target = new URL(url);
1500
+ if (!["ws:", "wss:"].includes(target.protocol) || target.username || target.password) throw new Error("Invalid socket URL");
1501
+ upstream = new import_ws.WebSocket(target, protocols || [], {
1502
+ headers: header2 || {},
1503
+ handshakeTimeout: typeof timeout === "number" && timeout > 0 ? timeout : 6e4
1504
+ });
1505
+ } catch {
1506
+ client.close(1008, "Invalid socket request");
1507
+ return;
1508
+ }
1509
+ const socket = upstream;
1510
+ let header = {};
1511
+ socket.on("upgrade", (response) => {
1512
+ header = response.headers;
1513
+ });
1514
+ socket.on("open", () => {
1515
+ if (client.readyState !== import_ws.WebSocket.OPEN) {
1516
+ socket.close();
1517
+ return;
1518
+ }
1519
+ client.send(JSON.stringify({ ready: true, header }));
1520
+ });
1521
+ socket.on("message", (message, isBinary) => {
1522
+ if (client.readyState === import_ws.WebSocket.OPEN) client.send(message, { binary: isBinary });
1523
+ });
1524
+ client.on("message", (message, isBinary) => {
1525
+ if (socket.readyState === import_ws.WebSocket.OPEN) socket.send(message, { binary: isBinary });
1526
+ });
1527
+ socket.on("error", () => client.terminate());
1528
+ socket.on("close", (code, reason) => {
1529
+ if (client.readyState === import_ws.WebSocket.OPEN) mirrorClose(client, code, reason);
1530
+ });
1531
+ });
1532
+ client.on("close", (code, reason) => {
1533
+ clearTimeout(timer);
1534
+ if (upstream?.readyState === import_ws.WebSocket.OPEN) mirrorClose(upstream, code, reason);
1535
+ else if (upstream) upstream.terminate();
1536
+ });
1537
+ });
1538
+ return server;
1539
+ }
1540
+
1456
1541
  // src/server/hmr.ts
1457
1542
  var STYLE_FILE = /\.(wxss|less|scss|sass|css)$/;
1458
1543
  function planHotUpdate(files, units) {
@@ -1471,7 +1556,7 @@ function planHotUpdate(files, units) {
1471
1556
  var import_child_process = require("child_process");
1472
1557
  var import_crypto = require("crypto");
1473
1558
  var import_promises2 = require("timers/promises");
1474
- var import_ws = require("ws");
1559
+ var import_ws2 = require("ws");
1475
1560
  var import_wxa_runner_extractor = require("@open-wxa/wxa-runner-extractor");
1476
1561
  var AUTO_PORT = 9420;
1477
1562
  var CLI_TIMEOUT = 6e4;
@@ -1562,7 +1647,7 @@ function cliErrorMessage(stdout) {
1562
1647
  function requestTicket(port) {
1563
1648
  return new Promise((resolve, reject) => {
1564
1649
  const id = `wxa-runner-${(0, import_crypto.randomUUID)()}`;
1565
- const socket = new import_ws.WebSocket(`ws://127.0.0.1:${port}`);
1650
+ const socket = new import_ws2.WebSocket(`ws://127.0.0.1:${port}`);
1566
1651
  const settle = (finish) => {
1567
1652
  clearTimeout(timer);
1568
1653
  socket.removeAllListeners().on("error", () => void 0).terminate();
@@ -1948,8 +2033,11 @@ var DevServer = class {
1948
2033
  constructor(options) {
1949
2034
  this.options = options;
1950
2035
  this.server = null;
2036
+ this.socketProxy = null;
1951
2037
  this.sockets = null;
1952
2038
  this.watcher = null;
2039
+ /** 上次编译失败时页面上显示着错误,修好后整页刷新才能清掉 */
2040
+ this.lastBuildFailed = false;
1953
2041
  this.address = "";
1954
2042
  this.lanAccess = false;
1955
2043
  /** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
@@ -1993,20 +2081,27 @@ var DevServer = class {
1993
2081
  return Object.values(import_os3.default.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
1994
2082
  }
1995
2083
  async start() {
2084
+ if (this.options.watch !== false) await this.startWatching();
1996
2085
  try {
1997
2086
  const build2 = await this.builder.build();
1998
2087
  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`);
1999
2088
  } catch (error) {
2089
+ this.lastBuildFailed = true;
2000
2090
  this.logger.error(`[wxa-runner] \u7F16\u8BD1\u5931\u8D25\uFF1A
2001
2091
  ${error.message}`);
2002
2092
  }
2003
2093
  const server = import_http.default.createServer(this.app);
2004
- this.sockets = new import_ws2.WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });
2094
+ this.sockets = new import_ws3.WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });
2005
2095
  this.sockets.on("connection", (socket) => {
2006
2096
  socket.on("error", () => void 0);
2007
2097
  if (this.options.forwardConsole) printBrowserReports(socket, this.logger);
2008
2098
  });
2099
+ this.socketProxy = createSocketProxy();
2009
2100
  server.on("upgrade", (req, socket, head) => {
2101
+ if (req.url === "/__wxa__/socket") {
2102
+ this.socketProxy.handleUpgrade(req, socket, head, (ws) => this.socketProxy.emit("connection", ws, req));
2103
+ return;
2104
+ }
2010
2105
  if (req.url?.startsWith("/__wxa__/ws")) {
2011
2106
  this.sockets.handleUpgrade(req, socket, head, (ws) => this.sockets.emit("connection", ws, req));
2012
2107
  } else {
@@ -2014,11 +2109,17 @@ ${error.message}`);
2014
2109
  }
2015
2110
  });
2016
2111
  const host = this.options.host || "localhost";
2017
- const port = await listen(server, this.options.port, host, this.logger);
2112
+ let port;
2113
+ try {
2114
+ port = await listen(server, this.options.port, host, this.logger);
2115
+ } catch (error) {
2116
+ await this.watcher?.close();
2117
+ this.watcher = null;
2118
+ throw error;
2119
+ }
2018
2120
  this.server = server;
2019
2121
  this.lanAccess = LOCAL_HOST_OF_ANY_ADDRESS.has(host);
2020
2122
  this.address = `http://${LOCAL_HOST_OF_ANY_ADDRESS.get(host) ?? host}:${port}`;
2021
- if (this.options.watch !== false) this.startWatching();
2022
2123
  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}`));
2023
2124
  return this.address;
2024
2125
  }
@@ -2026,6 +2127,8 @@ ${error.message}`);
2026
2127
  await this.watcher?.close();
2027
2128
  this.sockets?.clients.forEach((client) => client.terminate());
2028
2129
  this.sockets?.close();
2130
+ this.socketProxy?.clients.forEach((client) => client.terminate());
2131
+ this.socketProxy?.close();
2029
2132
  const server = this.server;
2030
2133
  this.server = null;
2031
2134
  if (server) {
@@ -2056,6 +2159,7 @@ ${error.message}`);
2056
2159
  baseLibVersion: this.baseLib.version,
2057
2160
  device: this.device,
2058
2161
  devtoolsLogin: this.devtoolsLogin,
2162
+ socketProxy: true,
2059
2163
  forwardConsole: this.options.forwardConsole ?? false,
2060
2164
  webViewWindow: this.options.webViewWindow ?? false
2061
2165
  };
@@ -2252,36 +2356,37 @@ ${error.message}`);
2252
2356
  get hotEnabled() {
2253
2357
  return this.options.watch !== false;
2254
2358
  }
2359
+ /** 返回初次扫描完成的时刻,此前已存在的文件不会作为改动报告 */
2255
2360
  startWatching() {
2256
- let lastBuildFailed = false;
2257
2361
  const watched = [this.project.root];
2258
2362
  const outsideRoot = (file) => import_path6.default.relative(this.project.root, file).startsWith("..");
2259
2363
  if (this.setupFile && outsideRoot(this.setupFile)) watched.push(this.setupFile);
2260
2364
  const projectConfig = import_path6.default.join(this.project.projectPath, "project.config.json");
2261
2365
  if (outsideRoot(projectConfig) && import_fs6.default.existsSync(projectConfig)) watched.push(projectConfig);
2262
2366
  this.watcher = watchProject(this.project.root, watched, async ({ files, structural }) => {
2263
- const reloadOnly = structural || lastBuildFailed;
2367
+ const reloadOnly = structural || this.lastBuildFailed;
2264
2368
  this.builder.invalidate(files);
2265
2369
  const label = files.length > 3 ? `${files.slice(0, 3).join(", ")} \u7B49 ${files.length} \u4E2A\u6587\u4EF6` : files.join(", ");
2266
2370
  try {
2267
2371
  const build2 = await this.builder.build();
2268
- lastBuildFailed = false;
2372
+ this.lastBuildFailed = false;
2269
2373
  const plan = reloadOnly ? null : planHotUpdate(files, build2);
2270
2374
  this.logger.log(`[wxa-runner] ${label} \u5DF2\u66F4\u65B0\uFF0C\u91CD\u65B0\u7F16\u8BD1 ${build2.durationMs}ms${plan ? "\uFF0C\u70ED\u66F4\u65B0" : ""}`);
2271
2375
  this.broadcast(plan ? { type: "hot", version: build2.version, ...plan } : { type: "reload" });
2272
2376
  } catch (error) {
2273
- lastBuildFailed = true;
2377
+ this.lastBuildFailed = true;
2274
2378
  const message = error.message;
2275
2379
  this.logger.error(`[wxa-runner] \u7F16\u8BD1\u5931\u8D25\uFF1A
2276
2380
  ${message}`);
2277
2381
  this.broadcast({ type: "error", message });
2278
2382
  }
2279
2383
  });
2384
+ return this.watcher.ready;
2280
2385
  }
2281
2386
  broadcast(message) {
2282
2387
  const data = JSON.stringify(message);
2283
2388
  this.sockets?.clients.forEach((client) => {
2284
- if (client.readyState === import_ws2.WebSocket.OPEN) client.send(data);
2389
+ if (client.readyState === import_ws3.WebSocket.OPEN) client.send(data);
2285
2390
  });
2286
2391
  }
2287
2392
  };