weapp-ide-cli 6.2.0 → 6.3.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/README.md +59 -3
- package/dist/{automator-CRpB6_pA.js → automator-BOe32upt.js} +388 -142
- package/dist/automator-VSjgHujY.js +1 -0
- package/dist/automator-session-VOB03glx.js +186 -0
- package/dist/{sessionStore-zdzy2UFM.js → automatorProject-DjYLqMj6.js} +495 -219
- package/dist/{cli-ChMQqi5w.js → cli-CFlqFpIw.js} +372 -61
- package/dist/cli.js +2 -2
- package/dist/commands-DmirGBy4.js +1 -0
- package/dist/{commands-C1PF1B4P.js → commands-mslf7-sP.js} +3 -2
- package/dist/{devtoolsProjectOwnership-yDD6h0dO.js → devtoolsProjectOwnership-BMfSAg4n.js} +394 -98
- package/dist/index.d.ts +123 -39
- package/dist/index.js +8 -8
- package/dist/{managed-Sq8y8FzP.js → managed-B04Xo9U_.js} +107 -55
- package/dist/run-mcp-83-VKZVA.js +1 -0
- package/dist/{run-mcp-Dl_1TH1x.js → run-mcp-Ni139Z5C.js} +11 -1
- package/package.json +9 -9
- package/dist/automator-BkZZsGtn.js +0 -1
- package/dist/automator-session-gWEsSs_A.js +0 -558
- package/dist/commands-CZVPfkE9.js +0 -1
- package/dist/run-mcp-CPv8cesh.js +0 -1
package/README.md
CHANGED
|
@@ -259,9 +259,42 @@ console.log(WEAPP_IDE_TOP_LEVEL_COMMAND_NAMES)
|
|
|
259
259
|
|
|
260
260
|
### 安装选择与共享登录
|
|
261
261
|
|
|
262
|
-
|
|
262
|
+
默认选择本机已安装的最新 Stable;官方灰度发布或网络查询失败不会阻塞默认选版。macOS 扫描系统及用户 Applications,Windows 读取安装登记和常见目录,Linux 与自定义位置通过显式 CLI 路径或本地登记接入。渠道和产品版本读取实际应用元数据,按数字段比较版本;符号链接按真实安装去重,同版本存在多份安装时要求 `--cli-path`。
|
|
263
263
|
|
|
264
|
-
|
|
264
|
+
```sh
|
|
265
|
+
weapp devtools list --json
|
|
266
|
+
weapp devtools add "<path-to-cli>"
|
|
267
|
+
weapp devtools remove "<path-to-cli>"
|
|
268
|
+
wv ide devtools list
|
|
269
|
+
wv open --devtools-channel=nightly
|
|
270
|
+
wv dev --open --devtools-channel=rc
|
|
271
|
+
wv ide preview --devtools-channel=rc --devtools-version=2.02.2608080
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
安装由用户完成,登记只保存 CLI 路径,每次重新读取元数据。列表显示渠道、版本、安装身份、实际配置目录及失效原因;登录使用各安装自己的配置目录,不复制 ticket、Cookie 或账号数据。
|
|
275
|
+
|
|
276
|
+
同一微信账号切换安装后,另一安装的登录可能失效。无需准备多个账号,可在需要测试的版本中重新扫码;工具不共享或迁移登录数据。矩阵使用非交互模式,登录失效记录为 `blocked`,附带 `runtimeBlocker.code: "needs-login"`,保留原始错误并继续其他版本;不会等待扫码或自动重试,最终返回非零退出码。重新登录后可单独运行该渠道补测。
|
|
277
|
+
|
|
278
|
+
选择优先级为:已固定的 `target` / 当前操作安装 → 本次 `cliPath`、`devtoolsChannel`、`devtoolsVersion` → 环境变量 → 旧全局 `cliPath` → 本机最新 Stable。显式渠道/版本覆盖继承的路径固定;同层路径、渠道、版本必须一致。`--devtools-version` 是开发者工具产品版本,上传的 `--version` 仍为小程序版本。包装参数不会传给官方 CLI。RC/nightly 需要明确选择,操作的启动、构建、连接、恢复和清理沿用同一安装。
|
|
279
|
+
|
|
280
|
+
公共环境变量为 `WEAPP_IDE_CLI_PATH`、`WEAPP_IDE_DEVTOOLS_CHANNEL`、`WEAPP_IDE_DEVTOOLS_VERSION`;E2E 使用对应的 `WEAPP_VITE_E2E_DEVTOOLS_*`,解析后向全部子进程传入具体 CLI。兼容 `WEAPP_VITE_E2E_ACCEPTED_DEVTOOLS_VERSION` / `WEAPP_VITE_E2E_ACCEPTED_DEVTOOLS_CHANNEL` 的固定选择。显式 `--require-official-latest`(或公共 `WEAPP_IDE_REQUIRE_OFFICIAL_LATEST=1`、E2E `WEAPP_VITE_E2E_REQUIRE_OFFICIAL_LATEST=1`)才要求匹配所选渠道官方最新版本,查询失败也会阻塞此严格模式。
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import { listWechatDevtoolsInstallations, previewWechatIde, resolveWechatDevtoolsTarget } from 'weapp-ide-cli'
|
|
284
|
+
|
|
285
|
+
const installations = await listWechatDevtoolsInstallations()
|
|
286
|
+
const target = await resolveWechatDevtoolsTarget({ devtoolsChannel: 'rc' })
|
|
287
|
+
await previewWechatIde({ projectPath: './dist', target })
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
两套 MCP 的连接工具和相关 REST 输入都支持 `cliPath`、`devtoolsChannel`、`devtoolsVersion`、`requireOfficialLatest`;会话按真实安装身份隔离,同一项目不会串用 Stable 与 nightly 连接。
|
|
291
|
+
|
|
292
|
+
```sh
|
|
293
|
+
pnpm e2e:ide:matrix --targets=stable,rc,nightly --suite=ide-smoke
|
|
294
|
+
pnpm e2e:ide:matrix --targets=stable@2.02.2608080,nightly --suite=ide-gate --filter=<task>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
矩阵默认只测 Stable,复用既有 suite 及其筛选和严格策略,全程持有同机同用户串行租约。普通测试失败或缺失安装记录后继续;所有权异常、清理失败、OOM 或取消停止后续目标。报告保存在 `docs/reports/devtools-matrix`,分别记录请求、实际安装、runtime/基础库版本、官方查询状态和结果。缺失、阻塞、partial 或未执行不能计为完整通过,返回非零退出码;全量正式验收仍遵守主线工作树要求。
|
|
265
298
|
|
|
266
299
|
HTTP 服务端口和 automator 会话都绑定选定安装。另一安装的配置较新、端口可连或版本号相同,均不代表可以复用。未知旧会话不会直接连接;宿主或监听端口归属不一致时停止操作,保留原有 IDE 和账号状态。`port` 仍表示对应接口原有的端口类型,不将 automator WebSocket 端口当作 HTTP 服务端口。
|
|
267
300
|
|
|
@@ -269,7 +302,9 @@ HTTP 服务端口和 automator 会话都绑定选定安装。另一安装的配
|
|
|
269
302
|
|
|
270
303
|
`openWechatIdeProjectByHttp()` 使用 `/v2/open` 的 `project` 参数并保留原始响应正文;空正文或空 JSON 对象会抛出 `WECHAT_DEVTOOLS_HTTP_OPEN_UNCONFIRMED`,不能把 HTTP 200 当作项目已打开的证据。目标项目及 runtime 是否就绪仍需通过会话或 IDE 界面确认。
|
|
271
304
|
|
|
272
|
-
本仓库真实 IDE E2E
|
|
305
|
+
本仓库真实 IDE E2E 自动解析并固定本轮安装,核对实际宿主及 runtime 版本;不会自动安装工具。不同 worktree 的 E2E 使用同机同用户的排他租约,冲突报告 `Runtime busy`,应等待持有者结束。普通 CLI 的宿主变更同样参与协调,已建立的多项目连接不长期占用此租约。自动恢复不清共享缓存、不退出账号;只释放本任务登记的资源。
|
|
306
|
+
|
|
307
|
+
macOS 安装退出核验直接使用系统报告的映像路径,避免无关进程的受保护或已删除文件阻塞清理。当前磁盘路径被替换也不会覆盖原映像的归属判断;PID、启动时间、端口和任务登记仍须通过校验。
|
|
273
308
|
|
|
274
309
|
### 6. 程序化 opened-session helper
|
|
275
310
|
|
|
@@ -318,6 +353,27 @@ await clearWechatIdeCacheByAutomator({
|
|
|
318
353
|
console.log(info, ticket)
|
|
319
354
|
```
|
|
320
355
|
|
|
356
|
+
### 7. 程序化 automator 与异步断开
|
|
357
|
+
|
|
358
|
+
`connectOpenedAutomator()` 返回的会话提供公开 `send(method, params?, { timeout }?)` 与 `CDP().enable/sendCommand/listProtocol/onEvent`,用于直接调用已连接 DevTools 支持的自动化和调试协议。`onEvent` 返回取消订阅函数;可用域和命令以目标宿主 `listProtocol()` 的结果为准。
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
import { connectOpenedAutomator } from 'weapp-ide-cli'
|
|
362
|
+
|
|
363
|
+
const miniProgram = await connectOpenedAutomator({ projectPath: '.' })
|
|
364
|
+
try {
|
|
365
|
+
console.log(await miniProgram.send('Tool.getInfo', {}, { timeout: 3_000 }))
|
|
366
|
+
console.log(await miniProgram.CDP().listProtocol({ timeout: 3_000 }))
|
|
367
|
+
}
|
|
368
|
+
finally {
|
|
369
|
+
await miniProgram.disconnect()
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`disconnect()` 现在返回 `Promise<void>`,调用时立即停止订阅并拒绝未完成请求,等待 WebSocket 关闭后完成。迁移同步清理回调时应返回或 `await` 此 Promise;超时和清理失败继续抛出。它只释放本连接,保留已打开项目窗口;需要关闭本任务受管窗口时使用相应会话的 `close()`,并等待窗口 owner 完成确认。MCP、日志转发及命令结束清理均等待各自持有的连接,不重新启动 IDE。
|
|
374
|
+
|
|
375
|
+
headless 会话的 `send` 仅适配 `Tool.getInfo`、`App.getCurrentPage/getPageStack/callFunction/callWxMethod`;`CDP()` 和其他原始协议明确报告 `HEADLESS_PROTOCOL_UNSUPPORTED`。触摸接口使用标准 `changedTouches`,旧 `changeTouches` 仍兼容,同时传入以标准字段为准。
|
|
376
|
+
|
|
321
377
|
## 路径与参数兼容
|
|
322
378
|
|
|
323
379
|
- `-p` 会被自动替换为 `--project`,并且相对路径会解析为绝对路径。
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as readPersistedAutomatorSession, i as persistAutomatorSession, j as colors, m as assertWechatDevtoolsPort, p as assertWechatDevtoolsHost, r as resolveAutomatorProjectPath, u as resolveWechatDevtoolsTarget, z as readCustomConfig } from "./automatorProject-DjYLqMj6.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { withMachineE2ELease } from "@weapp-vite/devtools-runtime";
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
4
|
import fs from "node:fs/promises";
|
|
6
5
|
import process from "node:process";
|
|
7
|
-
import
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
8
7
|
import { OperationLifecycle } from "@weapp-vite/miniprogram-automator/operation";
|
|
8
|
+
import { emitKeypressEvents } from "node:readline";
|
|
9
9
|
//#region src/cli/automator/port.ts
|
|
10
10
|
/** 显式端口不能被截断或作为默认值忽略,避免连接其他项目。 */
|
|
11
11
|
function assertAutomatorPort(port) {
|
|
@@ -40,6 +40,377 @@ async function resolveAutomatorSessionOptions(options) {
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
//#endregion
|
|
43
|
+
//#region src/i18n.ts
|
|
44
|
+
let currentLocale = "zh";
|
|
45
|
+
/**
|
|
46
|
+
* @description 设置当前语言。
|
|
47
|
+
*/
|
|
48
|
+
function setLocale(locale) {
|
|
49
|
+
currentLocale = normalizeLocale(locale);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* @description 从 argv 和环境变量中解析语言并设置。
|
|
53
|
+
*/
|
|
54
|
+
function configureLocaleFromArgv(argv, fallbackLocale) {
|
|
55
|
+
const localeFromArgv = readLangOption(argv);
|
|
56
|
+
if (localeFromArgv) {
|
|
57
|
+
setLocale(localeFromArgv);
|
|
58
|
+
return currentLocale;
|
|
59
|
+
}
|
|
60
|
+
if (fallbackLocale) {
|
|
61
|
+
setLocale(fallbackLocale);
|
|
62
|
+
return currentLocale;
|
|
63
|
+
}
|
|
64
|
+
const localeFromEnv = process.env.WEAPP_IDE_CLI_LANG || process.env.LANG;
|
|
65
|
+
if (localeFromEnv) {
|
|
66
|
+
setLocale(localeFromEnv);
|
|
67
|
+
return currentLocale;
|
|
68
|
+
}
|
|
69
|
+
setLocale("zh");
|
|
70
|
+
return currentLocale;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* @description 根据当前语言选择文案。
|
|
74
|
+
*/
|
|
75
|
+
function i18nText(zh, en) {
|
|
76
|
+
return currentLocale === "en" ? en : zh;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* @description 校验 --lang 参数是否合法。
|
|
80
|
+
*/
|
|
81
|
+
function validateLocaleOption(argv) {
|
|
82
|
+
const localeFromArgv = readLangOption(argv);
|
|
83
|
+
if (!localeFromArgv) return;
|
|
84
|
+
if (!isLocaleTokenSupported(localeFromArgv)) throw new Error(`不支持的语言: ${localeFromArgv},仅支持 zh 或 en`);
|
|
85
|
+
}
|
|
86
|
+
function normalizeLocale(raw) {
|
|
87
|
+
if (!raw) return "zh";
|
|
88
|
+
const normalized = raw.trim().toLowerCase();
|
|
89
|
+
if (normalized === "en" || normalized.startsWith("en_") || normalized.startsWith("en-")) return "en";
|
|
90
|
+
return "zh";
|
|
91
|
+
}
|
|
92
|
+
function isLocaleTokenSupported(raw) {
|
|
93
|
+
const normalized = raw.trim().toLowerCase();
|
|
94
|
+
return normalized === "zh" || normalized === "en" || normalized.startsWith("zh_") || normalized.startsWith("zh-") || normalized.startsWith("en_") || normalized.startsWith("en-");
|
|
95
|
+
}
|
|
96
|
+
function readLangOption(argv) {
|
|
97
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
98
|
+
const token = argv[index];
|
|
99
|
+
if (!token) continue;
|
|
100
|
+
if (token === "--lang") {
|
|
101
|
+
const value = argv[index + 1];
|
|
102
|
+
return typeof value === "string" ? value : void 0;
|
|
103
|
+
}
|
|
104
|
+
if (token.startsWith("--lang=")) return token.slice(7);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/cli/inputCoordinator.ts
|
|
109
|
+
const sharedSessions = /* @__PURE__ */ new Set();
|
|
110
|
+
const exclusiveKeypressStack = [];
|
|
111
|
+
const EXCLUSIVE_KEYPRESS_CARRYOVER_GUARD_MS = 500;
|
|
112
|
+
let initialized = false;
|
|
113
|
+
let rawModeEnabled = false;
|
|
114
|
+
let dataListenerAttached = false;
|
|
115
|
+
let keypressListenerAttached = false;
|
|
116
|
+
let lastExclusiveResolvedKeypress;
|
|
117
|
+
function hasInteractiveStdin() {
|
|
118
|
+
return Boolean(process.stdin?.isTTY);
|
|
119
|
+
}
|
|
120
|
+
function canSetRawMode() {
|
|
121
|
+
return typeof process.stdin?.setRawMode === "function";
|
|
122
|
+
}
|
|
123
|
+
function updateTerminalState() {
|
|
124
|
+
const shouldKeepInputActive = Array.from(sharedSessions).some((session) => !session.closed && !session.suspended) || exclusiveKeypressStack.length > 0;
|
|
125
|
+
if (canSetRawMode() && rawModeEnabled !== shouldKeepInputActive) {
|
|
126
|
+
process.stdin.setRawMode(shouldKeepInputActive);
|
|
127
|
+
rawModeEnabled = shouldKeepInputActive;
|
|
128
|
+
}
|
|
129
|
+
if (shouldKeepInputActive) {
|
|
130
|
+
process.stdin.resume();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
process.stdin.pause();
|
|
134
|
+
}
|
|
135
|
+
function handleData(chunk) {
|
|
136
|
+
if (exclusiveKeypressStack.length > 0) return;
|
|
137
|
+
for (const session of sharedSessions) {
|
|
138
|
+
if (session.closed || session.suspended || !session.options.onData) continue;
|
|
139
|
+
session.options.onData(chunk);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function handleKeypress(str, key) {
|
|
143
|
+
const activeExclusive = exclusiveKeypressStack[exclusiveKeypressStack.length - 1];
|
|
144
|
+
if (activeExclusive) {
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
const signature = `${key?.ctrl ? "ctrl+" : ""}${key?.name ?? str}`;
|
|
147
|
+
if (lastExclusiveResolvedKeypress && lastExclusiveResolvedKeypress.signature === signature && now - lastExclusiveResolvedKeypress.timestamp <= EXCLUSIVE_KEYPRESS_CARRYOVER_GUARD_MS) {
|
|
148
|
+
lastExclusiveResolvedKeypress.timestamp = now;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (now < activeExclusive.ignoreUntil) return;
|
|
152
|
+
const nextValue = activeExclusive.onKeypress(str, key);
|
|
153
|
+
if (nextValue !== void 0) {
|
|
154
|
+
clearTimeout(activeExclusive.timeout);
|
|
155
|
+
exclusiveKeypressStack.pop();
|
|
156
|
+
lastExclusiveResolvedKeypress = {
|
|
157
|
+
signature,
|
|
158
|
+
timestamp: now
|
|
159
|
+
};
|
|
160
|
+
updateTerminalState();
|
|
161
|
+
activeExclusive.resolve(nextValue);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
for (const session of sharedSessions) {
|
|
166
|
+
if (session.closed || session.suspended || !session.options.onKeypress) continue;
|
|
167
|
+
session.options.onKeypress(str, key);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function ensureCoordinatorInitialized() {
|
|
171
|
+
if (initialized || !hasInteractiveStdin()) return;
|
|
172
|
+
emitKeypressEvents(process.stdin);
|
|
173
|
+
if (!dataListenerAttached) {
|
|
174
|
+
process.stdin.on("data", handleData);
|
|
175
|
+
dataListenerAttached = true;
|
|
176
|
+
}
|
|
177
|
+
if (!keypressListenerAttached) {
|
|
178
|
+
process.stdin.on("keypress", handleKeypress);
|
|
179
|
+
keypressListenerAttached = true;
|
|
180
|
+
}
|
|
181
|
+
initialized = true;
|
|
182
|
+
}
|
|
183
|
+
function createSharedInputSession(options) {
|
|
184
|
+
if (!hasInteractiveStdin()) return;
|
|
185
|
+
ensureCoordinatorInitialized();
|
|
186
|
+
const session = {
|
|
187
|
+
closed: false,
|
|
188
|
+
options,
|
|
189
|
+
suspended: false
|
|
190
|
+
};
|
|
191
|
+
sharedSessions.add(session);
|
|
192
|
+
updateTerminalState();
|
|
193
|
+
return {
|
|
194
|
+
close() {
|
|
195
|
+
if (session.closed) return;
|
|
196
|
+
session.closed = true;
|
|
197
|
+
sharedSessions.delete(session);
|
|
198
|
+
updateTerminalState();
|
|
199
|
+
},
|
|
200
|
+
resume() {
|
|
201
|
+
if (session.closed) return;
|
|
202
|
+
session.suspended = false;
|
|
203
|
+
updateTerminalState();
|
|
204
|
+
},
|
|
205
|
+
suspend() {
|
|
206
|
+
if (session.closed) return;
|
|
207
|
+
session.suspended = true;
|
|
208
|
+
updateTerminalState();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
async function waitForExclusiveKeypress(options) {
|
|
213
|
+
options.signal?.throwIfAborted();
|
|
214
|
+
if (!hasInteractiveStdin()) return "timeout";
|
|
215
|
+
ensureCoordinatorInitialized();
|
|
216
|
+
return await new Promise((resolve, reject) => {
|
|
217
|
+
const normalizedTimeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs && options.timeoutMs > 0 ? options.timeoutMs : 3e4;
|
|
218
|
+
const normalizedIgnoreInitialMs = Number.isFinite(options.ignoreInitialMs) && options.ignoreInitialMs && options.ignoreInitialMs > 0 ? options.ignoreInitialMs : 0;
|
|
219
|
+
let onAbort;
|
|
220
|
+
const record = {
|
|
221
|
+
ignoreUntil: Date.now() + normalizedIgnoreInitialMs,
|
|
222
|
+
onKeypress: options.onKeypress,
|
|
223
|
+
resolve: (value) => {
|
|
224
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
225
|
+
resolve(value);
|
|
226
|
+
},
|
|
227
|
+
timeout: setTimeout(() => {
|
|
228
|
+
const index = exclusiveKeypressStack.lastIndexOf(record);
|
|
229
|
+
if (index >= 0) exclusiveKeypressStack.splice(index, 1);
|
|
230
|
+
updateTerminalState();
|
|
231
|
+
record.resolve("timeout");
|
|
232
|
+
}, normalizedTimeoutMs)
|
|
233
|
+
};
|
|
234
|
+
onAbort = () => {
|
|
235
|
+
clearTimeout(record.timeout);
|
|
236
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
237
|
+
const index = exclusiveKeypressStack.indexOf(record);
|
|
238
|
+
if (index >= 0) exclusiveKeypressStack.splice(index, 1);
|
|
239
|
+
updateTerminalState();
|
|
240
|
+
reject(options.signal?.reason);
|
|
241
|
+
};
|
|
242
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
243
|
+
exclusiveKeypressStack.push(record);
|
|
244
|
+
updateTerminalState();
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
async function runWithSuspendedSharedInput(runner) {
|
|
248
|
+
const previousStates = /* @__PURE__ */ new Map();
|
|
249
|
+
for (const session of sharedSessions) {
|
|
250
|
+
if (session.closed) continue;
|
|
251
|
+
previousStates.set(session, session.suspended);
|
|
252
|
+
session.suspended = true;
|
|
253
|
+
}
|
|
254
|
+
if (canSetRawMode() && rawModeEnabled) {
|
|
255
|
+
process.stdin.setRawMode(false);
|
|
256
|
+
rawModeEnabled = false;
|
|
257
|
+
}
|
|
258
|
+
process.stdin.resume();
|
|
259
|
+
try {
|
|
260
|
+
return await runner();
|
|
261
|
+
} finally {
|
|
262
|
+
for (const [session, suspended] of previousStates) if (!session.closed) session.suspended = suspended;
|
|
263
|
+
updateTerminalState();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/cli/retry.ts
|
|
268
|
+
const RETRY_PROMPT_INITIAL_IGNORE_MS = 300;
|
|
269
|
+
const RETRY_CONFIRM_KEYS = ["y"];
|
|
270
|
+
const RETRY_CANCEL_KEYS = [
|
|
271
|
+
"q",
|
|
272
|
+
"Esc",
|
|
273
|
+
"Ctrl+C"
|
|
274
|
+
];
|
|
275
|
+
const LOGIN_REQUIRED_PATTERNS = [
|
|
276
|
+
/code(?:\s*[:=]\s*|\s+)13\b/i,
|
|
277
|
+
/需要重新登录/,
|
|
278
|
+
/need\s+re-?login/i,
|
|
279
|
+
/re-?login/i,
|
|
280
|
+
/\blogin(?:\s+again)?\s+(?:is\s+)?required\b/i
|
|
281
|
+
];
|
|
282
|
+
/**
|
|
283
|
+
* @description 提取执行错误文本,便于统一匹配与提示。
|
|
284
|
+
*/
|
|
285
|
+
function extractExecutionErrorText(error) {
|
|
286
|
+
if (!error || typeof error !== "object") return "";
|
|
287
|
+
const parts = [];
|
|
288
|
+
const candidate = error;
|
|
289
|
+
for (const field of [
|
|
290
|
+
candidate.message,
|
|
291
|
+
candidate.shortMessage,
|
|
292
|
+
candidate.stderr,
|
|
293
|
+
candidate.stdout
|
|
294
|
+
]) if (typeof field === "string" && field.trim()) parts.push(field);
|
|
295
|
+
return parts.join("\n");
|
|
296
|
+
}
|
|
297
|
+
function extractLoginRequiredMessage$1(text) {
|
|
298
|
+
if (!text) return "";
|
|
299
|
+
if (/需要重新登录/.test(text)) return "需要重新登录";
|
|
300
|
+
const englishMatch = text.match(/need\s+re-?login|re-?login/i);
|
|
301
|
+
if (englishMatch?.[0]) return englishMatch[0].toLowerCase();
|
|
302
|
+
const firstLine = text.split(/\r?\n/).map((line) => line.trim()).find((line) => Boolean(line) && !line.startsWith("at "));
|
|
303
|
+
if (!firstLine) return "";
|
|
304
|
+
return firstLine.replace(/^\[error\]\s*/i, "").replace(/^error\s*:\s*/i, "").slice(0, 120);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* @description 判断是否为微信开发者工具登录失效错误。
|
|
308
|
+
*/
|
|
309
|
+
function isWechatIdeLoginRequiredError(error) {
|
|
310
|
+
const text = extractExecutionErrorText(error);
|
|
311
|
+
if (!text) return false;
|
|
312
|
+
return LOGIN_REQUIRED_PATTERNS.some((pattern) => pattern.test(text));
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* @description 将登录失效错误格式化为更易读的摘要。
|
|
316
|
+
*/
|
|
317
|
+
function formatWechatIdeLoginRequiredError(error) {
|
|
318
|
+
const text = extractExecutionErrorText(error);
|
|
319
|
+
const code = text.match(/code\s*[:=]\s*(\d+)/i)?.[1];
|
|
320
|
+
const message = extractLoginRequiredMessage$1(text);
|
|
321
|
+
const lines = ["微信开发者工具返回登录错误:"];
|
|
322
|
+
if (code) lines.push(`- code: ${code}`);
|
|
323
|
+
if (message) lines.push(`- message: ${message}`);
|
|
324
|
+
if (!code && !message) lines.push("- message: 需要重新登录");
|
|
325
|
+
return lines.join("\n");
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* @description 创建登录失效专用错误,并携带退出码语义。
|
|
329
|
+
*/
|
|
330
|
+
function createWechatIdeLoginRequiredExitError(error, reason) {
|
|
331
|
+
const summary = formatWechatIdeLoginRequiredError(error);
|
|
332
|
+
const message = reason ? `${reason}\n${summary}` : summary;
|
|
333
|
+
const loginError = new Error(message, { cause: error });
|
|
334
|
+
loginError.name = "WechatIdeLoginRequiredError";
|
|
335
|
+
loginError.code = 10;
|
|
336
|
+
loginError.exitCode = 10;
|
|
337
|
+
return loginError;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* @description 判断错误是否为登录失效专用退出错误。
|
|
341
|
+
*/
|
|
342
|
+
function isWechatIdeLoginRequiredExitError(error) {
|
|
343
|
+
if (!error || typeof error !== "object") return false;
|
|
344
|
+
const candidate = error;
|
|
345
|
+
return candidate.name === "WechatIdeLoginRequiredError" || (candidate.code === 10 || candidate.exitCode === 10) && isWechatIdeLoginRequiredError(error);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* @description 交互等待用户按 y 重试,按 q 或 Ctrl+C 取消。
|
|
349
|
+
*/
|
|
350
|
+
async function waitForRetryKeypress(options = {}) {
|
|
351
|
+
const { timeoutMs = 3e4 } = options;
|
|
352
|
+
if (!process.stdin.isTTY) return "cancel";
|
|
353
|
+
return await waitForExclusiveKeypress({
|
|
354
|
+
signal: options.signal,
|
|
355
|
+
ignoreInitialMs: 300,
|
|
356
|
+
onKeypress: (_str, key) => {
|
|
357
|
+
if (!key) return;
|
|
358
|
+
if (key.ctrl && key.name === "c") return "cancel";
|
|
359
|
+
if (key.name === "y") return "retry";
|
|
360
|
+
if (key.name === "q" || key.name === "escape") return "cancel";
|
|
361
|
+
},
|
|
362
|
+
timeoutMs
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
function highlightHotkey(key) {
|
|
366
|
+
return colors.bold(colors.green(key));
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* @description 生成重试按键提示,并高亮关键热键。
|
|
370
|
+
*/
|
|
371
|
+
function formatRetryHotkeyPrompt(timeoutMs = 3e4) {
|
|
372
|
+
const highlight = (key) => highlightHotkey(key);
|
|
373
|
+
const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1e3));
|
|
374
|
+
const confirmKeys = RETRY_CONFIRM_KEYS.map(highlight).join(" / ");
|
|
375
|
+
const cancelKeys = RETRY_CANCEL_KEYS.map(highlight).join(" / ");
|
|
376
|
+
return i18nText(`按 ${confirmKeys} 重试,按 ${cancelKeys} 退出(${timeoutSeconds}s 内无输入将自动失败)。`, `Press ${confirmKeys} to retry, ${cancelKeys} to cancel (auto fail in ${timeoutSeconds}s).`);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* @description 输出重试热键提示,并独占等待当前提示对应的按键输入。
|
|
380
|
+
*/
|
|
381
|
+
async function promptRetryKeypress(options) {
|
|
382
|
+
const { logger, timeoutMs = 3e4 } = options;
|
|
383
|
+
logger.info(formatRetryHotkeyPrompt(timeoutMs));
|
|
384
|
+
return await waitForRetryKeypress({
|
|
385
|
+
timeoutMs,
|
|
386
|
+
signal: options.signal
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* @description 统一处理微信开发者工具登录失效场景下的提示与重试交互。
|
|
391
|
+
*/
|
|
392
|
+
async function promptWechatIdeLoginRetry(options) {
|
|
393
|
+
const { allowRetry = true, cancelLevel = "info", error, logger, promptOpenIdeLogin = false, retryTimeoutMs = 3e4 } = options;
|
|
394
|
+
logger.error(i18nText("检测到微信开发者工具登录状态失效,请先登录后重试。", "Wechat DevTools login has expired. Please login and retry."));
|
|
395
|
+
if (promptOpenIdeLogin) logger.warn(i18nText("请先打开微信开发者工具完成登录。", "Please open Wechat DevTools and complete login first."));
|
|
396
|
+
logger.warn(formatWechatIdeLoginRequiredError(error));
|
|
397
|
+
if (!allowRetry) return "cancel";
|
|
398
|
+
const action = await promptRetryKeypress({
|
|
399
|
+
logger,
|
|
400
|
+
timeoutMs: retryTimeoutMs,
|
|
401
|
+
signal: options.signal
|
|
402
|
+
});
|
|
403
|
+
if (action === "timeout") {
|
|
404
|
+
logger.error(i18nText(`等待登录重试输入超时(${retryTimeoutMs}ms),已自动取消。`, `Retry prompt timed out (${retryTimeoutMs}ms), canceled automatically.`));
|
|
405
|
+
return "cancel";
|
|
406
|
+
}
|
|
407
|
+
if (action !== "retry") {
|
|
408
|
+
logger[cancelLevel](i18nText("已取消重试。完成登录后请重新执行当前命令。", "Retry canceled. Please run the command again after login."));
|
|
409
|
+
return "cancel";
|
|
410
|
+
}
|
|
411
|
+
return "retry";
|
|
412
|
+
}
|
|
413
|
+
//#endregion
|
|
43
414
|
//#region src/cli/automator/errors.ts
|
|
44
415
|
const ERROR_STACK_PREFIX_RE = /^at /;
|
|
45
416
|
const ERROR_PREFIX_RE = /^\[error\]\s*/i;
|
|
@@ -63,12 +434,6 @@ const DEVTOOLS_INFRA_ERROR_PATTERNS = [
|
|
|
63
434
|
/ECONNREFUSED/i,
|
|
64
435
|
/connect ECONNREFUSED/i
|
|
65
436
|
];
|
|
66
|
-
const DEVTOOLS_LOGIN_REQUIRED_PATTERNS = [
|
|
67
|
-
/code\s*[:=]\s*10/i,
|
|
68
|
-
/需要重新登录/,
|
|
69
|
-
/need\s+re-?login/i,
|
|
70
|
-
/re-?login/i
|
|
71
|
-
];
|
|
72
437
|
/**
|
|
73
438
|
* @description 从错误对象中提取可读文本。
|
|
74
439
|
*/
|
|
@@ -146,9 +511,7 @@ function getAutomatorProtocolTimeoutMethod(error) {
|
|
|
146
511
|
* @description 判断错误是否属于开发者工具登录失效。
|
|
147
512
|
*/
|
|
148
513
|
function isAutomatorLoginError(error) {
|
|
149
|
-
|
|
150
|
-
if (!text) return false;
|
|
151
|
-
return DEVTOOLS_LOGIN_REQUIRED_PATTERNS.some((pattern) => pattern.test(text));
|
|
514
|
+
return isWechatIdeLoginRequiredError(error);
|
|
152
515
|
}
|
|
153
516
|
/**
|
|
154
517
|
* @description 格式化开发者工具登录失效错误,便于终端展示。
|
|
@@ -164,124 +527,6 @@ function formatAutomatorLoginError(error) {
|
|
|
164
527
|
return lines.join("\n");
|
|
165
528
|
}
|
|
166
529
|
//#endregion
|
|
167
|
-
//#region src/cli/automatorProject.ts
|
|
168
|
-
const WRAPPER_ROOT = path.join(os.tmpdir(), "weapp-ide-cli-automator-projects");
|
|
169
|
-
function normalizeProjectRelativeRoot(rawRoot) {
|
|
170
|
-
if (typeof rawRoot !== "string") return;
|
|
171
|
-
const normalized = rawRoot.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
172
|
-
if (!normalized || normalized === ".") return;
|
|
173
|
-
if (normalized.split("/").includes("..")) return;
|
|
174
|
-
return normalized;
|
|
175
|
-
}
|
|
176
|
-
async function pathExists(filePath) {
|
|
177
|
-
try {
|
|
178
|
-
await fs.access(filePath);
|
|
179
|
-
return true;
|
|
180
|
-
} catch {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
async function readJsonObject$1(filePath) {
|
|
185
|
-
try {
|
|
186
|
-
const raw = await fs.readFile(filePath, "utf8");
|
|
187
|
-
const value = JSON.parse(raw);
|
|
188
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
189
|
-
} catch {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
async function writeJsonObject$1(filePath, value) {
|
|
194
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
195
|
-
await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
196
|
-
}
|
|
197
|
-
async function copyProjectRoot(sourceProjectPath, wrapperProjectPath, relativeRoot) {
|
|
198
|
-
const sourcePath = path.join(sourceProjectPath, relativeRoot);
|
|
199
|
-
if (!await pathExists(sourcePath)) return;
|
|
200
|
-
await fs.cp(sourcePath, path.join(wrapperProjectPath, relativeRoot), {
|
|
201
|
-
dereference: true,
|
|
202
|
-
force: true,
|
|
203
|
-
recursive: true
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
function createWrapperProjectConfig(source, patch) {
|
|
207
|
-
const { miniprogramRoot: _miniprogramRoot, qcloudRoot: _qcloudRoot, srcMiniprogramRoot: _srcMiniprogramRoot, ...rest } = source;
|
|
208
|
-
const sourceSetting = source.setting && typeof source.setting === "object" ? source.setting : {};
|
|
209
|
-
const patchSetting = patch.setting && typeof patch.setting === "object" ? patch.setting : {};
|
|
210
|
-
return {
|
|
211
|
-
...rest,
|
|
212
|
-
...patch,
|
|
213
|
-
setting: {
|
|
214
|
-
...sourceSetting,
|
|
215
|
-
...patchSetting,
|
|
216
|
-
packNpmManually: false,
|
|
217
|
-
packNpmRelationList: []
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
async function ensureWrapperAppConfig(wrapperProjectPath) {
|
|
222
|
-
const appConfigPath = path.join(wrapperProjectPath, "app.json");
|
|
223
|
-
const appConfig = await readJsonObject$1(appConfigPath);
|
|
224
|
-
if (!appConfig) return;
|
|
225
|
-
await writeJsonObject$1(appConfigPath, {
|
|
226
|
-
...appConfig,
|
|
227
|
-
subPackages: Array.isArray(appConfig.subPackages) ? appConfig.subPackages : []
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* @description 为 DevTools automator 准备稳定的小程序根目录,避免打开瞬间跨 miniprogramRoot 读取 app.json 抖动。
|
|
232
|
-
*/
|
|
233
|
-
async function resolveAutomatorProjectPath(projectPath) {
|
|
234
|
-
const sourceProjectPath = path.resolve(projectPath);
|
|
235
|
-
const projectConfig = await readJsonObject$1(path.join(sourceProjectPath, "project.config.json"));
|
|
236
|
-
const miniprogramRoot = normalizeProjectRelativeRoot(projectConfig?.miniprogramRoot);
|
|
237
|
-
if (!miniprogramRoot) return {
|
|
238
|
-
projectPath: sourceProjectPath,
|
|
239
|
-
sourceProjectPath
|
|
240
|
-
};
|
|
241
|
-
const sourceProjectConfig = projectConfig ?? {};
|
|
242
|
-
const distRoot = path.join(sourceProjectPath, miniprogramRoot);
|
|
243
|
-
if (!await pathExists(path.join(distRoot, "app.json"))) return {
|
|
244
|
-
projectPath: sourceProjectPath,
|
|
245
|
-
sourceProjectPath
|
|
246
|
-
};
|
|
247
|
-
const wrapperHash = createHash("sha1").update(sourceProjectPath).update("\0").update(path.resolve(distRoot)).digest("hex").slice(0, 16);
|
|
248
|
-
const wrapperProjectPath = path.join(WRAPPER_ROOT, wrapperHash);
|
|
249
|
-
await fs.rm(wrapperProjectPath, {
|
|
250
|
-
recursive: true,
|
|
251
|
-
force: true
|
|
252
|
-
});
|
|
253
|
-
await fs.mkdir(wrapperProjectPath, { recursive: true });
|
|
254
|
-
await fs.cp(distRoot, wrapperProjectPath, {
|
|
255
|
-
dereference: true,
|
|
256
|
-
force: true,
|
|
257
|
-
recursive: true
|
|
258
|
-
});
|
|
259
|
-
const pluginRoot = normalizeProjectRelativeRoot(sourceProjectConfig.pluginRoot);
|
|
260
|
-
if (pluginRoot) await copyProjectRoot(sourceProjectPath, wrapperProjectPath, pluginRoot);
|
|
261
|
-
const rootPatch = {
|
|
262
|
-
compileType: sourceProjectConfig.compileType === "plugin" ? "plugin" : "miniprogram",
|
|
263
|
-
miniprogramRoot: "./",
|
|
264
|
-
srcMiniprogramRoot: "./"
|
|
265
|
-
};
|
|
266
|
-
const projectPrivateConfig = await readJsonObject$1(path.join(sourceProjectPath, "project.private.config.json")) ?? {};
|
|
267
|
-
await writeJsonObject$1(path.join(wrapperProjectPath, "project.config.json"), createWrapperProjectConfig(sourceProjectConfig, {
|
|
268
|
-
...projectPrivateConfig,
|
|
269
|
-
...rootPatch
|
|
270
|
-
}));
|
|
271
|
-
await fs.rm(path.join(wrapperProjectPath, "project.private.config.json"), { force: true });
|
|
272
|
-
await ensureWrapperAppConfig(wrapperProjectPath);
|
|
273
|
-
return {
|
|
274
|
-
cleanup: async () => {
|
|
275
|
-
await fs.rm(wrapperProjectPath, {
|
|
276
|
-
recursive: true,
|
|
277
|
-
force: true
|
|
278
|
-
});
|
|
279
|
-
},
|
|
280
|
-
projectPath: wrapperProjectPath,
|
|
281
|
-
sourceProjectPath
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
//#endregion
|
|
285
530
|
//#region src/cli/wechatDevtoolsSettings.ts
|
|
286
531
|
function createStorageHash(key) {
|
|
287
532
|
return createHash("md5").update(key).digest("hex");
|
|
@@ -420,25 +665,26 @@ async function launchSelectedAutomator(options) {
|
|
|
420
665
|
const launcher = new Launcher();
|
|
421
666
|
let lastError = null;
|
|
422
667
|
let bootstrapResult;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
sourceProjectPath: path.resolve(projectPath)
|
|
426
|
-
} : await scope.step(() => resolveAutomatorProjectPath(projectPath), { stage: "project" });
|
|
427
|
-
if (process.env.WEAPP_IDE_MANAGED_PROJECT_JOURNAL) {
|
|
428
|
-
const { launchManagedAutomator } = await import("./managed-Sq8y8FzP.js");
|
|
668
|
+
if (process.env["WEAPP_IDE_MANAGED_PROJECT_JOURNAL"]?.trim()) {
|
|
669
|
+
const { launchManagedAutomator } = await import("./managed-B04Xo9U_.js");
|
|
429
670
|
managedLaunch = launchManagedAutomator({
|
|
430
671
|
launcher,
|
|
431
672
|
scope,
|
|
432
673
|
target,
|
|
433
|
-
projectPath:
|
|
674
|
+
projectPath: path.resolve(projectPath),
|
|
434
675
|
sourceProjectPath: projectPath,
|
|
435
|
-
port: port ?? resolveProjectAutomatorPort(
|
|
676
|
+
port: port ?? resolveProjectAutomatorPort(projectPath),
|
|
436
677
|
sessionId,
|
|
437
678
|
trustProject: resolvedTrustProject,
|
|
438
|
-
persistAsDefaultSession: options.persistAsDefaultSession
|
|
679
|
+
persistAsDefaultSession: options.persistAsDefaultSession,
|
|
680
|
+
preserveProjectRoot: options.preserveProjectRoot
|
|
439
681
|
});
|
|
440
682
|
return await managedLaunch;
|
|
441
683
|
}
|
|
684
|
+
const resolvedProject = options.preserveProjectRoot ? {
|
|
685
|
+
projectPath: path.resolve(projectPath),
|
|
686
|
+
sourceProjectPath: path.resolve(projectPath)
|
|
687
|
+
} : await scope.step(() => resolveAutomatorProjectPath(projectPath), { stage: "project" });
|
|
442
688
|
if (config.autoBootstrapDevtools !== false) bootstrapResult = await scope.step(() => bootstrapWechatDevtoolsSettings({
|
|
443
689
|
target,
|
|
444
690
|
projectPath: resolvedProject.projectPath,
|
|
@@ -563,14 +809,14 @@ async function connectOpenedAutomator(options) {
|
|
|
563
809
|
}), {
|
|
564
810
|
stage: "connect",
|
|
565
811
|
disposeLate: (program) => {
|
|
566
|
-
if (program && typeof program === "object" && "disconnect" in program && typeof program.disconnect === "function") program.disconnect();
|
|
812
|
+
if (program && typeof program === "object" && "disconnect" in program && typeof program.disconnect === "function") return program.disconnect();
|
|
567
813
|
}
|
|
568
814
|
});
|
|
569
815
|
if (process.env.WEAPP_IDE_MANAGED_PROJECT_JOURNAL) miniProgram.close = async () => {
|
|
570
|
-
miniProgram.disconnect();
|
|
816
|
+
await miniProgram.disconnect();
|
|
571
817
|
};
|
|
572
818
|
return miniProgram;
|
|
573
819
|
});
|
|
574
820
|
}
|
|
575
821
|
//#endregion
|
|
576
|
-
export { parseAutomatorPort as _, bootstrapWechatDevtoolsSettings as a, getAutomatorProtocolTimeoutMethod as c, isAutomatorProtocolTimeoutError as d, isAutomatorWsConnectError as f,
|
|
822
|
+
export { waitForExclusiveKeypress as A, isWechatIdeLoginRequiredError as C, waitForRetryKeypress as D, promptWechatIdeLoginRetry as E, parseAutomatorPort as F, i18nText as M, validateLocaleOption as N, createSharedInputSession as O, resolveAutomatorSessionOptions as P, formatWechatIdeLoginRequiredError as S, promptRetryKeypress as T, RETRY_CONFIRM_KEYS as _, bootstrapWechatDevtoolsSettings as a, extractExecutionErrorText as b, getAutomatorProtocolTimeoutMethod as c, isAutomatorProtocolTimeoutError as d, isAutomatorWsConnectError as f, RETRY_CANCEL_KEYS as g, isRetryableAutomatorLaunchError as h, resolveProjectAutomatorPort as i, configureLocaleFromArgv as j, runWithSuspendedSharedInput as k, isAutomatorLoginError as l, isDevtoolsHttpPortError as m, launchAutomator as n, detectWechatDevtoolsServicePort as o, isDevtoolsExtensionContextInvalidatedError as p, persistOpenedAutomatorSession as r, formatAutomatorLoginError as s, connectOpenedAutomator as t, isAutomatorPortInUseError as u, RETRY_PROMPT_INITIAL_IGNORE_MS as v, isWechatIdeLoginRequiredExitError as w, formatRetryHotkeyPrompt as x, createWechatIdeLoginRequiredExitError as y };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { n as launchAutomator } from "./automator-BOe32upt.js";
|