weapp-ide-cli 6.1.8 → 6.2.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.
@@ -0,0 +1,605 @@
1
+ import path from "node:path";
2
+ import { execa } from "execa";
3
+ import { createHash } from "node:crypto";
4
+ import fs from "node:fs/promises";
5
+ import process from "node:process";
6
+ import { fs as fs$1 } from "@weapp-core/shared/fs";
7
+ import baseLogger, { colors } from "@weapp-core/logger";
8
+ import os from "node:os";
9
+ import path$1 from "pathe";
10
+ import { Buffer } from "node:buffer";
11
+ //#region src/logger.ts
12
+ const logger = baseLogger.create({
13
+ stdout: process.stderr,
14
+ stderr: process.stderr
15
+ });
16
+ //#endregion
17
+ //#region src/runtime/platform.ts
18
+ /**
19
+ * @description 官方微信开发者工具只支持 Windows、macOS,Linux 只有社区版。
20
+ * https://github.com/msojocs/wechat-web-devtools-linux
21
+ */
22
+ const SupportedPlatformsMap = {
23
+ Windows_NT: "Windows_NT",
24
+ Darwin: "Darwin",
25
+ Linux: "Linux"
26
+ };
27
+ /**
28
+ * @description 判断当前系统是否支持微信开发者工具。
29
+ */
30
+ function isOperatingSystemSupported(osName = os.type()) {
31
+ return osName === SupportedPlatformsMap.Windows_NT || osName === SupportedPlatformsMap.Darwin || osName === SupportedPlatformsMap.Linux;
32
+ }
33
+ /**
34
+ * @description 当前系统名称。
35
+ */
36
+ const operatingSystemName = os.type();
37
+ async function findFirstExistingPath(candidates) {
38
+ for (const candidate of candidates) {
39
+ if (!candidate) continue;
40
+ if (await fs$1.pathExists(candidate)) return candidate;
41
+ }
42
+ }
43
+ async function getFirstBinaryPath(command) {
44
+ const pathDirs = (process.env.PATH || "").split(path$1.delimiter);
45
+ for (const dir of pathDirs) {
46
+ const fullPath = path$1.join(dir, command);
47
+ try {
48
+ await fs$1.access(fullPath, fs$1.constants.X_OK);
49
+ return fullPath;
50
+ } catch {
51
+ continue;
52
+ }
53
+ }
54
+ }
55
+ function createLinuxCliResolver() {
56
+ let resolvedPath;
57
+ let attempted = false;
58
+ let pending = null;
59
+ return async () => {
60
+ if (attempted) return resolvedPath;
61
+ if (!pending) pending = (async () => {
62
+ try {
63
+ const envPath = await getFirstBinaryPath("wechat-devtools-cli");
64
+ if (envPath) resolvedPath = envPath;
65
+ } catch (error) {
66
+ const reason = error instanceof Error ? error.message : String(error);
67
+ logger.warn(`获取 Linux wechat-devtools-cli 路径失败:${reason}`);
68
+ } finally {
69
+ attempted = true;
70
+ }
71
+ return resolvedPath;
72
+ })();
73
+ return pending;
74
+ };
75
+ }
76
+ function createWindowsCliResolver() {
77
+ let resolvedPath;
78
+ let attempted = false;
79
+ let pending = null;
80
+ return async () => {
81
+ if (attempted) return resolvedPath;
82
+ if (!pending) pending = (async () => {
83
+ const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
84
+ const programFiles = process.env.ProgramFiles || "C:\\Program Files";
85
+ const localAppData = process.env.LOCALAPPDATA || path$1.join(os.homedir(), "AppData", "Local");
86
+ const candidates = [
87
+ path$1.join(programFilesX86, "Tencent", "微信web开发者工具", "cli.bat"),
88
+ path$1.join(programFiles, "Tencent", "微信web开发者工具", "cli.bat"),
89
+ path$1.join(localAppData, "Programs", "微信开发者工具", "cli.bat"),
90
+ path$1.join(localAppData, "Tencent", "微信web开发者工具", "cli.bat")
91
+ ];
92
+ try {
93
+ resolvedPath = await findFirstExistingPath(candidates);
94
+ } catch (error) {
95
+ const reason = error instanceof Error ? error.message : String(error);
96
+ logger.warn(`获取 Windows 微信开发者工具 CLI 路径失败:${reason}`);
97
+ } finally {
98
+ attempted = true;
99
+ }
100
+ return resolvedPath;
101
+ })();
102
+ return pending;
103
+ };
104
+ }
105
+ const linuxCliResolver = createLinuxCliResolver();
106
+ const windowsCliResolver = createWindowsCliResolver();
107
+ const DARWIN_DEFAULT_CLI = "/Applications/wechatwebdevtools.app/Contents/MacOS/cli";
108
+ const cliPathResolvers = {
109
+ [SupportedPlatformsMap.Windows_NT]: windowsCliResolver,
110
+ [SupportedPlatformsMap.Darwin]: async () => DARWIN_DEFAULT_CLI,
111
+ [SupportedPlatformsMap.Linux]: linuxCliResolver
112
+ };
113
+ /**
114
+ * @description 获取默认 CLI 路径(按系统)。
115
+ */
116
+ async function getDefaultCliPath(targetOs = operatingSystemName) {
117
+ if (!isOperatingSystemSupported(targetOs)) return;
118
+ const resolver = cliPathResolvers[targetOs];
119
+ return await resolver();
120
+ }
121
+ //#endregion
122
+ //#region src/utils/path.ts
123
+ /**
124
+ * @description 解析为绝对路径(基于当前工作目录)
125
+ */
126
+ function resolvePath(filePath) {
127
+ if (path$1.isAbsolute(filePath)) return filePath;
128
+ return path$1.resolve(process.cwd(), filePath);
129
+ }
130
+ //#endregion
131
+ //#region src/config/paths.ts
132
+ const homedir = os.homedir();
133
+ /**
134
+ * @description 默认自定义配置目录
135
+ */
136
+ const defaultCustomConfigDirPath = path$1.join(homedir, ".weapp-ide-cli");
137
+ /**
138
+ * @description 默认自定义配置文件路径
139
+ */
140
+ const defaultCustomConfigFilePath = path$1.join(defaultCustomConfigDirPath, "config.json");
141
+ //#endregion
142
+ //#region src/config/custom.ts
143
+ const JSON_OPTIONS = {
144
+ encoding: "utf8",
145
+ spaces: 2
146
+ };
147
+ async function readCustomConfig() {
148
+ if (!await fs$1.pathExists(defaultCustomConfigFilePath)) return {};
149
+ try {
150
+ const config = await fs$1.readJSON(defaultCustomConfigFilePath);
151
+ if (!config || typeof config !== "object") return {};
152
+ const candidate = config;
153
+ const next = {};
154
+ if (typeof candidate.cliPath === "string" && candidate.cliPath.trim()) next.cliPath = candidate.cliPath.trim();
155
+ if (candidate.locale === "zh" || candidate.locale === "en") next.locale = candidate.locale;
156
+ if (typeof candidate.autoBootstrapDevtools === "boolean") next.autoBootstrapDevtools = candidate.autoBootstrapDevtools;
157
+ if (typeof candidate.autoTrustProject === "boolean") next.autoTrustProject = candidate.autoTrustProject;
158
+ return next;
159
+ } catch {
160
+ return {};
161
+ }
162
+ }
163
+ async function writeCustomConfig(patch, options = {}) {
164
+ const nextConfig = {
165
+ ...options.replace ? {} : await readCustomConfig(),
166
+ ...patch
167
+ };
168
+ await fs$1.ensureDir(defaultCustomConfigDirPath);
169
+ await fs$1.writeJSON(defaultCustomConfigFilePath, nextConfig, JSON_OPTIONS);
170
+ }
171
+ /**
172
+ * @description 写入自定义 CLI 路径配置
173
+ */
174
+ async function createCustomConfig(params) {
175
+ const trimmedCliPath = params.cliPath.trim();
176
+ if (!trimmedCliPath) throw new Error("cliPath cannot be empty");
177
+ const normalizedCliPath = resolvePath(trimmedCliPath);
178
+ await writeCustomConfig({ cliPath: normalizedCliPath });
179
+ return normalizedCliPath;
180
+ }
181
+ /**
182
+ * @description 写入语言配置(zh / en)。
183
+ */
184
+ async function createLocaleConfig(locale) {
185
+ await writeCustomConfig({ locale });
186
+ return locale;
187
+ }
188
+ /**
189
+ * @description 写入开发者工具自动预热配置。
190
+ */
191
+ async function createAutoBootstrapDevtoolsConfig(value) {
192
+ await writeCustomConfig({ autoBootstrapDevtools: value });
193
+ return value;
194
+ }
195
+ /**
196
+ * @description 写入项目自动信任配置。
197
+ */
198
+ async function createAutoTrustProjectConfig(value) {
199
+ await writeCustomConfig({ autoTrustProject: value });
200
+ return value;
201
+ }
202
+ /**
203
+ * @description 删除指定配置项。
204
+ */
205
+ async function removeCustomConfigKey(key) {
206
+ const currentConfig = await readCustomConfig();
207
+ if (!(key in currentConfig)) return;
208
+ const nextConfig = { ...currentConfig };
209
+ delete nextConfig[key];
210
+ await writeCustomConfig(nextConfig, { replace: true });
211
+ }
212
+ /**
213
+ * @description 覆盖写入配置内容(会替换原内容)。
214
+ */
215
+ async function overwriteCustomConfig(config) {
216
+ const nextConfig = {};
217
+ if (typeof config.cliPath === "string" && config.cliPath.trim()) nextConfig.cliPath = resolvePath(config.cliPath.trim());
218
+ if (config.locale === "zh" || config.locale === "en") nextConfig.locale = config.locale;
219
+ if (typeof config.autoBootstrapDevtools === "boolean") nextConfig.autoBootstrapDevtools = config.autoBootstrapDevtools;
220
+ if (typeof config.autoTrustProject === "boolean") nextConfig.autoTrustProject = config.autoTrustProject;
221
+ await writeCustomConfig(nextConfig, { replace: true });
222
+ }
223
+ //#endregion
224
+ //#region src/config/resolver.ts
225
+ function isCustomConfigJson(value) {
226
+ return typeof value === "object" && value !== null;
227
+ }
228
+ /**
229
+ * @description 解析开发者工具自动预热相关配置的最终生效值。
230
+ */
231
+ function resolveDevtoolsAutomationDefaults(config) {
232
+ return {
233
+ autoBootstrapDevtools: config.autoBootstrapDevtools ?? true,
234
+ autoTrustProject: config.autoTrustProject ?? false
235
+ };
236
+ }
237
+ /**
238
+ * @description 读取并解析 CLI 配置(进程选择优先,其次用户配置与平台默认)
239
+ */
240
+ async function getConfig() {
241
+ const selectedCliPath = process.env.WEAPP_IDE_CLI_PATH?.trim();
242
+ if (await fs$1.pathExists(defaultCustomConfigFilePath)) try {
243
+ const rawConfig = await fs$1.readJSON(defaultCustomConfigFilePath);
244
+ const config = isCustomConfigJson(rawConfig) ? rawConfig : {};
245
+ const cliPath = typeof config.cliPath === "string" ? config.cliPath.trim() : "";
246
+ const locale = config.locale === "zh" || config.locale === "en" ? config.locale : void 0;
247
+ const autoBootstrapDevtools = typeof config.autoBootstrapDevtools === "boolean" ? config.autoBootstrapDevtools : void 0;
248
+ const autoTrustProject = typeof config.autoTrustProject === "boolean" ? config.autoTrustProject : void 0;
249
+ if (selectedCliPath || cliPath) {
250
+ logger.info(`全局配置文件路径:${colors.green(defaultCustomConfigFilePath)}`);
251
+ logger.info(`${selectedCliPath ? "进程指定" : "自定义"} CLI 路径:${colors.green(selectedCliPath || cliPath)}`);
252
+ return {
253
+ cliPath: selectedCliPath || cliPath,
254
+ locale,
255
+ autoBootstrapDevtools,
256
+ autoTrustProject,
257
+ source: selectedCliPath ? "environment" : "custom"
258
+ };
259
+ }
260
+ logger.warn("自定义配置文件缺少有效的 CLI 路径,将尝试使用默认路径。");
261
+ } catch (error) {
262
+ const reason = error instanceof Error ? error.message : String(error);
263
+ logger.warn(`解析自定义配置失败,${selectedCliPath ? "继续使用进程指定路径" : "将尝试使用默认路径"}。原因:${reason}`);
264
+ }
265
+ if (selectedCliPath) return {
266
+ cliPath: selectedCliPath,
267
+ source: "environment"
268
+ };
269
+ const fallbackPath = await getDefaultCliPath();
270
+ if (fallbackPath) return {
271
+ cliPath: fallbackPath,
272
+ locale: void 0,
273
+ autoBootstrapDevtools: void 0,
274
+ autoTrustProject: void 0,
275
+ source: "default"
276
+ };
277
+ return {
278
+ cliPath: "",
279
+ locale: void 0,
280
+ autoBootstrapDevtools: void 0,
281
+ autoTrustProject: void 0,
282
+ source: "missing"
283
+ };
284
+ }
285
+ /**
286
+ * @description 获取用户配置的语言偏好。
287
+ */
288
+ async function getConfiguredLocale() {
289
+ return (await readCustomConfig()).locale;
290
+ }
291
+ //#endregion
292
+ //#region src/devtoolsTarget/metadata.ts
293
+ const MAX_HEADER_BYTES = 16777216;
294
+ const MAX_PACKAGE_BYTES = 1048576;
295
+ function record(value) {
296
+ return !!value && typeof value === "object" && !Array.isArray(value);
297
+ }
298
+ async function readPackageFile(filePath) {
299
+ if ((await fs.stat(filePath)).size > MAX_PACKAGE_BYTES) throw new Error("DevTools package metadata exceeds the size limit.");
300
+ return JSON.parse(await fs.readFile(filePath, "utf8"));
301
+ }
302
+ /** 只读取归档目录和根 package.json,不执行宿主代码或解包其他文件。 */
303
+ async function readAsarPackage(asarPath) {
304
+ const file = await fs.open(asarPath, "r");
305
+ try {
306
+ const stat = await file.stat();
307
+ const prefix = Buffer.alloc(16);
308
+ if ((await file.read(prefix, 0, prefix.length, 0)).bytesRead !== prefix.length) throw new Error("Invalid DevTools ASAR header.");
309
+ const headerSize = prefix.readUInt32LE(4);
310
+ const jsonSize = prefix.readUInt32LE(12);
311
+ if (prefix.readUInt32LE(0) !== 4 || prefix.readUInt32LE(8) + 4 !== headerSize || headerSize < 8 || headerSize > MAX_HEADER_BYTES || jsonSize > headerSize - 8 || headerSize + 8 > stat.size) throw new Error("Invalid DevTools ASAR header size.");
312
+ const header = Buffer.alloc(jsonSize);
313
+ if ((await file.read(header, 0, jsonSize, 16)).bytesRead !== jsonSize) throw new Error("Truncated DevTools ASAR header.");
314
+ const parsed = JSON.parse(header.toString("utf8"));
315
+ const entry = record(parsed) && record(parsed.files) ? parsed.files["package.json"] : void 0;
316
+ if (!record(entry) || entry.link !== void 0) throw new Error("DevTools ASAR has no direct package metadata.");
317
+ if (entry.unpacked === true) return await readPackageFile(path.join(`${asarPath}.unpacked`, "package.json"));
318
+ const size = entry.size;
319
+ const offset = typeof entry.offset === "string" && /^\d+$/.test(entry.offset) ? Number(entry.offset) : NaN;
320
+ const position = 8 + headerSize + offset;
321
+ if (typeof size !== "number" || !Number.isSafeInteger(size) || size < 0 || size > MAX_PACKAGE_BYTES || !Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(position) || position + size > stat.size) throw new Error("Invalid DevTools ASAR package bounds.");
322
+ const content = Buffer.alloc(size);
323
+ if ((await file.read(content, 0, size, position)).bytesRead !== size) throw new Error("Truncated DevTools ASAR package metadata.");
324
+ return JSON.parse(content.toString("utf8"));
325
+ } finally {
326
+ await file.close();
327
+ }
328
+ }
329
+ /** 产品版本来自应用 package.json,不能使用外层 Electron 的版本。 */
330
+ async function readWechatDevtoolsMetadata(appPath) {
331
+ const value = appPath.endsWith(".asar") ? await readAsarPackage(appPath) : await readPackageFile(path.join(appPath, "package.json"));
332
+ if (!record(value)) throw new Error("Invalid DevTools package metadata.");
333
+ if (![value.name, value.productName].filter((name) => typeof name === "string").some((name) => name === "微信开发者工具" || /wechat(?:web)?devtools/i.test(name))) throw new Error("The selected application metadata does not identify WeChat DevTools.");
334
+ return {
335
+ version: typeof value.version === "string" && /^\d+\.\d+\.\d+$/.test(value.version) ? value.version : void 0,
336
+ channel: String(value.versionType) === "0" ? "stable" : String(value.versionType) === "1" ? "rc" : String(value.versionType) === "2" ? "nightly" : "unknown"
337
+ };
338
+ }
339
+ //#endregion
340
+ //#region src/devtoolsTarget/profile.ts
341
+ /** 按所选宿主的存储布局定位配置,不扫描其他安装的目录。 */
342
+ function resolveWechatDevtoolsProfileDir(appPath, options = {}) {
343
+ const platform = options.platform ?? process.platform;
344
+ const homeDir = options.homeDir ?? os.homedir();
345
+ const baseDir = platform === "darwin" ? path.join(homeDir, "Library", "Application Support", "微信开发者工具") : platform === "win32" ? path.join(options.localAppDataDir ?? process.env.LOCALAPPDATA ?? path.join(homeDir, "AppData", "Local"), "微信开发者工具", "User Data") : path.join(process.env.XDG_CONFIG_HOME ?? path.join(homeDir, ".config"), "微信开发者工具");
346
+ if (platform === "win32" && path.basename(appPath) === "package.nw") return baseDir;
347
+ return path.join(baseDir, createHash("md5").update(appPath).digest("hex"));
348
+ }
349
+ //#endregion
350
+ //#region src/utils/powershell.ts
351
+ /** 固定重定向 stdout 的 UTF-8 编码,避免默认代码页损坏进程路径和安装身份。 */
352
+ function withPowerShellUtf8Output(script) {
353
+ return `[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false); ${script}`;
354
+ }
355
+ //#endregion
356
+ //#region src/devtoolsTarget/inspection.ts
357
+ /** Windows 冷启动还需初始化 PowerShell 与 CIM;显式调用预算始终优先。 */
358
+ function resolveWechatInspectionTimeout(platform, timeout) {
359
+ if (timeout !== void 0 && (Number.isNaN(timeout) || timeout <= 0)) throw new RangeError("DevTools inspection timeout must be positive.");
360
+ const maximum = platform === "win32" ? 1e4 : 3e3;
361
+ return Math.min(timeout ?? maximum, maximum);
362
+ }
363
+ //#endregion
364
+ //#region src/devtoolsTarget/host.ts
365
+ function ownershipError(message) {
366
+ return Object.assign(new Error(message), { code: "WECHAT_DEVTOOLS_HOST_IDENTITY_MISMATCH" });
367
+ }
368
+ function normalize(value, platform) {
369
+ const normalized = value.replaceAll("\\", "/").replace(/\/$/, "");
370
+ return platform === "win32" ? normalized.toLowerCase() : normalized;
371
+ }
372
+ /** 复用宿主归属检查的安装根规则,不能把 app.asar 当作可执行文件目录。 */
373
+ function resolveWechatDevtoolsInstallationRoot(target, platform) {
374
+ const appPath = normalize(target.appPath, platform);
375
+ return platform === "darwin" ? appPath.split("/Contents/")[0] : appPath.replace(/\/(?:resources\/)?(?:app\.asar|app|package\.nw)$/, "");
376
+ }
377
+ function assertExecutable(target, executable, platform) {
378
+ const root = resolveWechatDevtoolsInstallationRoot(target, platform);
379
+ const selected = normalize(executable.trim(), platform);
380
+ if (!root || !selected.startsWith(`${root}/`)) throw ownershipError("The running WeChat DevTools host belongs to a different installation. Keep the selected installation running and retry; no host was stopped.");
381
+ }
382
+ async function runInspection(file, args, options) {
383
+ options.signal?.throwIfAborted();
384
+ try {
385
+ const timeout = resolveWechatInspectionTimeout(options.platform ?? process.platform, options.timeout);
386
+ const result = await execa(file, args, {
387
+ timeout,
388
+ cancelSignal: options.signal,
389
+ reject: false,
390
+ windowsHide: true
391
+ });
392
+ options.signal?.throwIfAborted();
393
+ return result;
394
+ } catch (cause) {
395
+ options.signal?.throwIfAborted();
396
+ throw Object.assign(ownershipError("Unable to verify the running WeChat DevTools installation."), { cause });
397
+ }
398
+ }
399
+ async function readUnixExecutable(pid, options) {
400
+ if ((options.platform ?? process.platform) === "linux") {
401
+ options.signal?.throwIfAborted();
402
+ try {
403
+ const executable = await fs.readlink(`/proc/${pid}/exe`);
404
+ options.signal?.throwIfAborted();
405
+ return executable;
406
+ } catch (cause) {
407
+ options.signal?.throwIfAborted();
408
+ if (cause.code === "ENOENT") return;
409
+ throw Object.assign(ownershipError("The running WeChat DevTools executable could not be verified through procfs."), { cause });
410
+ }
411
+ }
412
+ const result = await runInspection("ps", [
413
+ "-p",
414
+ String(pid),
415
+ "-o",
416
+ "comm="
417
+ ], options);
418
+ if (result.exitCode === 1 && !result.stdout.trim()) return;
419
+ if (result.exitCode !== 0 || !result.stdout.trim()) throw ownershipError("The running WeChat DevTools process identity could not be verified.");
420
+ return result.stdout.trim();
421
+ }
422
+ async function readWindowsProcesses(script, options) {
423
+ const result = await runInspection("powershell.exe", [
424
+ "-NoProfile",
425
+ "-NonInteractive",
426
+ "-Command",
427
+ withPowerShellUtf8Output(script)
428
+ ], options);
429
+ if (result.exitCode !== 0) throw ownershipError("Windows could not verify the WeChat DevTools process identity.");
430
+ let parsed;
431
+ try {
432
+ parsed = JSON.parse(result.stdout.trim() || "[]");
433
+ } catch (cause) {
434
+ throw Object.assign(ownershipError("Windows returned unreadable DevTools process identities."), { cause });
435
+ }
436
+ return (Array.isArray(parsed) ? parsed : [parsed]).map((value) => {
437
+ if (!value || typeof value !== "object") throw ownershipError("Windows returned an invalid DevTools process identity.");
438
+ const entry = value;
439
+ if (typeof entry.ProcessId !== "number" || !Number.isInteger(entry.ProcessId) || entry.ProcessId <= 0 || typeof entry.ExecutablePath !== "string" || !entry.ExecutablePath) throw ownershipError("Windows could not read the DevTools executable path.");
440
+ return {
441
+ pid: entry.ProcessId,
442
+ executable: entry.ExecutablePath
443
+ };
444
+ });
445
+ }
446
+ /** 启动前验证共享宿主身份;无活跃宿主可以继续,其他安装不得被隐式接管。 */
447
+ async function assertWechatDevtoolsHost(target, options = {}) {
448
+ options.signal?.throwIfAborted();
449
+ const platform = options.platform ?? process.platform;
450
+ if (platform === "win32") {
451
+ const entries = await readWindowsProcesses("@(Get-CimInstance Win32_Process -Filter \"Name='wechatdevtools.exe' OR Name='wechatwebdevtools.exe'\" | Select-Object ProcessId,ExecutablePath) | ConvertTo-Json -Compress", options);
452
+ for (const entry of entries) assertExecutable(target, entry.executable, platform);
453
+ return;
454
+ }
455
+ for (const directory of /* @__PURE__ */ new Set([path.dirname(target.profileDir), target.profileDir])) {
456
+ let link;
457
+ try {
458
+ link = await fs.readlink(path.join(directory, "SingletonLock"));
459
+ } catch (error) {
460
+ if (error.code === "ENOENT") continue;
461
+ throw ownershipError("The shared DevTools host lock could not be verified.");
462
+ }
463
+ const match = /-(\d+)$/.exec(link);
464
+ if (!match) throw ownershipError("The shared DevTools host lock has an unknown owner.");
465
+ const executable = await readUnixExecutable(Number(match[1]), options);
466
+ if (executable) assertExecutable(target, executable, platform);
467
+ }
468
+ }
469
+ /** HTTP 与 WebSocket 的显式端口均需由所选安装持有,端口号本身不是宿主身份。 */
470
+ async function assertWechatDevtoolsPort(target, port, options = {}) {
471
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("Invalid WeChat DevTools port.");
472
+ options.signal?.throwIfAborted();
473
+ const platform = options.platform ?? process.platform;
474
+ if (platform === "win32") {
475
+ const entries = await readWindowsProcesses(`$ErrorActionPreference='Stop'; @(Get-NetTCPConnection -LocalPort ${port} -State Listen | Select-Object -ExpandProperty OwningProcess -Unique | ForEach-Object { Get-CimInstance Win32_Process -Filter "ProcessId=$_" } | Select-Object ProcessId,ExecutablePath) | ConvertTo-Json -Compress`, options);
476
+ if (!entries.length) throw ownershipError("No verified WeChat DevTools listener owns the selected port.");
477
+ for (const entry of entries) assertExecutable(target, entry.executable, platform);
478
+ return;
479
+ }
480
+ const result = await runInspection("lsof", [
481
+ "-nP",
482
+ `-iTCP:${port}`,
483
+ "-sTCP:LISTEN",
484
+ "-Fp"
485
+ ], options);
486
+ const pids = [...new Set(result.stdout.split(/\r?\n/).filter((line) => /^p\d+$/.test(line)).map((line) => Number(line.slice(1))))];
487
+ if (result.exitCode !== 0 || !pids.length) throw ownershipError("No verified WeChat DevTools listener owns the selected port.");
488
+ for (const pid of pids) {
489
+ const executable = await readUnixExecutable(pid, options);
490
+ if (!executable) throw ownershipError("The selected DevTools listener exited before its identity could be verified.");
491
+ assertExecutable(target, executable, platform);
492
+ }
493
+ }
494
+ //#endregion
495
+ //#region src/devtoolsTarget/index.ts
496
+ async function resolveApplicationPath(cliPath, platform) {
497
+ const root = platform === "darwin" && path.basename(path.dirname(cliPath)) === "MacOS" ? path.join(path.dirname(path.dirname(cliPath)), "Resources") : path.dirname(cliPath);
498
+ const candidates = platform === "darwin" ? [
499
+ path.join(root, "app.asar"),
500
+ path.join(root, "app"),
501
+ path.join(root, "package.nw")
502
+ ] : [
503
+ path.join(root, "resources", "app.asar"),
504
+ path.join(root, "resources", "app"),
505
+ path.join(root, "package.nw"),
506
+ path.join(root, "resources", "package.nw")
507
+ ];
508
+ for (const candidate of candidates) try {
509
+ const stat = await fs.stat(candidate);
510
+ if (candidate.endsWith(".asar") ? stat.isFile() : stat.isDirectory()) return await fs.realpath(candidate);
511
+ } catch (error) {
512
+ if (error.code !== "ENOENT") throw error;
513
+ }
514
+ throw new Error("Cannot identify the selected WeChat DevTools installation. Select its official CLI path.");
515
+ }
516
+ /** 一次解析 CLI、应用身份及配置目录,供本次操作的所有阶段共同使用。 */
517
+ async function resolveWechatDevtoolsTarget(options = {}) {
518
+ if (options.target) {
519
+ const explicitCliPath = options.cliPath?.trim();
520
+ if (explicitCliPath && explicitCliPath !== options.target.cliPath) {
521
+ const [explicit, selected] = await Promise.all([fs.realpath(explicitCliPath), fs.realpath(options.target.cliPath)]);
522
+ if (explicit !== selected) throw Object.assign(/* @__PURE__ */ new Error("The explicit CLI path conflicts with the selected DevTools installation; no installation was changed."), { code: "WECHAT_DEVTOOLS_INSTALLATION_SELECTION_CONFLICT" });
523
+ }
524
+ return options.target;
525
+ }
526
+ const selected = options.cliPath?.trim() || (await getConfig()).cliPath;
527
+ if (!selected) throw new Error("No WeChat DevTools CLI has been selected.");
528
+ const cliPath = await fs.realpath(selected);
529
+ if (!(await fs.stat(cliPath)).isFile()) throw new Error("The selected WeChat DevTools CLI is not a file.");
530
+ const appPath = await resolveApplicationPath(cliPath, options.platform ?? process.platform);
531
+ const metadata = await readWechatDevtoolsMetadata(appPath);
532
+ return {
533
+ cliPath,
534
+ appPath,
535
+ installationId: createHash("sha256").update(appPath).digest("hex"),
536
+ profileDir: resolveWechatDevtoolsProfileDir(appPath, options),
537
+ ...metadata
538
+ };
539
+ }
540
+ //#endregion
541
+ //#region src/cli/automator/sessionStore.ts
542
+ const SESSION_DIRECTORY = path.join(os.tmpdir(), "weapp-vite-automator-sessions");
543
+ function resolveAutomatorSessionFilePath(options) {
544
+ const key = JSON.stringify([
545
+ options.installationId,
546
+ path.resolve(options.projectPath),
547
+ options.sessionId?.trim() || null,
548
+ options.port || null
549
+ ]);
550
+ return path.join(SESSION_DIRECTORY, `${createHash("sha256").update(key).digest("hex")}.json`);
551
+ }
552
+ /** 只保存所选安装的端点,不覆盖其他安装或旧版会话记录。 */
553
+ async function persistAutomatorSession(options) {
554
+ options.signal?.throwIfAborted();
555
+ const filePath = resolveAutomatorSessionFilePath(options);
556
+ const payload = {
557
+ installationId: options.installationId,
558
+ ...options.port ? { port: options.port } : {},
559
+ projectPath: path.resolve(options.projectPath),
560
+ ...options.sessionId ? { sessionId: options.sessionId } : {},
561
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
562
+ wsEndpoint: options.wsEndpoint
563
+ };
564
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
565
+ options.signal?.throwIfAborted();
566
+ await fs.writeFile(filePath, JSON.stringify(payload, null, 2), {
567
+ encoding: "utf8",
568
+ signal: options.signal
569
+ });
570
+ }
571
+ async function readSessionFile(filePath, options) {
572
+ try {
573
+ const raw = await fs.readFile(filePath, "utf8");
574
+ const payload = JSON.parse(raw);
575
+ if (!payload || typeof payload !== "object") return null;
576
+ const session = payload;
577
+ if (session.installationId !== options.installationId || session.projectPath !== path.resolve(options.projectPath)) return null;
578
+ if (options.sessionId && session.sessionId !== options.sessionId || options.port && session.port !== options.port) return null;
579
+ if (typeof session.wsEndpoint !== "string") return null;
580
+ const endpoint = new URL(session.wsEndpoint);
581
+ if (endpoint.protocol !== "ws:" || endpoint.hostname !== "127.0.0.1" || !endpoint.port || options.port && Number(endpoint.port) !== options.port) return null;
582
+ return session;
583
+ } catch {
584
+ return null;
585
+ }
586
+ }
587
+ /** 未携带安装身份、身份冲突或端口不符的缓存均不可用于连接。 */
588
+ async function readPersistedAutomatorSession(options) {
589
+ const direct = await readSessionFile(resolveAutomatorSessionFilePath(options), options);
590
+ if (direct || !options.sessionId || options.port) return direct;
591
+ try {
592
+ const entries = await fs.readdir(SESSION_DIRECTORY);
593
+ const matches = [];
594
+ for (const entry of entries) {
595
+ if (!entry.endsWith(".json")) continue;
596
+ const session = await readSessionFile(path.join(SESSION_DIRECTORY, entry), options);
597
+ if (session) matches.push(session);
598
+ }
599
+ return matches.length === 1 ? matches[0] : null;
600
+ } catch {
601
+ return null;
602
+ }
603
+ }
604
+ //#endregion
605
+ export { getDefaultCliPath as C, logger as D, colors as E, SupportedPlatformsMap as S, operatingSystemName as T, readCustomConfig as _, assertWechatDevtoolsPort as a, defaultCustomConfigFilePath as b, withPowerShellUtf8Output as c, resolveDevtoolsAutomationDefaults as d, createAutoBootstrapDevtoolsConfig as f, overwriteCustomConfig as g, createLocaleConfig as h, assertWechatDevtoolsHost as i, getConfig as l, createCustomConfig as m, readPersistedAutomatorSession as n, resolveWechatDevtoolsInstallationRoot as o, createAutoTrustProjectConfig as p, resolveWechatDevtoolsTarget as r, resolveWechatInspectionTimeout as s, persistAutomatorSession as t, getConfiguredLocale as u, removeCustomConfigKey as v, isOperatingSystemSupported as w, resolvePath as x, defaultCustomConfigDirPath as y };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weapp-ide-cli",
3
3
  "type": "module",
4
- "version": "6.1.8",
4
+ "version": "6.2.0",
5
5
  "description": "Command-line tools for automating WeChat DevTools and mini-program projects.",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "MIT",
@@ -64,20 +64,20 @@
64
64
  "registry": "https://registry.npmjs.org"
65
65
  },
66
66
  "dependencies": {
67
- "@modelcontextprotocol/server": "^2.0.0",
68
- "@weapp-core/logger": "3.1.7",
69
- "@weapp-core/shared": "3.2.6",
70
- "@weapp-vite/devtools-runtime": "0.4.23",
71
- "@weapp-vite/miniprogram-automator": "1.2.22",
72
- "cac": "^7.0.0",
73
- "execa": "10.0.1",
67
+ "@modelcontextprotocol/server": "^2.3.1",
68
+ "@weapp-core/logger": "3.1.9",
69
+ "@weapp-core/shared": "3.2.8",
70
+ "@weapp-vite/devtools-runtime": "0.5.0",
71
+ "@weapp-vite/miniprogram-automator": "1.3.0",
72
+ "cac": "^7.0.1",
73
+ "execa": "10.1.0",
74
74
  "pathe": "^2.0.3",
75
75
  "pixelmatch": "^7.2.0",
76
76
  "pngjs": "^7.0.0",
77
77
  "zod": "^4.6.5"
78
78
  },
79
79
  "devDependencies": {
80
- "@types/node": "^26.6.2"
80
+ "@types/node": "^26.6.4"
81
81
  },
82
82
  "tsd": {
83
83
  "compilerOptions": {
@@ -91,11 +91,12 @@
91
91
  "release": "node scripts/release.js",
92
92
  "test:dev": "vitest",
93
93
  "test": "vitest run",
94
- "test:types": "pnpm build && tsd --files \"test-d/**/*.test-d.ts\"",
94
+ "test:types": "pnpm build && pnpm run test:types:check",
95
+ "test:types:check": "tsd --files \"test-d/**/*.test-d.ts\"",
95
96
  "typecheck": "tsc --noEmit",
96
97
  "weapp": "tsx src/cli.ts",
97
98
  "debug": "tsx src/cli.ts config",
98
99
  "raw": "node bin/weapp.js",
99
- "sync": "cnpm sync weapp-ide-cli"
100
+ "sync": "repo release sync-npmmirror --package weapp-ide-cli"
100
101
  }
101
102
  }