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.
@@ -1,124 +1,15 @@
1
1
  import path from "node:path";
2
+ import { mutateLease } from "@weapp-vite/devtools-runtime";
2
3
  import { execa } from "execa";
3
- import { createHash } from "node:crypto";
4
4
  import fs from "node:fs/promises";
5
5
  import process from "node:process";
6
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
7
  import path$1 from "pathe";
8
+ import os from "node:os";
9
+ import { AsyncLocalStorage } from "node:async_hooks";
10
+ import baseLogger, { colors } from "@weapp-core/logger";
11
+ import { createHash } from "node:crypto";
10
12
  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
13
  //#region src/utils/path.ts
123
14
  /**
124
15
  * @description 解析为绝对路径(基于当前工作目录)
@@ -221,72 +112,124 @@ async function overwriteCustomConfig(config) {
221
112
  await writeCustomConfig(nextConfig, { replace: true });
222
113
  }
223
114
  //#endregion
224
- //#region src/config/resolver.ts
225
- function isCustomConfigJson(value) {
226
- return typeof value === "object" && value !== null;
227
- }
115
+ //#region src/devtoolsTarget/context.ts
116
+ const wechatDevtoolsSelectionContext = new AsyncLocalStorage();
117
+ //#endregion
118
+ //#region src/logger.ts
119
+ const logger = baseLogger.create({
120
+ stdout: process.stderr,
121
+ stderr: process.stderr
122
+ });
123
+ //#endregion
124
+ //#region src/runtime/platform.ts
228
125
  /**
229
- * @description 解析开发者工具自动预热相关配置的最终生效值。
126
+ * @description 官方微信开发者工具只支持 Windows、macOS,Linux 只有社区版。
127
+ * https://github.com/msojocs/wechat-web-devtools-linux
230
128
  */
231
- function resolveDevtoolsAutomationDefaults(config) {
232
- return {
233
- autoBootstrapDevtools: config.autoBootstrapDevtools ?? true,
234
- autoTrustProject: config.autoTrustProject ?? false
235
- };
129
+ const SupportedPlatformsMap = {
130
+ Windows_NT: "Windows_NT",
131
+ Darwin: "Darwin",
132
+ Linux: "Linux"
133
+ };
134
+ /**
135
+ * @description 判断当前系统是否支持微信开发者工具。
136
+ */
137
+ function isOperatingSystemSupported(osName = os.type()) {
138
+ return osName === SupportedPlatformsMap.Windows_NT || osName === SupportedPlatformsMap.Darwin || osName === SupportedPlatformsMap.Linux;
236
139
  }
237
140
  /**
238
- * @description 读取并解析 CLI 配置(进程选择优先,其次用户配置与平台默认)
141
+ * @description 当前系统名称。
239
142
  */
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
- };
143
+ const operatingSystemName = os.type();
144
+ async function findFirstExistingPath(candidates) {
145
+ for (const candidate of candidates) {
146
+ if (!candidate) continue;
147
+ if (await fs$1.pathExists(candidate)) return candidate;
148
+ }
149
+ }
150
+ async function getFirstBinaryPath(command) {
151
+ const pathDirs = (process.env.PATH || "").split(path$1.delimiter);
152
+ for (const dir of pathDirs) {
153
+ const fullPath = path$1.join(dir, command);
154
+ try {
155
+ await fs$1.access(fullPath, fs$1.constants.X_OK);
156
+ return fullPath;
157
+ } catch {
158
+ continue;
259
159
  }
260
- logger.warn("自定义配置文件缺少有效的 CLI 路径,将尝试使用默认路径。");
261
- } catch (error) {
262
- const reason = error instanceof Error ? error.message : String(error);
263
- logger.warn(`解析自定义配置失败,${selectedCliPath ? "继续使用进程指定路径" : "将尝试使用默认路径"}。原因:${reason}`);
264
160
  }
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"
161
+ }
162
+ function createLinuxCliResolver() {
163
+ let resolvedPath;
164
+ let attempted = false;
165
+ let pending = null;
166
+ return async () => {
167
+ if (attempted) return resolvedPath;
168
+ if (!pending) pending = (async () => {
169
+ try {
170
+ const envPath = await getFirstBinaryPath("wechat-devtools-cli");
171
+ if (envPath) resolvedPath = envPath;
172
+ } catch (error) {
173
+ const reason = error instanceof Error ? error.message : String(error);
174
+ logger.warn(`获取 Linux wechat-devtools-cli 路径失败:${reason}`);
175
+ } finally {
176
+ attempted = true;
177
+ }
178
+ return resolvedPath;
179
+ })();
180
+ return pending;
276
181
  };
277
- return {
278
- cliPath: "",
279
- locale: void 0,
280
- autoBootstrapDevtools: void 0,
281
- autoTrustProject: void 0,
282
- source: "missing"
182
+ }
183
+ function createWindowsCliResolver() {
184
+ let resolvedPath;
185
+ let attempted = false;
186
+ let pending = null;
187
+ return async () => {
188
+ if (attempted) return resolvedPath;
189
+ if (!pending) pending = (async () => {
190
+ const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
191
+ const programFiles = process.env.ProgramFiles || "C:\\Program Files";
192
+ const localAppData = process.env.LOCALAPPDATA || path$1.join(os.homedir(), "AppData", "Local");
193
+ const candidates = [
194
+ path$1.join(programFilesX86, "Tencent", "微信web开发者工具", "cli.bat"),
195
+ path$1.join(programFiles, "Tencent", "微信web开发者工具", "cli.bat"),
196
+ path$1.join(localAppData, "Programs", "微信开发者工具", "cli.bat"),
197
+ path$1.join(localAppData, "Tencent", "微信web开发者工具", "cli.bat")
198
+ ];
199
+ try {
200
+ resolvedPath = await findFirstExistingPath(candidates);
201
+ } catch (error) {
202
+ const reason = error instanceof Error ? error.message : String(error);
203
+ logger.warn(`获取 Windows 微信开发者工具 CLI 路径失败:${reason}`);
204
+ } finally {
205
+ attempted = true;
206
+ }
207
+ return resolvedPath;
208
+ })();
209
+ return pending;
283
210
  };
284
211
  }
212
+ const linuxCliResolver = createLinuxCliResolver();
213
+ const windowsCliResolver = createWindowsCliResolver();
214
+ const DARWIN_DEFAULT_CLI = "/Applications/wechatwebdevtools.app/Contents/MacOS/cli";
215
+ const cliPathResolvers = {
216
+ [SupportedPlatformsMap.Windows_NT]: windowsCliResolver,
217
+ [SupportedPlatformsMap.Darwin]: async () => DARWIN_DEFAULT_CLI,
218
+ [SupportedPlatformsMap.Linux]: linuxCliResolver
219
+ };
285
220
  /**
286
- * @description 获取用户配置的语言偏好。
221
+ * @description 获取默认 CLI 路径(按系统)。
287
222
  */
288
- async function getConfiguredLocale() {
289
- return (await readCustomConfig()).locale;
223
+ async function getDefaultCliPath(targetOs = operatingSystemName) {
224
+ if (!isOperatingSystemSupported(targetOs)) return;
225
+ const resolver = cliPathResolvers[targetOs];
226
+ return await resolver();
227
+ }
228
+ //#endregion
229
+ //#region src/utils/powershell.ts
230
+ /** 固定重定向 stdout 的 UTF-8 编码,避免默认代码页损坏进程路径和安装身份。 */
231
+ function withPowerShellUtf8Output(script) {
232
+ return `[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false); ${script}`;
290
233
  }
291
234
  //#endregion
292
235
  //#region src/devtoolsTarget/metadata.ts
@@ -330,9 +273,9 @@ async function readAsarPackage(asarPath) {
330
273
  async function readWechatDevtoolsMetadata(appPath) {
331
274
  const value = appPath.endsWith(".asar") ? await readAsarPackage(appPath) : await readPackageFile(path.join(appPath, "package.json"));
332
275
  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.");
276
+ if (![value.name, value.productName].filter((name) => typeof name === "string").some((name) => name === "微信开发者工具" || /wechat(?:web)?devtools/i.test(name))) throw Object.assign(/* @__PURE__ */ new Error("The selected application metadata does not identify WeChat DevTools."), { code: "WECHAT_DEVTOOLS_UNRELATED_APPLICATION" });
334
277
  return {
335
- version: typeof value.version === "string" && /^\d+\.\d+\.\d+$/.test(value.version) ? value.version : void 0,
278
+ version: typeof value.version === "string" && /^\d+(?:\.\d+){2,}$/.test(value.version) ? value.version : void 0,
336
279
  channel: String(value.versionType) === "0" ? "stable" : String(value.versionType) === "1" ? "rc" : String(value.versionType) === "2" ? "nightly" : "unknown"
337
280
  };
338
281
  }
@@ -347,17 +290,203 @@ function resolveWechatDevtoolsProfileDir(appPath, options = {}) {
347
290
  return path.join(baseDir, createHash("md5").update(appPath).digest("hex"));
348
291
  }
349
292
  //#endregion
350
- //#region src/utils/powershell.ts
351
- /** 固定重定向 stdout 的 UTF-8 编码,避免默认代码页损坏进程路径和安装身份。 */
352
- function withPowerShellUtf8Output(script) {
353
- return `[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false); ${script}`;
293
+ //#region src/devtoolsTarget/installation.ts
294
+ async function resolveApplicationPath(cliPath, platform) {
295
+ const root = platform === "darwin" && path.basename(path.dirname(cliPath)) === "MacOS" ? path.join(path.dirname(path.dirname(cliPath)), "Resources") : path.dirname(cliPath);
296
+ const candidates = platform === "darwin" ? [
297
+ path.join(root, "app.asar"),
298
+ path.join(root, "app"),
299
+ path.join(root, "package.nw")
300
+ ] : [
301
+ path.join(root, "resources", "app.asar"),
302
+ path.join(root, "resources", "app"),
303
+ path.join(root, "package.nw"),
304
+ path.join(root, "resources", "package.nw")
305
+ ];
306
+ for (const candidate of candidates) try {
307
+ const stat = await fs.stat(candidate);
308
+ if (candidate.endsWith(".asar") ? stat.isFile() : stat.isDirectory()) return await fs.realpath(candidate);
309
+ } catch (error) {
310
+ if (error.code !== "ENOENT") throw error;
311
+ }
312
+ throw new Error("Cannot identify the selected WeChat DevTools installation. Select its official CLI path.");
313
+ }
314
+ /** 只读取明确路径的安装元数据,不读取配置或启动宿主。 */
315
+ async function inspectWechatDevtoolsInstallation(selected, options = {}) {
316
+ const cliPath = await fs.realpath(selected);
317
+ if (!(await fs.stat(cliPath)).isFile()) throw new Error("The selected WeChat DevTools CLI is not a file.");
318
+ const appPath = await resolveApplicationPath(cliPath, options.platform ?? process.platform);
319
+ return {
320
+ cliPath,
321
+ appPath,
322
+ installationId: createHash("sha256").update(appPath).digest("hex"),
323
+ profileDir: resolveWechatDevtoolsProfileDir(appPath, options),
324
+ ...await readWechatDevtoolsMetadata(appPath)
325
+ };
326
+ }
327
+ //#endregion
328
+ //#region src/devtoolsTarget/registry.ts
329
+ const defaultWechatDevtoolsRegistryPath = path.join(defaultCustomConfigDirPath, "installations.json");
330
+ /** 登记只保存路径;安装渠道和版本每次从实际应用重新读取。 */
331
+ async function readWechatDevtoolsRegistry(registryPath = defaultWechatDevtoolsRegistryPath) {
332
+ try {
333
+ const value = JSON.parse(await fs.readFile(registryPath, "utf8"));
334
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string" && entry.trim())) throw new Error("Invalid DevTools installation registry: expected CLI paths.");
335
+ return value;
336
+ } catch (error) {
337
+ if (error.code === "ENOENT") return [];
338
+ throw error;
339
+ }
340
+ }
341
+ async function updateRegistry(registryPath, update) {
342
+ await fs.mkdir(path.dirname(registryPath), { recursive: true });
343
+ await mutateLease(`${registryPath}.lock`, async () => {
344
+ const paths = update(await readWechatDevtoolsRegistry(registryPath));
345
+ const temporary = `${registryPath}.${process.pid}.tmp`;
346
+ try {
347
+ await fs.writeFile(temporary, `${JSON.stringify(paths, null, 2)}\n`);
348
+ await fs.rename(temporary, registryPath);
349
+ } finally {
350
+ await fs.rm(temporary, { force: true });
351
+ }
352
+ });
353
+ }
354
+ /** 校验安装后登记真实 CLI 路径,不更改默认选择或登录数据。 */
355
+ async function registerWechatDevtoolsInstallation(cliPath, registryPath = defaultWechatDevtoolsRegistryPath) {
356
+ const target = await inspectWechatDevtoolsInstallation(cliPath);
357
+ await updateRegistry(registryPath, (paths) => [.../* @__PURE__ */ new Set([...paths, target.cliPath])]);
358
+ return target;
359
+ }
360
+ /** 允许移除已经失效的登记;不会删除应用或用户配置。 */
361
+ async function unregisterWechatDevtoolsInstallation(cliPath, registryPath = defaultWechatDevtoolsRegistryPath) {
362
+ const selected = await fs.realpath(cliPath).catch(() => path.resolve(cliPath));
363
+ await updateRegistry(registryPath, (paths) => paths.filter((entry) => entry !== selected && entry !== path.resolve(cliPath)));
364
+ }
365
+ //#endregion
366
+ //#region src/devtoolsTarget/discovery.ts
367
+ /** 产品版本按数字段比较,避免日期版本的字符串排序漂移。 */
368
+ function compareWechatDevtoolsVersions(a, b) {
369
+ const left = a.split(".").map(Number);
370
+ const right = b.split(".").map(Number);
371
+ for (let index = 0; index < Math.max(left.length, right.length); index++) {
372
+ const difference = (left[index] ?? 0) - (right[index] ?? 0);
373
+ if (difference) return difference;
374
+ }
375
+ return 0;
376
+ }
377
+ async function directories(root) {
378
+ try {
379
+ return await fs.readdir(root, { withFileTypes: true });
380
+ } catch (error) {
381
+ if (["ENOENT", "EACCES"].includes(error.code ?? "")) return [];
382
+ throw error;
383
+ }
384
+ }
385
+ async function windowsRegistryRoots() {
386
+ const result = await execa("powershell.exe", [
387
+ "-NoProfile",
388
+ "-NonInteractive",
389
+ "-Command",
390
+ withPowerShellUtf8Output("$ErrorActionPreference='Stop'; @('HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*','HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*','HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*') | ForEach-Object { Get-ItemProperty $_ -ErrorAction SilentlyContinue } | Where-Object { $_.DisplayName -match '微信.*开发者|wechat.*devtools' -and $_.InstallLocation } | Select-Object -ExpandProperty InstallLocation | ConvertTo-Json -Compress")
391
+ ], {
392
+ reject: false,
393
+ windowsHide: true,
394
+ timeout: 1e4
395
+ });
396
+ if (result.exitCode !== 0) throw new Error("Unable to inspect Windows DevTools installation registry.");
397
+ const parsed = JSON.parse(result.stdout.trim() || "[]");
398
+ return (Array.isArray(parsed) ? parsed : [parsed]).filter((value) => typeof value === "string");
399
+ }
400
+ /** 只扫描已知安装根,不递归遍历磁盘,也不执行候选应用。 */
401
+ async function listWechatDevtoolsInstallations(options = {}) {
402
+ const platform = options.platform ?? process.platform;
403
+ const home = options.homeDir ?? os.homedir();
404
+ const registered = await readWechatDevtoolsRegistry(options.registryPath);
405
+ const config = await readCustomConfig();
406
+ const candidates = new Set([
407
+ ...registered,
408
+ config.cliPath,
409
+ process.env.WEAPP_IDE_CLI_PATH
410
+ ].filter((value) => !!value));
411
+ const fallback = options.searchRoots || platform === "linux" ? void 0 : await getDefaultCliPath(platform === "darwin" ? "Darwin" : "Windows_NT");
412
+ if (fallback) candidates.add(fallback);
413
+ const roots = options.searchRoots ?? (platform === "darwin" ? ["/Applications", path.join(home, "Applications")] : platform === "win32" ? [
414
+ process.env.ProgramFiles ?? "C:\\Program Files",
415
+ process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)",
416
+ options.localAppDataDir ?? process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local")
417
+ ] : []);
418
+ if (platform === "win32" && !options.searchRoots) for (const root of await windowsRegistryRoots().catch(() => [])) candidates.add(path.join(root, "cli.bat"));
419
+ const explicitCandidates = new Set(candidates);
420
+ for (const root of roots) {
421
+ const entries = await directories(root);
422
+ if (platform === "darwin") for (const entry of entries.filter((entry) => /\.app$/i.test(entry.name))) {
423
+ const candidate = path.join(root, entry.name, "Contents", "MacOS", "cli");
424
+ if (await fs.stat(candidate).then((stat) => stat.isFile(), () => false)) candidates.add(candidate);
425
+ }
426
+ else if (platform === "win32") for (const directory of [root, ...entries.filter((entry) => entry.isDirectory()).map((entry) => path.join(root, entry.name))]) for (const candidateRoot of [directory, ...(await directories(directory)).filter((entry) => entry.isDirectory()).map((entry) => path.join(directory, entry.name))]) {
427
+ const candidate = path.join(candidateRoot, "cli.bat");
428
+ if (await fs.stat(candidate).then((stat) => stat.isFile(), () => false)) candidates.add(candidate);
429
+ }
430
+ }
431
+ const seen = /* @__PURE__ */ new Set();
432
+ const result = [];
433
+ for (const cliPath of candidates) try {
434
+ const target = await inspectWechatDevtoolsInstallation(cliPath, options);
435
+ if (!seen.has(target.installationId)) {
436
+ seen.add(target.installationId);
437
+ result.push({
438
+ cliPath: target.cliPath,
439
+ target
440
+ });
441
+ }
442
+ } catch (error) {
443
+ if (!explicitCandidates.has(cliPath)) continue;
444
+ if (error.code === "WECHAT_DEVTOOLS_UNRELATED_APPLICATION" && !registered.includes(cliPath) && cliPath !== config.cliPath && cliPath !== process.env.WEAPP_IDE_CLI_PATH) continue;
445
+ result.push({
446
+ cliPath,
447
+ error: error instanceof Error ? error.message : String(error)
448
+ });
449
+ }
450
+ return result.sort((a, b) => compareWechatDevtoolsVersions(b.target?.version ?? "0", a.target?.version ?? "0") || a.cliPath.localeCompare(b.cliPath));
451
+ }
452
+ /** 渠道必须显式 opt-in;找不到或存在同版本歧义时不静默换安装。 */
453
+ async function selectInstalledWechatDevtools(options = {}) {
454
+ const channel = options.devtoolsChannel ?? (options.devtoolsVersion ? void 0 : "stable");
455
+ const matches = (await listWechatDevtoolsInstallations(options)).flatMap((entry) => entry.target?.version && (!channel || entry.target.channel === channel) && (!options.devtoolsVersion || entry.target.version === options.devtoolsVersion) ? [entry.target] : []);
456
+ if (!matches.length) throw Object.assign(/* @__PURE__ */ new Error(`No installed WeChat DevTools matches ${channel ?? "any channel"}${options.devtoolsVersion ? `@${options.devtoolsVersion}` : ""}. Register its CLI with weapp devtools add <path-to-cli>.`), { code: "WECHAT_DEVTOOLS_INSTALLATION_NOT_FOUND" });
457
+ if (matches.filter((target) => compareWechatDevtoolsVersions(target.version, matches[0].version) === 0).length !== 1) throw Object.assign(/* @__PURE__ */ new Error("Multiple DevTools installations match the selected version. Specify --cli-path."), { code: "WECHAT_DEVTOOLS_INSTALLATION_AMBIGUOUS" });
458
+ return matches[0];
459
+ }
460
+ //#endregion
461
+ //#region src/devtoolsTarget/official.ts
462
+ const WECHAT_DEVTOOLS_OFFICIAL_CHANNELS_URL = "https://devtools.wxqcloud.qq.com.cn/WechatWebDev/nightly/versions/config.json";
463
+ /** 官方查询只提供版本证据,默认选择不依赖网络可用性。 */
464
+ async function readOfficialWechatDevtoolsVersion(channel = "stable") {
465
+ const queriedAt = (/* @__PURE__ */ new Date()).toISOString();
466
+ const response = await fetch(WECHAT_DEVTOOLS_OFFICIAL_CHANNELS_URL, { signal: AbortSignal.timeout(15e3) });
467
+ if (!response.ok) throw new Error(`Unable to query official DevTools versions: HTTP ${response.status}`);
468
+ const value = await response.json();
469
+ const entry = (value && typeof value === "object" && "channels" in value && Array.isArray(value.channels) ? value.channels : []).find((entry) => entry && typeof entry === "object" && "id" in entry && entry.id === channel);
470
+ if (!entry || typeof entry !== "object" || !("version" in entry) || typeof entry.version !== "string" || !/^\d+(?:\.\d+){2,}$/.test(entry.version)) throw new Error(`Official DevTools metadata has no valid ${channel} version.`);
471
+ return {
472
+ version: entry.version,
473
+ source: WECHAT_DEVTOOLS_OFFICIAL_CHANNELS_URL,
474
+ queriedAt
475
+ };
476
+ }
477
+ /** 仅显式严格模式要求所选安装匹配官方渠道最新版本。 */
478
+ async function assertOfficialWechatDevtoolsVersion(target) {
479
+ if (!target.channel || target.channel === "unknown") throw new Error("Cannot verify the official latest version for an unknown DevTools channel.");
480
+ const official = await readOfficialWechatDevtoolsVersion(target.channel);
481
+ if (target.version !== official.version) throw new Error(`DEVTOOLS_OFFICIAL_LATEST_REQUIRED: selected ${target.version}, official ${target.channel}@${official.version}.`);
482
+ return official;
354
483
  }
355
484
  //#endregion
356
485
  //#region src/devtoolsTarget/inspection.ts
357
- /** Windows 冷启动还需初始化 PowerShell 与 CIM;显式调用预算始终优先。 */
486
+ /** Windows 查询预算包含 PowerShell 冷启动及内核/CIM 初始化;显式较小预算始终优先。 */
358
487
  function resolveWechatInspectionTimeout(platform, timeout) {
359
488
  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;
489
+ const maximum = platform === "win32" ? 3e4 : 3e3;
361
490
  return Math.min(timeout ?? maximum, maximum);
362
491
  }
363
492
  //#endregion
@@ -366,8 +495,7 @@ function ownershipError(message) {
366
495
  return Object.assign(new Error(message), { code: "WECHAT_DEVTOOLS_HOST_IDENTITY_MISMATCH" });
367
496
  }
368
497
  function normalize(value, platform) {
369
- const normalized = value.replaceAll("\\", "/").replace(/\/$/, "");
370
- return platform === "win32" ? normalized.toLowerCase() : normalized;
498
+ return (platform === "win32" ? value.replaceAll("\\", "/").toLowerCase() : value).replace(/\/$/, "");
371
499
  }
372
500
  /** 复用宿主归属检查的安装根规则,不能把 app.asar 当作可执行文件目录。 */
373
501
  function resolveWechatDevtoolsInstallationRoot(target, platform) {
@@ -493,53 +621,76 @@ async function assertWechatDevtoolsPort(target, port, options = {}) {
493
621
  }
494
622
  //#endregion
495
623
  //#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.");
624
+ function conflict(message) {
625
+ throw Object.assign(new Error(message), { code: "WECHAT_DEVTOOLS_INSTALLATION_SELECTION_CONFLICT" });
626
+ }
627
+ function validateWechatDevtoolsSelection(options) {
628
+ if (options.cliPath !== void 0 && !options.cliPath.trim()) throw new Error("cliPath must not be empty.");
629
+ if (options.devtoolsChannel !== void 0 && ![
630
+ "stable",
631
+ "rc",
632
+ "nightly"
633
+ ].includes(options.devtoolsChannel)) throw new Error("devtoolsChannel must be stable, rc or nightly.");
634
+ if (options.devtoolsVersion !== void 0 && !/^\d+(?:\.\d+){2,}$/.test(options.devtoolsVersion)) throw new Error("devtoolsVersion must be a complete DevTools product version.");
635
+ }
636
+ async function assertSelection(target, options) {
637
+ if (options.cliPath?.trim() && await fs.realpath(options.cliPath.trim()) !== await fs.realpath(target.cliPath)) conflict("The explicit CLI path conflicts with the selected DevTools installation; no installation was changed.");
638
+ if (options.devtoolsChannel && options.devtoolsChannel !== target.channel) conflict("The requested DevTools channel conflicts with the selected installation.");
639
+ if (options.devtoolsVersion && options.devtoolsVersion !== target.version) conflict("The requested DevTools version conflicts with the selected installation.");
640
+ if (options.requireOfficialLatest) await assertOfficialWechatDevtoolsVersion(target);
641
+ return target;
642
+ }
643
+ function readWechatDevtoolsSelectionEnvironment(env = process.env) {
644
+ const options = {
645
+ cliPath: env.WEAPP_IDE_CLI_PATH?.trim() || void 0,
646
+ devtoolsChannel: env.WEAPP_IDE_DEVTOOLS_CHANNEL?.trim() || void 0,
647
+ devtoolsVersion: env.WEAPP_IDE_DEVTOOLS_VERSION?.trim() || void 0,
648
+ requireOfficialLatest: env.WEAPP_IDE_REQUIRE_OFFICIAL_LATEST === "1"
649
+ };
650
+ validateWechatDevtoolsSelection(options);
651
+ return options;
515
652
  }
516
- /** 一次解析 CLI、应用身份及配置目录,供本次操作的所有阶段共同使用。 */
653
+ /** 每次操作只解析一次安装,显式选择覆盖继承的配置但不写回全局设置。 */
517
654
  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;
655
+ validateWechatDevtoolsSelection(options);
656
+ const active = wechatDevtoolsSelectionContext.getStore();
657
+ if (active && options.target && active.installationId !== options.target.installationId) conflict("The fixed target conflicts with the active operation installation.");
658
+ const frozen = active ?? options.target;
659
+ if (frozen) return await assertSelection(frozen, options);
660
+ let selection = { ...options };
661
+ if (!options.cliPath?.trim() && !options.devtoolsChannel && !options.devtoolsVersion) {
662
+ selection = readWechatDevtoolsSelectionEnvironment();
663
+ selection.requireOfficialLatest = options.requireOfficialLatest ?? selection.requireOfficialLatest;
664
+ if (!selection.cliPath && !selection.devtoolsChannel && !selection.devtoolsVersion) selection = {
665
+ ...selection,
666
+ cliPath: (await readCustomConfig()).cliPath
667
+ };
525
668
  }
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
- };
669
+ selection.requireOfficialLatest ??= process.env.WEAPP_IDE_REQUIRE_OFFICIAL_LATEST === "1";
670
+ return await assertSelection(selection.cliPath?.trim() ? await inspectWechatDevtoolsInstallation(selection.cliPath.trim(), options) : await selectInstalledWechatDevtools({
671
+ ...options,
672
+ ...selection
673
+ }), selection);
674
+ }
675
+ /** 命令的所有异步阶段复用同一安装,彼此并发的调用不会污染环境变量。 */
676
+ async function withWechatDevtoolsSelection(options, runner) {
677
+ const target = await resolveWechatDevtoolsTarget(options);
678
+ return await wechatDevtoolsSelectionContext.run(target, () => runner(target));
679
+ }
680
+ /** 读取当前操作固定的安装;没有活动选择时不触发发现或启动。 */
681
+ function getActiveWechatDevtoolsTarget() {
682
+ return wechatDevtoolsSelectionContext.getStore();
539
683
  }
540
684
  //#endregion
685
+ //#region src/devtoolsProjectOwnership/journal/constants.ts
686
+ /** 受管项目日志的环境变量名称;基础设施预检可在构建包之前读取。 */
687
+ const MANAGED_PROJECT_JOURNAL_ENV = "WEAPP_IDE_MANAGED_PROJECT_JOURNAL";
688
+ /** 仅供显式受管任务声明双项目窗口验收;不允许无限扩大并行窗口。 */
689
+ const MANAGED_PROJECT_MAX_WINDOWS_ENV = "WEAPP_IDE_MANAGED_PROJECT_MAX_WINDOWS";
690
+ //#endregion
541
691
  //#region src/cli/automator/sessionStore.ts
542
692
  const SESSION_DIRECTORY = path.join(os.tmpdir(), "weapp-vite-automator-sessions");
693
+ /** 内部工具复用会话键;不从包入口公开缓存路径契约。 */
543
694
  function resolveAutomatorSessionFilePath(options) {
544
695
  const key = JSON.stringify([
545
696
  options.installationId,
@@ -602,4 +753,129 @@ async function readPersistedAutomatorSession(options) {
602
753
  }
603
754
  }
604
755
  //#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 };
756
+ //#region src/cli/automatorProject.ts
757
+ const WRAPPER_ROOT = path.join(os.tmpdir(), "weapp-ide-cli-automator-projects");
758
+ function normalizeProjectRelativeRoot(rawRoot) {
759
+ if (typeof rawRoot !== "string") return;
760
+ const normalized = rawRoot.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
761
+ if (!normalized || normalized === ".") return;
762
+ if (normalized.split("/").includes("..")) return;
763
+ return normalized;
764
+ }
765
+ async function pathExists(filePath) {
766
+ try {
767
+ await fs.access(filePath);
768
+ return true;
769
+ } catch {
770
+ return false;
771
+ }
772
+ }
773
+ async function readJsonObject(filePath) {
774
+ try {
775
+ const raw = await fs.readFile(filePath, "utf8");
776
+ const value = JSON.parse(raw);
777
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
778
+ } catch {
779
+ return;
780
+ }
781
+ }
782
+ async function writeJsonObject(filePath, value) {
783
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
784
+ await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
785
+ }
786
+ async function copyProjectRoot(sourceProjectPath, wrapperProjectPath, relativeRoot) {
787
+ const sourcePath = path.join(sourceProjectPath, relativeRoot);
788
+ if (!await pathExists(sourcePath)) return;
789
+ await fs.cp(sourcePath, path.join(wrapperProjectPath, relativeRoot), {
790
+ dereference: true,
791
+ force: true,
792
+ recursive: true
793
+ });
794
+ }
795
+ function createWrapperProjectConfig(source, patch) {
796
+ const { miniprogramRoot: _miniprogramRoot, qcloudRoot: _qcloudRoot, srcMiniprogramRoot: _srcMiniprogramRoot, ...rest } = source;
797
+ const sourceSetting = source.setting && typeof source.setting === "object" ? source.setting : {};
798
+ const patchSetting = patch.setting && typeof patch.setting === "object" ? patch.setting : {};
799
+ return {
800
+ ...rest,
801
+ ...patch,
802
+ setting: {
803
+ ...sourceSetting,
804
+ ...patchSetting,
805
+ packNpmManually: false,
806
+ packNpmRelationList: []
807
+ }
808
+ };
809
+ }
810
+ async function ensureWrapperAppConfig(wrapperProjectPath) {
811
+ const appConfigPath = path.join(wrapperProjectPath, "app.json");
812
+ const appConfig = await readJsonObject(appConfigPath);
813
+ if (!appConfig) return;
814
+ await writeJsonObject(appConfigPath, {
815
+ ...appConfig,
816
+ subPackages: Array.isArray(appConfig.subPackages) ? appConfig.subPackages : []
817
+ });
818
+ }
819
+ /** 只读解析布局,受管路径必须先通过窗口预算再创建快照。 */
820
+ async function readAutomatorProjectPlan(projectPath) {
821
+ const sourceProjectPath = path.resolve(projectPath);
822
+ const projectConfig = await readJsonObject(path.join(sourceProjectPath, "project.config.json"));
823
+ const miniprogramRoot = normalizeProjectRelativeRoot(projectConfig?.miniprogramRoot);
824
+ if (!miniprogramRoot) return;
825
+ const distRoot = path.join(sourceProjectPath, miniprogramRoot);
826
+ if (!await pathExists(path.join(distRoot, "app.json"))) return;
827
+ return {
828
+ sourceProjectPath,
829
+ distRoot,
830
+ projectConfig: projectConfig ?? {}
831
+ };
832
+ }
833
+ /** 仅转换已分配快照的布局;目录所有权与删除由调用方持有。 */
834
+ async function populateAutomatorProjectSnapshot(plan, wrapperProjectPath) {
835
+ const { sourceProjectPath, distRoot, projectConfig: sourceProjectConfig } = plan;
836
+ await fs.cp(distRoot, wrapperProjectPath, {
837
+ dereference: true,
838
+ force: true,
839
+ recursive: true
840
+ });
841
+ const pluginRoot = normalizeProjectRelativeRoot(sourceProjectConfig.pluginRoot);
842
+ if (pluginRoot) await copyProjectRoot(sourceProjectPath, wrapperProjectPath, pluginRoot);
843
+ const projectPrivateConfig = await readJsonObject(path.join(sourceProjectPath, "project.private.config.json")) ?? {};
844
+ await writeJsonObject(path.join(wrapperProjectPath, "project.config.json"), createWrapperProjectConfig(sourceProjectConfig, {
845
+ ...projectPrivateConfig,
846
+ compileType: sourceProjectConfig.compileType === "plugin" ? "plugin" : "miniprogram",
847
+ miniprogramRoot: "./",
848
+ srcMiniprogramRoot: "./"
849
+ }));
850
+ await fs.rm(path.join(wrapperProjectPath, "project.private.config.json"), { force: true });
851
+ await ensureWrapperAppConfig(wrapperProjectPath);
852
+ }
853
+ /** 为普通非受管 automator 保留既有稳定项目路径合同。 */
854
+ async function resolveAutomatorProjectPath(projectPath) {
855
+ const sourceProjectPath = path.resolve(projectPath);
856
+ const plan = await readAutomatorProjectPlan(sourceProjectPath);
857
+ if (!plan) return {
858
+ projectPath: sourceProjectPath,
859
+ sourceProjectPath
860
+ };
861
+ const wrapperHash = createHash("sha1").update(sourceProjectPath).update("\0").update(path.resolve(plan.distRoot)).digest("hex").slice(0, 16);
862
+ const wrapperProjectPath = path.join(WRAPPER_ROOT, wrapperHash);
863
+ await fs.rm(wrapperProjectPath, {
864
+ recursive: true,
865
+ force: true
866
+ });
867
+ await fs.mkdir(wrapperProjectPath, { recursive: true });
868
+ await populateAutomatorProjectSnapshot(plan, wrapperProjectPath);
869
+ return {
870
+ cleanup: async () => {
871
+ await fs.rm(wrapperProjectPath, {
872
+ recursive: true,
873
+ force: true
874
+ });
875
+ },
876
+ projectPath: wrapperProjectPath,
877
+ sourceProjectPath
878
+ };
879
+ }
880
+ //#endregion
881
+ export { operatingSystemName as A, removeCustomConfigKey as B, registerWechatDevtoolsInstallation as C, SupportedPlatformsMap as D, withPowerShellUtf8Output as E, createAutoTrustProjectConfig as F, defaultCustomConfigFilePath as H, createCustomConfig as I, createLocaleConfig as L, logger as M, wechatDevtoolsSelectionContext as N, getDefaultCliPath as O, createAutoBootstrapDevtoolsConfig as P, overwriteCustomConfig as R, selectInstalledWechatDevtools as S, inspectWechatDevtoolsInstallation as T, resolvePath as U, defaultCustomConfigDirPath as V, WECHAT_DEVTOOLS_OFFICIAL_CHANNELS_URL as _, readPersistedAutomatorSession as a, compareWechatDevtoolsVersions as b, getActiveWechatDevtoolsTarget as c, validateWechatDevtoolsSelection as d, withWechatDevtoolsSelection as f, resolveWechatInspectionTimeout as g, resolveWechatDevtoolsInstallationRoot as h, persistAutomatorSession as i, colors as j, isOperatingSystemSupported as k, readWechatDevtoolsSelectionEnvironment as l, assertWechatDevtoolsPort as m, readAutomatorProjectPlan as n, MANAGED_PROJECT_JOURNAL_ENV as o, assertWechatDevtoolsHost as p, resolveAutomatorProjectPath as r, MANAGED_PROJECT_MAX_WINDOWS_ENV as s, populateAutomatorProjectSnapshot as t, resolveWechatDevtoolsTarget as u, assertOfficialWechatDevtoolsVersion as v, unregisterWechatDevtoolsInstallation as w, listWechatDevtoolsInstallations as x, readOfficialWechatDevtoolsVersion as y, readCustomConfig as z };