@open-wxa/wxa-runner-core 0.2.3 → 0.2.5

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
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  __dirname
3
- } from "./chunk-22T3D5KS.mjs";
3
+ } from "./chunk-NGXYLLYS.mjs";
4
4
 
5
5
  // src/server/DevServer.ts
6
- import { randomUUID } from "crypto";
6
+ import { randomUUID as randomUUID2 } from "crypto";
7
7
  import fs4 from "fs";
8
8
  import http from "http";
9
9
  import path4 from "path";
10
10
  import express2 from "express";
11
- import { WebSocket, WebSocketServer } from "ws";
12
- import { findDeveloperTicket as findDeveloperTicket2, findRegionDataFile, resolveBaseLib, usesIsolatedContext } from "@open-wxa/wxa-runner-extractor";
11
+ import { WebSocket as WebSocket2, WebSocketServer } from "ws";
12
+ import { readRegionData, resolveBaseLib, usesIsolatedContext } from "@open-wxa/wxa-runner-extractor";
13
13
 
14
14
  // src/project/AppBuilder.ts
15
15
  import { compileWxml, compileWxss } from "@open-wxa/wxa-runner-compiler";
@@ -653,28 +653,164 @@ function planHotUpdate(files, units) {
653
653
  return plan;
654
654
  }
655
655
 
656
+ // src/server/developerTicket.ts
657
+ import { execFile } from "child_process";
658
+ import { randomUUID } from "crypto";
659
+ import { setTimeout as sleep } from "timers/promises";
660
+ import { WebSocket } from "ws";
661
+ import { findDeveloperTicket, findElectronDevtoolsCli, TICKET_ENV } from "@open-wxa/wxa-runner-extractor";
662
+ var AUTO_PORT = 9420;
663
+ var CLI_TIMEOUT = 6e4;
664
+ var AUTOMATOR_TIMEOUT = 5e3;
665
+ var WINDOW_TIMEOUT = 1e4;
666
+ var WINDOW_RETRY_INTERVAL = 500;
667
+ function developerTicketSource(projectPath, logger) {
668
+ const cli = findElectronDevtoolsCli();
669
+ if (!cli) return async () => findDeveloperTicket();
670
+ let automatorPort = null;
671
+ const startOnce = () => {
672
+ if (!automatorPort) {
673
+ const starting = startAutomation(cli, projectPath).then((port) => {
674
+ logger.log(`[wxa-runner] \u771F\u5B9E\u767B\u5F55\uFF1A\u5DF2\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u4E3A\u9879\u76EE\u5F00\u542F\u81EA\u52A8\u5316\u63A5\u53E3\uFF08\u7AEF\u53E3 ${port}\uFF09\uFF0Cticket \u4ECE\u5F00\u53D1\u8005\u5DE5\u5177\u5B9E\u65F6\u83B7\u53D6`);
675
+ return port;
676
+ });
677
+ starting.catch(() => {
678
+ if (automatorPort === starting) automatorPort = null;
679
+ });
680
+ automatorPort = starting;
681
+ }
682
+ return automatorPort;
683
+ };
684
+ return async () => {
685
+ if (process.env[TICKET_ENV]) return { newticket: process.env[TICKET_ENV] };
686
+ const port = startOnce();
687
+ try {
688
+ return await requestTicketOnceWindowReady(await port, projectPath);
689
+ } catch (error) {
690
+ if (!(error instanceof AutomatorGone)) throw error;
691
+ if (automatorPort === port) automatorPort = null;
692
+ return requestTicketOnceWindowReady(await startOnce(), projectPath);
693
+ }
694
+ };
695
+ }
696
+ var AutomatorGone = class extends Error {
697
+ };
698
+ var WindowNotFound = class extends Error {
699
+ };
700
+ async function requestTicketOnceWindowReady(port, projectPath) {
701
+ const deadline = Date.now() + WINDOW_TIMEOUT;
702
+ for (; ; ) {
703
+ try {
704
+ return await requestTicket(port);
705
+ } catch (error) {
706
+ if (!(error instanceof WindowNotFound)) throw error;
707
+ 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}`);
708
+ await sleep(WINDOW_RETRY_INTERVAL);
709
+ }
710
+ }
711
+ }
712
+ async function startAutomation(cli, projectPath) {
713
+ const stdout = await runCli(cli, ["agent", "start", "--project", projectPath, "--auto-port", String(AUTO_PORT), "--lang", "zh"]);
714
+ let autoPort = NaN;
715
+ try {
716
+ autoPort = Number(JSON.parse(stdout.match(/^\{$[\s\S]*?^\}$/m)?.[0] ?? "").autoPort);
717
+ } catch {
718
+ }
719
+ if (Number.isInteger(autoPort) && autoPort > 0) return autoPort;
720
+ if (stdout.includes("\u670D\u52A1\u7AEF\u53E3\u5DF2\u5173\u95ED")) {
721
+ throw new Error("\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u670D\u52A1\u7AEF\u53E3\u5DF2\u5173\u95ED\uFF0C\u8BF7\u5728\u5F00\u53D1\u8005\u5DE5\u5177\u300C\u8BBE\u7F6E \u2192 \u5B89\u5168\u8BBE\u7F6E\u300D\u91CC\u5F00\u542F\u670D\u52A1\u7AEF\u53E3");
722
+ }
723
+ throw new Error(`\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u6CA1\u80FD\u4E3A\u9879\u76EE\u5F00\u542F\u81EA\u52A8\u5316\u63A5\u53E3\uFF1A${cliErrorMessage(stdout)}`);
724
+ }
725
+ function runCli(cli, args) {
726
+ const bat = cli.endsWith(".bat");
727
+ const [file, fileArgs] = bat ? [`"${cli}"`, args.map((arg) => `"${arg}"`)] : [cli, args];
728
+ return new Promise((resolve, reject) => {
729
+ const child = execFile(file, fileArgs, { shell: bat, timeout: CLI_TIMEOUT, windowsHide: true }, (error, stdout) => {
730
+ if (error?.killed) {
731
+ reject(new Error(`\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u547D\u4EE4\u884C ${CLI_TIMEOUT / 1e3}s \u5185\u6CA1\u6709\u5B8C\u6210\uFF0C\u8BF7\u770B\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u662F\u5426\u6709\u7B49\u5F85\u786E\u8BA4\u7684\u5F39\u7A97\uFF08\u5982\u4FE1\u4EFB\u9879\u76EE\u3001\u626B\u7801\u767B\u5F55\uFF09`));
732
+ } else if (error && !stdout) {
733
+ reject(new Error(`\u8FD0\u884C\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u547D\u4EE4\u884C\u5931\u8D25\uFF1A${error.message}`));
734
+ } else {
735
+ resolve(stdout);
736
+ }
737
+ });
738
+ child.stdin?.end();
739
+ });
740
+ }
741
+ function cliErrorMessage(stdout) {
742
+ const output = stdout.replace(/\x1b\[[0-9;]*m/g, "");
743
+ const start = output.indexOf("[error]");
744
+ const error = (start < 0 ? output : output.slice(start + "[error]".length)).trim();
745
+ const message = error.match(/message: (['"`])((?:\\.|(?!\1)[^\\])*?)(?:\\n|\1)/)?.[2];
746
+ return message ?? (error.replace(/\s+/g, " ").slice(0, 200) || "\u547D\u4EE4\u884C\u6CA1\u6709\u8F93\u51FA");
747
+ }
748
+ function requestTicket(port) {
749
+ return new Promise((resolve, reject) => {
750
+ const id = `wxa-runner-${randomUUID()}`;
751
+ const socket = new WebSocket(`ws://127.0.0.1:${port}`);
752
+ const settle = (finish) => {
753
+ clearTimeout(timer);
754
+ socket.removeAllListeners().on("error", () => void 0).terminate();
755
+ finish();
756
+ };
757
+ const timer = setTimeout(
758
+ () => settle(
759
+ () => reject(new AutomatorGone(`\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u81EA\u52A8\u5316\u63A5\u53E3 ${AUTOMATOR_TIMEOUT / 1e3}s \u5185\u6CA1\u6709\u54CD\u5E94\uFF0C\u8BF7\u786E\u8BA4\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u6253\u5F00\u4E86\u9879\u76EE\u7A97\u53E3`))
760
+ ),
761
+ AUTOMATOR_TIMEOUT
762
+ );
763
+ const gone = () => settle(() => reject(new AutomatorGone(`\u8FDE\u4E0D\u4E0A\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u81EA\u52A8\u5316\u63A5\u53E3\uFF08\u7AEF\u53E3 ${port}\uFF09\uFF0C\u8BF7\u786E\u8BA4\u5F00\u53D1\u8005\u5DE5\u5177\u5728\u8FD0\u884C`)));
764
+ socket.on("error", gone);
765
+ socket.on("close", gone);
766
+ socket.on("open", () => socket.send(JSON.stringify({ id, method: "Tool.getTicket", params: {} })));
767
+ socket.on("message", (data) => {
768
+ let message;
769
+ try {
770
+ message = JSON.parse(String(data));
771
+ } catch {
772
+ return;
773
+ }
774
+ if (message?.id !== id) return;
775
+ if (message.error) settle(() => reject(explainAutomatorError(String(message.error.message))));
776
+ else if (typeof message.result?.ticket !== "string") settle(() => reject(new Error("\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u6CA1\u6709\u8FD4\u56DE\u767B\u5F55\u7968\u636E")));
777
+ else settle(() => resolve({ newticket: message.result.ticket, expiredAt: message.result.expiredTime }));
778
+ });
779
+ });
780
+ }
781
+ function explainAutomatorError(message) {
782
+ if (message.includes("GUI project window is not found")) return new WindowNotFound(message);
783
+ if (message.includes("not allow to get")) {
784
+ return new Error("\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u4E0D\u5141\u8BB8\u83B7\u53D6\u767B\u5F55\u7968\u636E\uFF0C\u8BF7\u5728\u5F00\u53D1\u8005\u5DE5\u5177\u300C\u8BBE\u7F6E \u2192 \u5B89\u5168\u8BBE\u7F6E\u300D\u91CC\u6253\u5F00\u300C\u5141\u8BB8\u83B7\u53D6\u5DE5\u5177\u767B\u5F55\u7968\u636E\u300D");
785
+ }
786
+ if (message.includes("need to login")) return new Error("\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u5728\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u767B\u5F55");
787
+ return new Error(`\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u8FD4\u56DE\u9519\u8BEF\uFF1A${message}`);
788
+ }
789
+
656
790
  // src/server/realLogin.ts
657
791
  import express from "express";
658
- import { findDeveloperTicket, TICKET_ENV } from "@open-wxa/wxa-runner-extractor";
792
+ import { findDeveloperTicket as findDeveloperTicket2, TICKET_ENV as TICKET_ENV2 } from "@open-wxa/wxa-runner-extractor";
659
793
  var DEV_LOGIC = "https://servicewechat.com/wxa-dev-logic";
660
794
  var CLIENT_VERSION = "1021810240";
661
795
  var UPSTREAM_TIMEOUT = 1e4;
662
796
  var LOOPBACK_ADDRESSES = ["127.0.0.1", "::1", "::ffff:127.0.0.1"];
663
797
  var LOOPBACK_HOSTS = ["localhost", "127.0.0.1", "[::1]"];
664
798
  var ERROR_HINTS = {
799
+ // 40001:开发者工具刷新过 ticket,旧的随即作废
800
+ 40001: "\uFF08\u767B\u5F55\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u91CD\u65B0\u767B\u5F55\uFF09",
665
801
  40029: "\uFF08\u767B\u5F55\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u91CD\u65B0\u767B\u5F55\uFF09",
666
802
  85023: "\uFF08\u5F53\u524D\u5FAE\u4FE1\u8D26\u53F7\u4E0D\u662F\u8BE5\u5C0F\u7A0B\u5E8F\u7684\u5F00\u53D1\u8005\uFF09"
667
803
  };
668
804
  function requireUsableTicket(ticket) {
669
- if (!ticket) throw new Error(`\u672A\u627E\u5230\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\uFF0C\u8BF7\u767B\u5F55\u5F00\u53D1\u8005\u5DE5\u5177\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${TICKET_ENV}`);
805
+ if (!ticket) throw new Error(`\u672A\u627E\u5230\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\uFF0C\u8BF7\u767B\u5F55\u5F00\u53D1\u8005\u5DE5\u5177\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${TICKET_ENV2}`);
670
806
  if (ticket.expiredAt !== void 0 && ticket.expiredAt <= Date.now()) {
671
807
  throw new Error("\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u6253\u5F00\u5F00\u53D1\u8005\u5DE5\u5177\u5237\u65B0\u767B\u5F55\u6001");
672
808
  }
673
809
  return ticket.newticket;
674
810
  }
675
- function realLoginRouter(appid, findTicket = findDeveloperTicket) {
811
+ function realLoginRouter(appid, findTicket = findDeveloperTicket2) {
676
812
  const callDevLogic = async (endpoint, body) => {
677
- const query = new URLSearchParams({ appid, newticket: requireUsableTicket(findTicket()), clientversion: CLIENT_VERSION });
813
+ const query = new URLSearchParams({ appid, newticket: requireUsableTicket(await findTicket()), clientversion: CLIENT_VERSION });
678
814
  const response = await fetch(`${DEV_LOGIC}/${endpoint}?${query}`, {
679
815
  method: "POST",
680
816
  headers: { "Content-Type": "application/json" },
@@ -853,13 +989,7 @@ var DevServer = class {
853
989
  if (!this.baseLib.compatible) {
854
990
  this.logger.warn(`[wxa-runner] \u57FA\u7840\u5E93 ${this.baseLib.version} \u9AD8\u4E8E\u5DF2\u9A8C\u8BC1\u7684\u7248\u672C\u8303\u56F4\uFF0C\u53EF\u80FD\u65E0\u6CD5\u6B63\u5E38\u8FD0\u884C`);
855
991
  }
856
- if (options.realLogin) {
857
- try {
858
- requireUsableTicket(findDeveloperTicket2());
859
- } catch (error) {
860
- this.logger.warn(`[wxa-runner] \u771F\u5B9E\u767B\u5F55\u6682\u4E0D\u53EF\u7528\uFF1A${error.message}`);
861
- }
862
- }
992
+ this.ticketSource = options.realLogin ? developerTicketSource(this.project.projectPath, this.logger) : null;
863
993
  this.device = { ...DEFAULT_DEVICE, ...options.device };
864
994
  this.builder = new AppBuilder(this.project);
865
995
  this.setupFile = resolveSetupFile(options.setup, this.project.projectPath);
@@ -895,6 +1025,7 @@ ${error.message}`);
895
1025
  const { port } = server.address();
896
1026
  this.address = `http://${host}:${port}`;
897
1027
  if (this.options.watch !== false) this.startWatching();
1028
+ this.ticketSource?.().then(requireUsableTicket).catch((error) => this.logger.warn(`[wxa-runner] \u771F\u5B9E\u767B\u5F55\u6682\u4E0D\u53EF\u7528\uFF1A${error.message}`));
898
1029
  return this.address;
899
1030
  }
900
1031
  async close() {
@@ -1005,18 +1136,18 @@ ${error.message}`);
1005
1136
  res.status(400).type("text").send(`\u975E\u6CD5\u7684\u6587\u4EF6\u7C7B\u578B ${type}`);
1006
1137
  return;
1007
1138
  }
1008
- const name = `${randomUUID()}.${type}`;
1139
+ const name = `${randomUUID2()}.${type}`;
1009
1140
  this.tempFiles.set(name, Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0));
1010
1141
  res.json({ url: `${req.protocol}://${req.get("host")}/__wxa__/tmp/${name}` });
1011
1142
  });
1012
1143
  app.get("/__wxa__/region/:kind", (req, res) => {
1013
1144
  const kind = req.params.kind;
1014
- const file = kind === "districts" || kind === "streets" ? findRegionDataFile(kind) : null;
1015
- if (!file) {
1145
+ const data = kind === "districts" || kind === "streets" ? readRegionData(kind) : null;
1146
+ if (!data) {
1016
1147
  res.status(404).end();
1017
1148
  return;
1018
1149
  }
1019
- res.type("text/plain; charset=utf-8").sendFile(file, { maxAge: "1d" });
1150
+ res.type("text/plain; charset=utf-8").set("Cache-Control", "public, max-age=86400").send(data);
1020
1151
  });
1021
1152
  app.get("/__wxa__/tmp/:name", (req, res) => {
1022
1153
  const data = this.tempFiles.get(req.params.name);
@@ -1029,7 +1160,7 @@ ${error.message}`);
1029
1160
  app.delete("/__wxa__/tmp/:name", (req, res) => {
1030
1161
  res.status(this.tempFiles.delete(req.params.name) ? 204 : 404).end();
1031
1162
  });
1032
- if (this.options.realLogin) app.use("/__wxa__/api", realLoginRouter(this.project.appid));
1163
+ if (this.ticketSource) app.use("/__wxa__/api", realLoginRouter(this.project.appid, this.ticketSource));
1033
1164
  app.use("/__wxa__/webview", express2.static(root));
1034
1165
  app.use(express2.static(root));
1035
1166
  app.use((error, _req, res, _next) => {
@@ -1108,7 +1239,7 @@ ${message}`);
1108
1239
  broadcast(message) {
1109
1240
  const data = JSON.stringify(message);
1110
1241
  this.sockets?.clients.forEach((client) => {
1111
- if (client.readyState === WebSocket.OPEN) client.send(data);
1242
+ if (client.readyState === WebSocket2.OPEN) client.send(data);
1112
1243
  });
1113
1244
  }
1114
1245
  };