weixin-devtools-mcp 0.0.1 → 0.1.1

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.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -128
  3. package/build/MiniProgramContext.d.ts +317 -0
  4. package/build/MiniProgramContext.d.ts.map +1 -0
  5. package/build/MiniProgramContext.js +696 -0
  6. package/build/MiniProgramContext.js.map +1 -0
  7. package/build/collectors/Collector.d.ts +127 -0
  8. package/build/collectors/Collector.d.ts.map +1 -0
  9. package/build/collectors/Collector.js +252 -0
  10. package/build/collectors/Collector.js.map +1 -0
  11. package/build/collectors/ConsoleCollector.d.ts +104 -0
  12. package/build/collectors/ConsoleCollector.d.ts.map +1 -0
  13. package/build/collectors/ConsoleCollector.js +157 -0
  14. package/build/collectors/ConsoleCollector.js.map +1 -0
  15. package/build/collectors/NetworkCollector.d.ts +167 -0
  16. package/build/collectors/NetworkCollector.d.ts.map +1 -0
  17. package/build/collectors/NetworkCollector.js +265 -0
  18. package/build/collectors/NetworkCollector.js.map +1 -0
  19. package/build/collectors/index.d.ts +13 -0
  20. package/build/collectors/index.d.ts.map +1 -0
  21. package/build/collectors/index.js +17 -0
  22. package/build/collectors/index.js.map +1 -0
  23. package/build/config/tool-profile.d.ts +30 -0
  24. package/build/config/tool-profile.d.ts.map +1 -0
  25. package/build/config/tool-profile.js +138 -0
  26. package/build/config/tool-profile.js.map +1 -0
  27. package/build/connection/adapters.d.ts +3 -0
  28. package/build/connection/adapters.d.ts.map +1 -0
  29. package/build/connection/adapters.js +134 -0
  30. package/build/connection/adapters.js.map +1 -0
  31. package/build/connection/errors.d.ts +34 -0
  32. package/build/connection/errors.d.ts.map +1 -0
  33. package/build/connection/errors.js +101 -0
  34. package/build/connection/errors.js.map +1 -0
  35. package/build/connection/health-probe.d.ts +4 -0
  36. package/build/connection/health-probe.d.ts.map +1 -0
  37. package/build/connection/health-probe.js +60 -0
  38. package/build/connection/health-probe.js.map +1 -0
  39. package/build/connection/index.d.ts +6 -0
  40. package/build/connection/index.d.ts.map +1 -0
  41. package/build/connection/index.js +6 -0
  42. package/build/connection/index.js.map +1 -0
  43. package/build/connection/manager.d.ts +19 -0
  44. package/build/connection/manager.d.ts.map +1 -0
  45. package/build/connection/manager.js +227 -0
  46. package/build/connection/manager.js.map +1 -0
  47. package/build/connection/resolver.d.ts +3 -0
  48. package/build/connection/resolver.d.ts.map +1 -0
  49. package/build/connection/resolver.js +99 -0
  50. package/build/connection/resolver.js.map +1 -0
  51. package/build/connection/types.d.ts +95 -0
  52. package/build/connection/types.d.ts.map +1 -0
  53. package/build/connection/types.js +16 -0
  54. package/build/connection/types.js.map +1 -0
  55. package/build/core/assertion.d.ts +22 -0
  56. package/build/core/assertion.d.ts.map +1 -0
  57. package/build/core/assertion.js +318 -0
  58. package/build/core/assertion.js.map +1 -0
  59. package/build/core/connection.d.ts +37 -0
  60. package/build/core/connection.d.ts.map +1 -0
  61. package/build/core/connection.js +745 -0
  62. package/build/core/connection.js.map +1 -0
  63. package/build/core/index.d.ts +13 -0
  64. package/build/core/index.d.ts.map +1 -0
  65. package/build/core/index.js +19 -0
  66. package/build/core/index.js.map +1 -0
  67. package/build/core/interaction.d.ts +22 -0
  68. package/build/core/interaction.d.ts.map +1 -0
  69. package/build/core/interaction.js +185 -0
  70. package/build/core/interaction.js.map +1 -0
  71. package/build/core/navigation.d.ts +26 -0
  72. package/build/core/navigation.d.ts.map +1 -0
  73. package/build/core/navigation.js +210 -0
  74. package/build/core/navigation.js.map +1 -0
  75. package/build/core/query.d.ts +14 -0
  76. package/build/core/query.d.ts.map +1 -0
  77. package/build/core/query.js +191 -0
  78. package/build/core/query.js.map +1 -0
  79. package/build/core/screenshot.d.ts +10 -0
  80. package/build/core/screenshot.d.ts.map +1 -0
  81. package/build/core/screenshot.js +93 -0
  82. package/build/core/screenshot.js.map +1 -0
  83. package/build/core/snapshot.d.ts +17 -0
  84. package/build/core/snapshot.d.ts.map +1 -0
  85. package/build/core/snapshot.js +225 -0
  86. package/build/core/snapshot.js.map +1 -0
  87. package/build/core/types.d.ts +250 -0
  88. package/build/core/types.d.ts.map +1 -0
  89. package/build/core/types.js +6 -0
  90. package/build/core/types.js.map +1 -0
  91. package/build/formatters/consoleFormatter.d.ts +50 -0
  92. package/build/formatters/consoleFormatter.d.ts.map +1 -0
  93. package/build/formatters/consoleFormatter.js +116 -0
  94. package/build/formatters/consoleFormatter.js.map +1 -0
  95. package/build/formatters/snapshotFormatter.d.ts +41 -0
  96. package/build/formatters/snapshotFormatter.d.ts.map +1 -0
  97. package/build/formatters/snapshotFormatter.js +156 -0
  98. package/build/formatters/snapshotFormatter.js.map +1 -0
  99. package/build/index.d.ts +11 -0
  100. package/build/index.d.ts.map +1 -0
  101. package/build/index.js +45 -9
  102. package/build/index.js.map +1 -0
  103. package/build/server.d.ts +7 -0
  104. package/build/server.d.ts.map +1 -0
  105. package/build/server.js +88 -32
  106. package/build/server.js.map +1 -0
  107. package/build/tools/ToolDefinition.d.ts +265 -0
  108. package/build/tools/ToolDefinition.d.ts.map +1 -0
  109. package/build/tools/ToolDefinition.js +16 -7
  110. package/build/tools/ToolDefinition.js.map +1 -0
  111. package/build/tools/assert.d.ts +17 -0
  112. package/build/tools/assert.d.ts.map +1 -0
  113. package/build/tools/assert.js +63 -103
  114. package/build/tools/assert.js.map +1 -0
  115. package/build/tools/connection.d.ts +13 -0
  116. package/build/tools/connection.d.ts.map +1 -0
  117. package/build/tools/connection.js +338 -611
  118. package/build/tools/connection.js.map +1 -0
  119. package/build/tools/console.d.ts +20 -0
  120. package/build/tools/console.d.ts.map +1 -0
  121. package/build/tools/console.js +162 -152
  122. package/build/tools/console.js.map +1 -0
  123. package/build/tools/diagnose.d.ts +22 -0
  124. package/build/tools/diagnose.d.ts.map +1 -0
  125. package/build/tools/diagnose.js +406 -13
  126. package/build/tools/diagnose.js.map +1 -0
  127. package/build/tools/index.d.ts +6 -0
  128. package/build/tools/index.d.ts.map +1 -0
  129. package/build/tools/index.js +3 -77
  130. package/build/tools/index.js.map +1 -0
  131. package/build/tools/input.d.ts +21 -0
  132. package/build/tools/input.d.ts.map +1 -0
  133. package/build/tools/input.js +73 -139
  134. package/build/tools/input.js.map +1 -0
  135. package/build/tools/navigate.d.ts +21 -0
  136. package/build/tools/navigate.d.ts.map +1 -0
  137. package/build/tools/navigate.js +63 -126
  138. package/build/tools/navigate.js.map +1 -0
  139. package/build/tools/network.d.ts +21 -0
  140. package/build/tools/network.d.ts.map +1 -0
  141. package/build/tools/network.js +214 -1044
  142. package/build/tools/network.js.map +1 -0
  143. package/build/tools/page.d.ts +13 -0
  144. package/build/tools/page.d.ts.map +1 -0
  145. package/build/tools/page.js +6 -3
  146. package/build/tools/page.js.map +1 -0
  147. package/build/tools/screenshot.d.ts +9 -0
  148. package/build/tools/screenshot.d.ts.map +1 -0
  149. package/build/tools/screenshot.js +3 -1
  150. package/build/tools/screenshot.js.map +1 -0
  151. package/build/tools/script.d.ts +6 -0
  152. package/build/tools/script.d.ts.map +1 -0
  153. package/build/tools/script.js +92 -0
  154. package/build/tools/script.js.map +1 -0
  155. package/build/tools/snapshot.d.ts +9 -0
  156. package/build/tools/snapshot.d.ts.map +1 -0
  157. package/build/tools/snapshot.js +78 -12
  158. package/build/tools/snapshot.js.map +1 -0
  159. package/build/tools/tools.d.ts +15 -0
  160. package/build/tools/tools.d.ts.map +1 -0
  161. package/build/tools/tools.js +63 -0
  162. package/build/tools/tools.js.map +1 -0
  163. package/build/tools.d.ts +431 -0
  164. package/build/tools.d.ts.map +1 -0
  165. package/build/tools.js +258 -118
  166. package/build/tools.js.map +1 -0
  167. package/build/types/errors.d.ts +189 -0
  168. package/build/types/errors.d.ts.map +1 -0
  169. package/build/types/errors.js +257 -0
  170. package/build/types/errors.js.map +1 -0
  171. package/build/utils/error.d.ts +6 -0
  172. package/build/utils/error.d.ts.map +1 -0
  173. package/build/utils/error.js +11 -0
  174. package/build/utils/error.js.map +1 -0
  175. package/build/utils/idGenerator.d.ts +21 -0
  176. package/build/utils/idGenerator.d.ts.map +1 -0
  177. package/build/utils/idGenerator.js +23 -0
  178. package/build/utils/idGenerator.js.map +1 -0
  179. package/build/version.d.ts +7 -0
  180. package/build/version.d.ts.map +1 -0
  181. package/build/version.js +10 -0
  182. package/build/version.js.map +1 -0
  183. package/package.json +31 -9
@@ -0,0 +1,745 @@
1
+ /**
2
+ * 连接管理核心逻辑
3
+ * 从 src/tools.ts 提取的连接相关函数
4
+ */
5
+ import { spawn } from "child_process";
6
+ import fs from "fs";
7
+ import path from "path";
8
+ import { promisify } from "util";
9
+ import net from 'node:net';
10
+ import automator from "miniprogram-automator";
11
+ import { DevToolsError, ErrorCode, ErrorCategory } from '../types/errors.js';
12
+ import { extractErrorMessage } from '../utils/error.js';
13
+ const sleep = promisify(setTimeout);
14
+ /**
15
+ * @deprecated 使用 DevToolsError 替代
16
+ * 保留此类以保持向后兼容性
17
+ */
18
+ export class DevToolsConnectionError extends DevToolsError {
19
+ phase;
20
+ originalError;
21
+ details;
22
+ constructor(message, phase, originalError, details) {
23
+ const code = phase === 'startup'
24
+ ? ErrorCode.CONNECTION_FAILED
25
+ : phase === 'connection'
26
+ ? ErrorCode.CONNECTION_FAILED
27
+ : ErrorCode.CONNECTION_TIMEOUT;
28
+ const context = {
29
+ operation: phase,
30
+ details,
31
+ cause: originalError,
32
+ };
33
+ super(message, code, {
34
+ category: ErrorCategory.CONNECTION,
35
+ context,
36
+ });
37
+ this.phase = phase;
38
+ this.originalError = originalError;
39
+ this.details = details;
40
+ this.name = 'DevToolsConnectionError';
41
+ }
42
+ }
43
+ /**
44
+ * 连接到微信开发者工具
45
+ */
46
+ export async function connectDevtools(options) {
47
+ const { projectPath, cliPath, port, autoAudits } = options;
48
+ if (!projectPath) {
49
+ throw new Error("项目路径是必需的");
50
+ }
51
+ try {
52
+ let resolvedProjectPath = projectPath;
53
+ if (projectPath.startsWith('@playground/')) {
54
+ const relativePath = projectPath.replace('@playground/', 'playground/');
55
+ resolvedProjectPath = path.resolve(process.cwd(), relativePath);
56
+ }
57
+ else if (!path.isAbsolute(projectPath)) {
58
+ resolvedProjectPath = path.resolve(process.cwd(), projectPath);
59
+ }
60
+ if (!fs.existsSync(resolvedProjectPath)) {
61
+ throw new Error(`Project path '${resolvedProjectPath}' doesn't exist`);
62
+ }
63
+ const launchOptions = { projectPath: resolvedProjectPath };
64
+ if (cliPath)
65
+ launchOptions.cliPath = cliPath;
66
+ if (port)
67
+ launchOptions.port = port;
68
+ if (typeof autoAudits === 'boolean') {
69
+ launchOptions.projectConfig = {
70
+ ...(launchOptions.projectConfig || {}),
71
+ setting: {
72
+ ...(launchOptions.projectConfig?.setting || {}),
73
+ autoAudits
74
+ }
75
+ };
76
+ }
77
+ const miniProgram = await automator.launch(launchOptions);
78
+ const currentPage = await miniProgram.currentPage();
79
+ if (!currentPage) {
80
+ throw new Error("无法获取当前页面");
81
+ }
82
+ const pagePath = await currentPage.path;
83
+ // 自动启动网络监听
84
+ try {
85
+ await miniProgram.mockWxMethod('request', function (options) {
86
+ // @ts-expect-error wx is available in WeChat miniprogram runtime - wx is available in WeChat miniprogram environment
87
+ const wxObj = (typeof wx !== 'undefined' ? wx : null);
88
+ if (!wxObj)
89
+ return this.origin(options);
90
+ if (!wxObj.__networkLogs)
91
+ wxObj.__networkLogs = [];
92
+ const requestId = 'req_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
93
+ const startTime = Date.now();
94
+ const originalSuccess = options.success;
95
+ options.success = function (res) {
96
+ wxObj.__networkLogs.push({
97
+ id: requestId,
98
+ type: 'request',
99
+ url: options.url,
100
+ method: options.method || 'GET',
101
+ headers: options.header,
102
+ data: options.data,
103
+ statusCode: res.statusCode,
104
+ response: res.data,
105
+ duration: Date.now() - startTime,
106
+ timestamp: new Date().toISOString(),
107
+ success: true
108
+ });
109
+ if (originalSuccess)
110
+ originalSuccess(res);
111
+ };
112
+ const originalFail = options.fail;
113
+ options.fail = function (err) {
114
+ wxObj.__networkLogs.push({
115
+ id: requestId,
116
+ type: 'request',
117
+ url: options.url,
118
+ method: options.method || 'GET',
119
+ headers: options.header,
120
+ data: options.data,
121
+ error: err.errMsg || String(err),
122
+ duration: Date.now() - startTime,
123
+ timestamp: new Date().toISOString(),
124
+ success: false
125
+ });
126
+ if (originalFail)
127
+ originalFail(err);
128
+ };
129
+ return this.origin(options);
130
+ });
131
+ await miniProgram.mockWxMethod('uploadFile', function (options) {
132
+ // @ts-expect-error wx is available in WeChat miniprogram runtime
133
+ const wxObj = (typeof wx !== 'undefined' ? wx : null);
134
+ if (!wxObj)
135
+ return this.origin(options);
136
+ if (!wxObj.__networkLogs)
137
+ wxObj.__networkLogs = [];
138
+ const requestId = 'req_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
139
+ const startTime = Date.now();
140
+ const originalSuccess = options.success;
141
+ options.success = function (res) {
142
+ wxObj.__networkLogs.push({
143
+ id: requestId,
144
+ type: 'uploadFile',
145
+ url: options.url,
146
+ statusCode: res.statusCode,
147
+ duration: Date.now() - startTime,
148
+ timestamp: new Date().toISOString(),
149
+ success: true
150
+ });
151
+ if (originalSuccess)
152
+ originalSuccess(res);
153
+ };
154
+ const originalFail = options.fail;
155
+ options.fail = function (err) {
156
+ wxObj.__networkLogs.push({
157
+ id: requestId,
158
+ type: 'uploadFile',
159
+ url: options.url,
160
+ error: err.errMsg || String(err),
161
+ duration: Date.now() - startTime,
162
+ timestamp: new Date().toISOString(),
163
+ success: false
164
+ });
165
+ if (originalFail)
166
+ originalFail(err);
167
+ };
168
+ return this.origin(options);
169
+ });
170
+ await miniProgram.mockWxMethod('downloadFile', function (options) {
171
+ // @ts-expect-error wx is available in WeChat miniprogram runtime
172
+ const wxObj = (typeof wx !== 'undefined' ? wx : null);
173
+ if (!wxObj)
174
+ return this.origin(options);
175
+ if (!wxObj.__networkLogs)
176
+ wxObj.__networkLogs = [];
177
+ const requestId = 'req_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
178
+ const startTime = Date.now();
179
+ const originalSuccess = options.success;
180
+ options.success = function (res) {
181
+ wxObj.__networkLogs.push({
182
+ id: requestId,
183
+ type: 'downloadFile',
184
+ url: options.url,
185
+ statusCode: res.statusCode,
186
+ duration: Date.now() - startTime,
187
+ timestamp: new Date().toISOString(),
188
+ success: true
189
+ });
190
+ if (originalSuccess)
191
+ originalSuccess(res);
192
+ };
193
+ const originalFail = options.fail;
194
+ options.fail = function (err) {
195
+ wxObj.__networkLogs.push({
196
+ id: requestId,
197
+ type: 'downloadFile',
198
+ url: options.url,
199
+ error: err.errMsg || String(err),
200
+ duration: Date.now() - startTime,
201
+ timestamp: new Date().toISOString(),
202
+ success: false
203
+ });
204
+ if (originalFail)
205
+ originalFail(err);
206
+ };
207
+ return this.origin(options);
208
+ });
209
+ // 拦截 Mpx 框架的 $xfetch
210
+ await miniProgram.evaluate(function () {
211
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
212
+ const getApp = globalThis.getApp;
213
+ // @ts-expect-error wx is available in WeChat miniprogram runtime
214
+ if (typeof wx === 'undefined')
215
+ return;
216
+ // @ts-expect-error wx is available in WeChat miniprogram runtime
217
+ wx.__networkLogs = wx.__networkLogs || [];
218
+ const app = typeof getApp !== 'undefined' ? getApp?.() : null;
219
+ const hasMpxFetch = app &&
220
+ app.$xfetch &&
221
+ app.$xfetch.interceptors &&
222
+ typeof app.$xfetch.interceptors.request.use === 'function';
223
+ const hasGetApp = typeof getApp !== 'undefined';
224
+ const debugInfo = {
225
+ hasGetApp,
226
+ hasApp: !!app,
227
+ has$xfetch: !!(app && app.$xfetch),
228
+ hasInterceptors: !!(app && app.$xfetch && app.$xfetch.interceptors),
229
+ hasMpxFetch: hasMpxFetch
230
+ };
231
+ console.error('[MCP-DEBUG] Mpx检测:', debugInfo);
232
+ if (hasMpxFetch) {
233
+ console.error('[MCP] 正在安装 Mpx $xfetch 拦截器(强制覆盖)...');
234
+ app.$xfetch.interceptors.request.use(function (config) {
235
+ const requestId = 'mpx_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
236
+ const startTime = Date.now();
237
+ config.__mcp_requestId = requestId;
238
+ config.__mcp_startTime = startTime;
239
+ // @ts-expect-error wx is available in WeChat miniprogram runtime
240
+ wx.__networkLogs.push({
241
+ id: requestId,
242
+ type: 'request',
243
+ method: config.method || 'GET',
244
+ url: config.url,
245
+ headers: config.header || config.headers,
246
+ data: config.data,
247
+ params: config.params,
248
+ timestamp: new Date().toISOString(),
249
+ source: 'getApp().$xfetch',
250
+ phase: 'request'
251
+ });
252
+ return config;
253
+ });
254
+ app.$xfetch.interceptors.response.use(function onSuccess(response) {
255
+ const requestId = response.requestConfig?.__mcp_requestId;
256
+ const startTime = response.requestConfig?.__mcp_startTime || Date.now();
257
+ // @ts-expect-error wx is available in WeChat miniprogram runtime
258
+ wx.__networkLogs.push({
259
+ id: requestId,
260
+ type: 'response',
261
+ statusCode: response.status,
262
+ data: response.data,
263
+ headers: response.header || response.headers,
264
+ duration: Date.now() - startTime,
265
+ timestamp: new Date().toISOString(),
266
+ source: 'getApp().$xfetch',
267
+ phase: 'response',
268
+ success: true
269
+ });
270
+ return response;
271
+ }, function onError(error) {
272
+ const requestId = error.requestConfig?.__mcp_requestId;
273
+ const startTime = error.requestConfig?.__mcp_startTime || Date.now();
274
+ // @ts-expect-error wx is available in WeChat miniprogram runtime
275
+ wx.__networkLogs.push({
276
+ id: requestId,
277
+ type: 'response',
278
+ statusCode: error.status || error.statusCode,
279
+ error: error.message || error.errMsg || String(error),
280
+ duration: Date.now() - startTime,
281
+ timestamp: new Date().toISOString(),
282
+ source: 'getApp().$xfetch',
283
+ phase: 'response',
284
+ success: false
285
+ });
286
+ throw error;
287
+ });
288
+ console.error('[MCP] Mpx $xfetch 拦截器安装完成');
289
+ }
290
+ // @ts-expect-error wx is available in WeChat miniprogram runtime
291
+ wx.__networkInterceptorsInstalled = true;
292
+ });
293
+ console.error('[connectDevtools] 网络监听已自动启动(包含 Mpx 框架支持)');
294
+ }
295
+ catch (err) {
296
+ console.warn('[connectDevtools] 网络监听启动失败:', err);
297
+ }
298
+ return {
299
+ miniProgram,
300
+ currentPage,
301
+ pagePath
302
+ };
303
+ }
304
+ catch (error) {
305
+ const errorMessage = extractErrorMessage(error);
306
+ throw new Error(`连接微信开发者工具失败: ${errorMessage}`);
307
+ }
308
+ }
309
+ /**
310
+ * 智能连接到微信开发者工具(优化版)
311
+ */
312
+ export async function connectDevtoolsEnhanced(options) {
313
+ const { mode = 'auto', verbose = false } = options;
314
+ const startTime = Date.now();
315
+ if (!options.projectPath) {
316
+ throw new Error("项目路径是必需的");
317
+ }
318
+ let resolvedProjectPath = options.projectPath;
319
+ if (options.projectPath.startsWith('@playground/')) {
320
+ const relativePath = options.projectPath.replace('@playground/', 'playground/');
321
+ resolvedProjectPath = path.resolve(process.cwd(), relativePath);
322
+ }
323
+ else if (!path.isAbsolute(options.projectPath)) {
324
+ resolvedProjectPath = path.resolve(process.cwd(), options.projectPath);
325
+ }
326
+ if (!fs.existsSync(resolvedProjectPath)) {
327
+ throw new Error(`Project path '${resolvedProjectPath}' doesn't exist`);
328
+ }
329
+ if (verbose) {
330
+ console.error(`开始连接微信开发者工具,模式: ${mode}`);
331
+ console.error(`项目路径: ${resolvedProjectPath}`);
332
+ }
333
+ try {
334
+ switch (mode) {
335
+ case 'auto':
336
+ return await intelligentConnect(options, startTime);
337
+ case 'connect':
338
+ return await connectMode(options, startTime);
339
+ case 'launch':
340
+ return await launchMode(options, startTime);
341
+ default:
342
+ throw new Error(`不支持的连接模式: ${mode}`);
343
+ }
344
+ }
345
+ catch (error) {
346
+ if (verbose) {
347
+ console.error(`连接失败:`, error);
348
+ }
349
+ throw error;
350
+ }
351
+ }
352
+ function isSessionConflictError(error) {
353
+ if (error instanceof DevToolsConnectionError) {
354
+ return error.details?.reason === 'session_conflict';
355
+ }
356
+ const message = error?.message || '';
357
+ return message.includes('already') ||
358
+ message.includes('session') ||
359
+ message.includes('conflict') ||
360
+ message.includes('automation');
361
+ }
362
+ async function intelligentConnect(options, startTime) {
363
+ if (options.verbose) {
364
+ console.error('🎯 智能连接策略: 优先使用 launchMode(自动处理项目验证和会话复用)');
365
+ }
366
+ try {
367
+ return await launchMode(options, startTime);
368
+ }
369
+ catch (error) {
370
+ if (options.verbose) {
371
+ console.error('⚠️ launchMode 失败,分析错误类型...');
372
+ }
373
+ if (options.fallbackMode && isSessionConflictError(error)) {
374
+ if (options.verbose) {
375
+ console.error('🔄 检测到会话冲突,尝试回退到 connectMode');
376
+ }
377
+ return await connectMode(options, startTime);
378
+ }
379
+ throw error;
380
+ }
381
+ }
382
+ async function connectMode(options, startTime) {
383
+ try {
384
+ const startupResult = await executeWithDetailedError(() => startupPhase(options), 'startup');
385
+ const connectionResult = await executeWithDetailedError(() => connectionPhase(options, startupResult), 'connection');
386
+ let healthStatus = 'healthy';
387
+ if (options.healthCheck) {
388
+ healthStatus = await executeWithDetailedError(() => performHealthCheck(connectionResult.miniProgram), 'health_check');
389
+ }
390
+ return {
391
+ ...connectionResult,
392
+ connectionMode: 'connect',
393
+ startupTime: Date.now() - startTime,
394
+ healthStatus,
395
+ processInfo: startupResult.processInfo
396
+ };
397
+ }
398
+ catch (error) {
399
+ if (error instanceof DevToolsConnectionError &&
400
+ error.phase === 'startup' &&
401
+ error.details?.reason === 'session_conflict') {
402
+ if (options.verbose) {
403
+ console.error('🔄 检测到会话冲突,自动回退到传统连接模式(launch)...');
404
+ }
405
+ if (options.fallbackMode) {
406
+ return await launchMode(options, startTime);
407
+ }
408
+ }
409
+ throw error;
410
+ }
411
+ }
412
+ async function launchMode(options, startTime) {
413
+ const connectOptions = {
414
+ projectPath: options.projectPath,
415
+ cliPath: options.cliPath,
416
+ port: options.autoPort || options.port,
417
+ autoAudits: options.autoAudits
418
+ };
419
+ const result = await connectDevtools(connectOptions);
420
+ let healthStatus = 'healthy';
421
+ if (options.healthCheck) {
422
+ healthStatus = await executeWithDetailedError(() => performHealthCheck(result.miniProgram), 'health_check');
423
+ }
424
+ return {
425
+ ...result,
426
+ connectionMode: 'launch',
427
+ startupTime: Date.now() - startTime,
428
+ healthStatus
429
+ };
430
+ }
431
+ async function startupPhase(options) {
432
+ const port = options.autoPort || 9420;
433
+ const cliCommand = buildCliCommand(options);
434
+ if (options.verbose) {
435
+ console.error('执行CLI命令:', cliCommand.join(' '));
436
+ }
437
+ const cliProcess = await executeCliCommand(cliCommand);
438
+ await waitForWebSocketReady(port, options.timeout || 45000, options.verbose);
439
+ return {
440
+ processInfo: {
441
+ pid: cliProcess.pid,
442
+ port
443
+ },
444
+ startTime: Date.now()
445
+ };
446
+ }
447
+ async function connectionPhase(options, startupResult) {
448
+ const wsEndpoint = `ws://localhost:${startupResult.processInfo.port}`;
449
+ if (options.verbose) {
450
+ console.error('连接WebSocket端点:', wsEndpoint);
451
+ }
452
+ const miniProgram = await connectWithRetry(wsEndpoint, 3);
453
+ const currentPage = await miniProgram.currentPage();
454
+ if (!currentPage) {
455
+ throw new Error('无法获取当前页面');
456
+ }
457
+ const pagePath = await currentPage.path;
458
+ return {
459
+ miniProgram,
460
+ currentPage,
461
+ pagePath
462
+ };
463
+ }
464
+ function buildCliCommand(options) {
465
+ const cliPath = options.cliPath || findDefaultCliPath();
466
+ const resolvedProjectPath = resolveProjectPath(options.projectPath);
467
+ const args = ['auto', '--project', resolvedProjectPath];
468
+ if (options.autoPort) {
469
+ args.push('--auto-port', options.autoPort.toString());
470
+ }
471
+ if (options.autoAccount) {
472
+ console.warn('autoAccount参数可能不受支持,已忽略');
473
+ }
474
+ if (options.verbose) {
475
+ args.push('--debug');
476
+ }
477
+ return [cliPath, ...args];
478
+ }
479
+ function findDefaultCliPath() {
480
+ const platform = process.platform;
481
+ if (platform === 'darwin') {
482
+ return '/Applications/wechatwebdevtools.app/Contents/MacOS/cli';
483
+ }
484
+ else if (platform === 'win32') {
485
+ return 'C:/Program Files (x86)/Tencent/微信web开发者工具/cli.bat';
486
+ }
487
+ else {
488
+ throw new Error(`不支持的平台: ${platform}`);
489
+ }
490
+ }
491
+ function resolveProjectPath(projectPath) {
492
+ if (projectPath.startsWith('@playground/')) {
493
+ const relativePath = projectPath.replace('@playground/', 'playground/');
494
+ return path.resolve(process.cwd(), relativePath);
495
+ }
496
+ else if (!path.isAbsolute(projectPath)) {
497
+ return path.resolve(process.cwd(), projectPath);
498
+ }
499
+ return projectPath;
500
+ }
501
+ async function executeCliCommand(command) {
502
+ const [cliPath, ...args] = command;
503
+ return new Promise((resolve, reject) => {
504
+ const cliProcess = spawn(cliPath, args, {
505
+ stdio: ['ignore', 'pipe', 'pipe']
506
+ });
507
+ let output = '';
508
+ let errorOutput = '';
509
+ let resolved = false;
510
+ if (cliProcess.stdout) {
511
+ cliProcess.stdout.on('data', (data) => {
512
+ const text = data.toString();
513
+ output += text;
514
+ console.error('[CLI stdout]:', text.trim());
515
+ });
516
+ }
517
+ if (cliProcess.stderr) {
518
+ cliProcess.stderr.on('data', (data) => {
519
+ const text = data.toString();
520
+ errorOutput += text;
521
+ console.error('[CLI stderr]:', text.trim());
522
+ if (text.includes('must be restarted on port')) {
523
+ const match = text.match(/started on .+:(\d+) and must be restarted on port (\d+)/);
524
+ if (match) {
525
+ const [, currentPort, requestedPort] = match;
526
+ if (!resolved) {
527
+ resolved = true;
528
+ cliProcess.kill();
529
+ reject(new Error(`端口冲突: IDE已在端口 ${currentPort} 上运行,但请求的端口是 ${requestedPort}。\n` +
530
+ `解决方案:\n` +
531
+ `1. 使用当前端口:autoPort: ${currentPort}\n` +
532
+ `2. 关闭微信开发者工具后重新连接`));
533
+ }
534
+ }
535
+ }
536
+ if ((text.includes('automation') || text.includes('自动化')) &&
537
+ (text.includes('already') || text.includes('exists') || text.includes('已存在'))) {
538
+ if (!resolved) {
539
+ resolved = true;
540
+ cliProcess.kill();
541
+ const sessionConflictError = new DevToolsConnectionError(`自动化会话冲突: 微信开发者工具已有活跃的自动化会话`, 'startup', undefined, {
542
+ reason: 'session_conflict',
543
+ suggestFallback: true,
544
+ details: `可能原因:\n` +
545
+ `1. 之前使用了 connect_devtools (传统模式) 并已建立连接\n` +
546
+ `2. 其他程序正在使用自动化功能\n` +
547
+ `解决方案:\n` +
548
+ `1. 使用已建立的连接(工具会自动检测并复用)\n` +
549
+ `2. 关闭微信开发者工具并重新打开\n` +
550
+ `3. 使用 connect_devtools 继续传统模式`
551
+ });
552
+ reject(sessionConflictError);
553
+ }
554
+ }
555
+ if (text.includes('error') || text.includes('failed') || text.includes('失败')) {
556
+ if (!resolved && text.length > 10) {
557
+ console.error('[CLI 警告] 检测到潜在错误:', text.trim());
558
+ }
559
+ }
560
+ });
561
+ }
562
+ cliProcess.on('error', (error) => {
563
+ if (!resolved) {
564
+ resolved = true;
565
+ reject(new Error(`CLI命令执行失败: ${error.message}`));
566
+ }
567
+ });
568
+ cliProcess.on('exit', (code, signal) => {
569
+ if (!resolved && code !== 0 && code !== null) {
570
+ resolved = true;
571
+ const errorMsg = errorOutput || `CLI进程异常退出 (code=${code}, signal=${signal})`;
572
+ reject(new Error(errorMsg));
573
+ }
574
+ });
575
+ cliProcess.on('spawn', () => {
576
+ if (!resolved) {
577
+ resolved = true;
578
+ resolve(cliProcess);
579
+ }
580
+ });
581
+ setTimeout(() => {
582
+ if (!resolved && !cliProcess.killed) {
583
+ resolved = true;
584
+ cliProcess.kill();
585
+ reject(new Error('CLI命令启动超时'));
586
+ }
587
+ }, 10000);
588
+ });
589
+ }
590
+ /**
591
+ * 等待WebSocket服务就绪
592
+ */
593
+ export async function waitForWebSocketReady(port, timeout, verbose = false) {
594
+ const startTime = Date.now();
595
+ let attempt = 0;
596
+ if (verbose) {
597
+ console.error(`等待WebSocket服务启动,端口: ${port},超时: ${timeout}ms`);
598
+ }
599
+ while (Date.now() - startTime < timeout) {
600
+ attempt++;
601
+ if (verbose && attempt % 5 === 0) {
602
+ const elapsed = Date.now() - startTime;
603
+ console.error(`WebSocket检测进度: ${Math.round(elapsed / 1000)}s / ${Math.round(timeout / 1000)}s`);
604
+ }
605
+ const isReady = await checkDevToolsRunning(port) || await checkWebSocketDirectly(port);
606
+ if (isReady) {
607
+ if (verbose) {
608
+ const elapsed = Date.now() - startTime;
609
+ console.error(`WebSocket服务已启动,耗时: ${elapsed}ms`);
610
+ }
611
+ return;
612
+ }
613
+ const waitTime = attempt <= 10 ? 500 : 1000;
614
+ await sleep(waitTime);
615
+ }
616
+ const elapsed = Date.now() - startTime;
617
+ throw new Error(`WebSocket服务启动超时,端口: ${port},已等待: ${elapsed}ms`);
618
+ }
619
+ async function checkWebSocketDirectly(port) {
620
+ // TCP connect probe: good enough for readiness gating here.
621
+ return new Promise((resolve) => {
622
+ const socket = net.connect({ host: '127.0.0.1', port });
623
+ const timer = setTimeout(() => {
624
+ socket.destroy();
625
+ resolve(false);
626
+ }, 2000);
627
+ socket.once('connect', () => {
628
+ clearTimeout(timer);
629
+ socket.end();
630
+ resolve(true);
631
+ });
632
+ socket.once('error', () => {
633
+ clearTimeout(timer);
634
+ socket.destroy();
635
+ resolve(false);
636
+ });
637
+ socket.once('close', () => {
638
+ // No-op; resolve handled by connect/error/timeout.
639
+ });
640
+ });
641
+ }
642
+ /**
643
+ * 检查开发者工具是否运行
644
+ */
645
+ export async function checkDevToolsRunning(port) {
646
+ try {
647
+ const response = await fetch(`http://localhost:${port}`, {
648
+ signal: AbortSignal.timeout(1000)
649
+ });
650
+ return response.ok;
651
+ }
652
+ catch {
653
+ return false;
654
+ }
655
+ }
656
+ /**
657
+ * 自动检测当前IDE运行的端口
658
+ */
659
+ export async function detectIDEPort(verbose = false) {
660
+ const commonPorts = [9420, 9440, 9430, 9450, 9460];
661
+ if (verbose) {
662
+ console.error('🔍 检测微信开发者工具运行端口...');
663
+ }
664
+ for (const port of commonPorts) {
665
+ if (verbose) {
666
+ console.error(` 检测端口 ${port}...`);
667
+ }
668
+ if (await checkDevToolsRunning(port)) {
669
+ if (verbose) {
670
+ console.error(`✅ 检测到IDE运行在端口 ${port}`);
671
+ }
672
+ return port;
673
+ }
674
+ }
675
+ if (process.platform === 'darwin' || process.platform === 'linux') {
676
+ try {
677
+ const { execSync } = await import('child_process');
678
+ const execOutput = execSync("lsof -i -P | grep wechat | grep LISTEN | awk '{print $9}' | cut -d: -f2 | grep '^94[0-9][0-9]$'", { encoding: 'utf-8', timeout: 3000 }).trim();
679
+ if (execOutput) {
680
+ const ports = execOutput.split('\n').map((p) => parseInt(p, 10)).filter((p) => !isNaN(p));
681
+ if (verbose && ports.length > 0) {
682
+ console.error(` lsof检测到端口: ${ports.join(', ')}`);
683
+ }
684
+ for (const port of ports) {
685
+ if (port >= 9400 && port <= 9500) {
686
+ if (await checkDevToolsRunning(port)) {
687
+ if (verbose) {
688
+ console.error(`✅ 通过lsof检测到IDE运行在端口 ${port}`);
689
+ }
690
+ return port;
691
+ }
692
+ }
693
+ }
694
+ }
695
+ }
696
+ catch (error) {
697
+ if (verbose) {
698
+ console.error(' lsof检测失败');
699
+ }
700
+ }
701
+ }
702
+ if (verbose) {
703
+ console.error('❌ 未检测到IDE运行端口');
704
+ }
705
+ return null;
706
+ }
707
+ async function connectWithRetry(wsEndpoint, maxRetries) {
708
+ for (let i = 0; i < maxRetries; i++) {
709
+ try {
710
+ return await automator.connect({ wsEndpoint });
711
+ }
712
+ catch (error) {
713
+ if (i === maxRetries - 1) {
714
+ throw error;
715
+ }
716
+ await sleep(1000 * Math.pow(2, i));
717
+ }
718
+ }
719
+ }
720
+ async function performHealthCheck(miniProgram) {
721
+ try {
722
+ const currentPage = await miniProgram.currentPage();
723
+ if (!currentPage) {
724
+ return 'unhealthy';
725
+ }
726
+ const pagePath = await currentPage.path;
727
+ if (!pagePath) {
728
+ return 'degraded';
729
+ }
730
+ return 'healthy';
731
+ }
732
+ catch {
733
+ return 'unhealthy';
734
+ }
735
+ }
736
+ async function executeWithDetailedError(operation, phase) {
737
+ try {
738
+ return await operation();
739
+ }
740
+ catch (error) {
741
+ const originalError = error instanceof Error ? error : new Error(String(error));
742
+ throw new DevToolsConnectionError(originalError.message, phase, originalError, { timestamp: new Date().toISOString() });
743
+ }
744
+ }
745
+ //# sourceMappingURL=connection.js.map