weapp-ide-cli 6.1.9 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/dist/automator-BOe32upt.js +822 -0
- package/dist/automator-VSjgHujY.js +1 -0
- package/dist/automator-session-VOB03glx.js +186 -0
- package/dist/automatorProject-DjYLqMj6.js +881 -0
- package/dist/{cli-2sY1sAVq.js → cli-CFlqFpIw.js} +1552 -74
- package/dist/cli.js +3 -3
- package/dist/commands-DmirGBy4.js +1 -0
- package/dist/commands-mslf7-sP.js +529 -0
- package/dist/devtoolsProjectOwnership-BMfSAg4n.js +2022 -0
- package/dist/index.d.ts +420 -76
- package/dist/index.js +8 -5
- package/dist/managed-B04Xo9U_.js +264 -0
- package/dist/run-mcp-83-VKZVA.js +1 -0
- package/dist/{run-mcp-LqaWlQJ_.js → run-mcp-Ni139Z5C.js} +28 -9
- package/package.json +13 -12
- package/dist/automator-session-fbanL2eW.js +0 -1362
- package/dist/commands-DpMG1w04.js +0 -2
- package/dist/commands-__RJ552L.js +0 -1359
- package/dist/run-mcp-C2SQ_hRJ.js +0 -2
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { mutateLease } from "@weapp-vite/devtools-runtime";
|
|
3
|
+
import { execa } from "execa";
|
|
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 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";
|
|
12
|
+
import { Buffer } from "node:buffer";
|
|
13
|
+
//#region src/utils/path.ts
|
|
14
|
+
/**
|
|
15
|
+
* @description 解析为绝对路径(基于当前工作目录)
|
|
16
|
+
*/
|
|
17
|
+
function resolvePath(filePath) {
|
|
18
|
+
if (path$1.isAbsolute(filePath)) return filePath;
|
|
19
|
+
return path$1.resolve(process.cwd(), filePath);
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/config/paths.ts
|
|
23
|
+
const homedir = os.homedir();
|
|
24
|
+
/**
|
|
25
|
+
* @description 默认自定义配置目录
|
|
26
|
+
*/
|
|
27
|
+
const defaultCustomConfigDirPath = path$1.join(homedir, ".weapp-ide-cli");
|
|
28
|
+
/**
|
|
29
|
+
* @description 默认自定义配置文件路径
|
|
30
|
+
*/
|
|
31
|
+
const defaultCustomConfigFilePath = path$1.join(defaultCustomConfigDirPath, "config.json");
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/config/custom.ts
|
|
34
|
+
const JSON_OPTIONS = {
|
|
35
|
+
encoding: "utf8",
|
|
36
|
+
spaces: 2
|
|
37
|
+
};
|
|
38
|
+
async function readCustomConfig() {
|
|
39
|
+
if (!await fs$1.pathExists(defaultCustomConfigFilePath)) return {};
|
|
40
|
+
try {
|
|
41
|
+
const config = await fs$1.readJSON(defaultCustomConfigFilePath);
|
|
42
|
+
if (!config || typeof config !== "object") return {};
|
|
43
|
+
const candidate = config;
|
|
44
|
+
const next = {};
|
|
45
|
+
if (typeof candidate.cliPath === "string" && candidate.cliPath.trim()) next.cliPath = candidate.cliPath.trim();
|
|
46
|
+
if (candidate.locale === "zh" || candidate.locale === "en") next.locale = candidate.locale;
|
|
47
|
+
if (typeof candidate.autoBootstrapDevtools === "boolean") next.autoBootstrapDevtools = candidate.autoBootstrapDevtools;
|
|
48
|
+
if (typeof candidate.autoTrustProject === "boolean") next.autoTrustProject = candidate.autoTrustProject;
|
|
49
|
+
return next;
|
|
50
|
+
} catch {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function writeCustomConfig(patch, options = {}) {
|
|
55
|
+
const nextConfig = {
|
|
56
|
+
...options.replace ? {} : await readCustomConfig(),
|
|
57
|
+
...patch
|
|
58
|
+
};
|
|
59
|
+
await fs$1.ensureDir(defaultCustomConfigDirPath);
|
|
60
|
+
await fs$1.writeJSON(defaultCustomConfigFilePath, nextConfig, JSON_OPTIONS);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* @description 写入自定义 CLI 路径配置
|
|
64
|
+
*/
|
|
65
|
+
async function createCustomConfig(params) {
|
|
66
|
+
const trimmedCliPath = params.cliPath.trim();
|
|
67
|
+
if (!trimmedCliPath) throw new Error("cliPath cannot be empty");
|
|
68
|
+
const normalizedCliPath = resolvePath(trimmedCliPath);
|
|
69
|
+
await writeCustomConfig({ cliPath: normalizedCliPath });
|
|
70
|
+
return normalizedCliPath;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* @description 写入语言配置(zh / en)。
|
|
74
|
+
*/
|
|
75
|
+
async function createLocaleConfig(locale) {
|
|
76
|
+
await writeCustomConfig({ locale });
|
|
77
|
+
return locale;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* @description 写入开发者工具自动预热配置。
|
|
81
|
+
*/
|
|
82
|
+
async function createAutoBootstrapDevtoolsConfig(value) {
|
|
83
|
+
await writeCustomConfig({ autoBootstrapDevtools: value });
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* @description 写入项目自动信任配置。
|
|
88
|
+
*/
|
|
89
|
+
async function createAutoTrustProjectConfig(value) {
|
|
90
|
+
await writeCustomConfig({ autoTrustProject: value });
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* @description 删除指定配置项。
|
|
95
|
+
*/
|
|
96
|
+
async function removeCustomConfigKey(key) {
|
|
97
|
+
const currentConfig = await readCustomConfig();
|
|
98
|
+
if (!(key in currentConfig)) return;
|
|
99
|
+
const nextConfig = { ...currentConfig };
|
|
100
|
+
delete nextConfig[key];
|
|
101
|
+
await writeCustomConfig(nextConfig, { replace: true });
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* @description 覆盖写入配置内容(会替换原内容)。
|
|
105
|
+
*/
|
|
106
|
+
async function overwriteCustomConfig(config) {
|
|
107
|
+
const nextConfig = {};
|
|
108
|
+
if (typeof config.cliPath === "string" && config.cliPath.trim()) nextConfig.cliPath = resolvePath(config.cliPath.trim());
|
|
109
|
+
if (config.locale === "zh" || config.locale === "en") nextConfig.locale = config.locale;
|
|
110
|
+
if (typeof config.autoBootstrapDevtools === "boolean") nextConfig.autoBootstrapDevtools = config.autoBootstrapDevtools;
|
|
111
|
+
if (typeof config.autoTrustProject === "boolean") nextConfig.autoTrustProject = config.autoTrustProject;
|
|
112
|
+
await writeCustomConfig(nextConfig, { replace: true });
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
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
|
|
125
|
+
/**
|
|
126
|
+
* @description 官方微信开发者工具只支持 Windows、macOS,Linux 只有社区版。
|
|
127
|
+
* https://github.com/msojocs/wechat-web-devtools-linux
|
|
128
|
+
*/
|
|
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;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* @description 当前系统名称。
|
|
142
|
+
*/
|
|
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;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
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;
|
|
181
|
+
};
|
|
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;
|
|
210
|
+
};
|
|
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
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* @description 获取默认 CLI 路径(按系统)。
|
|
222
|
+
*/
|
|
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}`;
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/devtoolsTarget/metadata.ts
|
|
236
|
+
const MAX_HEADER_BYTES = 16777216;
|
|
237
|
+
const MAX_PACKAGE_BYTES = 1048576;
|
|
238
|
+
function record(value) {
|
|
239
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
240
|
+
}
|
|
241
|
+
async function readPackageFile(filePath) {
|
|
242
|
+
if ((await fs.stat(filePath)).size > MAX_PACKAGE_BYTES) throw new Error("DevTools package metadata exceeds the size limit.");
|
|
243
|
+
return JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
244
|
+
}
|
|
245
|
+
/** 只读取归档目录和根 package.json,不执行宿主代码或解包其他文件。 */
|
|
246
|
+
async function readAsarPackage(asarPath) {
|
|
247
|
+
const file = await fs.open(asarPath, "r");
|
|
248
|
+
try {
|
|
249
|
+
const stat = await file.stat();
|
|
250
|
+
const prefix = Buffer.alloc(16);
|
|
251
|
+
if ((await file.read(prefix, 0, prefix.length, 0)).bytesRead !== prefix.length) throw new Error("Invalid DevTools ASAR header.");
|
|
252
|
+
const headerSize = prefix.readUInt32LE(4);
|
|
253
|
+
const jsonSize = prefix.readUInt32LE(12);
|
|
254
|
+
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.");
|
|
255
|
+
const header = Buffer.alloc(jsonSize);
|
|
256
|
+
if ((await file.read(header, 0, jsonSize, 16)).bytesRead !== jsonSize) throw new Error("Truncated DevTools ASAR header.");
|
|
257
|
+
const parsed = JSON.parse(header.toString("utf8"));
|
|
258
|
+
const entry = record(parsed) && record(parsed.files) ? parsed.files["package.json"] : void 0;
|
|
259
|
+
if (!record(entry) || entry.link !== void 0) throw new Error("DevTools ASAR has no direct package metadata.");
|
|
260
|
+
if (entry.unpacked === true) return await readPackageFile(path.join(`${asarPath}.unpacked`, "package.json"));
|
|
261
|
+
const size = entry.size;
|
|
262
|
+
const offset = typeof entry.offset === "string" && /^\d+$/.test(entry.offset) ? Number(entry.offset) : NaN;
|
|
263
|
+
const position = 8 + headerSize + offset;
|
|
264
|
+
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.");
|
|
265
|
+
const content = Buffer.alloc(size);
|
|
266
|
+
if ((await file.read(content, 0, size, position)).bytesRead !== size) throw new Error("Truncated DevTools ASAR package metadata.");
|
|
267
|
+
return JSON.parse(content.toString("utf8"));
|
|
268
|
+
} finally {
|
|
269
|
+
await file.close();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/** 产品版本来自应用 package.json,不能使用外层 Electron 的版本。 */
|
|
273
|
+
async function readWechatDevtoolsMetadata(appPath) {
|
|
274
|
+
const value = appPath.endsWith(".asar") ? await readAsarPackage(appPath) : await readPackageFile(path.join(appPath, "package.json"));
|
|
275
|
+
if (!record(value)) throw new Error("Invalid DevTools package metadata.");
|
|
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" });
|
|
277
|
+
return {
|
|
278
|
+
version: typeof value.version === "string" && /^\d+(?:\.\d+){2,}$/.test(value.version) ? value.version : void 0,
|
|
279
|
+
channel: String(value.versionType) === "0" ? "stable" : String(value.versionType) === "1" ? "rc" : String(value.versionType) === "2" ? "nightly" : "unknown"
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region src/devtoolsTarget/profile.ts
|
|
284
|
+
/** 按所选宿主的存储布局定位配置,不扫描其他安装的目录。 */
|
|
285
|
+
function resolveWechatDevtoolsProfileDir(appPath, options = {}) {
|
|
286
|
+
const platform = options.platform ?? process.platform;
|
|
287
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
288
|
+
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"), "微信开发者工具");
|
|
289
|
+
if (platform === "win32" && path.basename(appPath) === "package.nw") return baseDir;
|
|
290
|
+
return path.join(baseDir, createHash("md5").update(appPath).digest("hex"));
|
|
291
|
+
}
|
|
292
|
+
//#endregion
|
|
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;
|
|
483
|
+
}
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region src/devtoolsTarget/inspection.ts
|
|
486
|
+
/** Windows 查询预算包含 PowerShell 冷启动及内核/CIM 初始化;显式较小预算始终优先。 */
|
|
487
|
+
function resolveWechatInspectionTimeout(platform, timeout) {
|
|
488
|
+
if (timeout !== void 0 && (Number.isNaN(timeout) || timeout <= 0)) throw new RangeError("DevTools inspection timeout must be positive.");
|
|
489
|
+
const maximum = platform === "win32" ? 3e4 : 3e3;
|
|
490
|
+
return Math.min(timeout ?? maximum, maximum);
|
|
491
|
+
}
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region src/devtoolsTarget/host.ts
|
|
494
|
+
function ownershipError(message) {
|
|
495
|
+
return Object.assign(new Error(message), { code: "WECHAT_DEVTOOLS_HOST_IDENTITY_MISMATCH" });
|
|
496
|
+
}
|
|
497
|
+
function normalize(value, platform) {
|
|
498
|
+
return (platform === "win32" ? value.replaceAll("\\", "/").toLowerCase() : value).replace(/\/$/, "");
|
|
499
|
+
}
|
|
500
|
+
/** 复用宿主归属检查的安装根规则,不能把 app.asar 当作可执行文件目录。 */
|
|
501
|
+
function resolveWechatDevtoolsInstallationRoot(target, platform) {
|
|
502
|
+
const appPath = normalize(target.appPath, platform);
|
|
503
|
+
return platform === "darwin" ? appPath.split("/Contents/")[0] : appPath.replace(/\/(?:resources\/)?(?:app\.asar|app|package\.nw)$/, "");
|
|
504
|
+
}
|
|
505
|
+
function assertExecutable(target, executable, platform) {
|
|
506
|
+
const root = resolveWechatDevtoolsInstallationRoot(target, platform);
|
|
507
|
+
const selected = normalize(executable.trim(), platform);
|
|
508
|
+
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.");
|
|
509
|
+
}
|
|
510
|
+
async function runInspection(file, args, options) {
|
|
511
|
+
options.signal?.throwIfAborted();
|
|
512
|
+
try {
|
|
513
|
+
const timeout = resolveWechatInspectionTimeout(options.platform ?? process.platform, options.timeout);
|
|
514
|
+
const result = await execa(file, args, {
|
|
515
|
+
timeout,
|
|
516
|
+
cancelSignal: options.signal,
|
|
517
|
+
reject: false,
|
|
518
|
+
windowsHide: true
|
|
519
|
+
});
|
|
520
|
+
options.signal?.throwIfAborted();
|
|
521
|
+
return result;
|
|
522
|
+
} catch (cause) {
|
|
523
|
+
options.signal?.throwIfAborted();
|
|
524
|
+
throw Object.assign(ownershipError("Unable to verify the running WeChat DevTools installation."), { cause });
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
async function readUnixExecutable(pid, options) {
|
|
528
|
+
if ((options.platform ?? process.platform) === "linux") {
|
|
529
|
+
options.signal?.throwIfAborted();
|
|
530
|
+
try {
|
|
531
|
+
const executable = await fs.readlink(`/proc/${pid}/exe`);
|
|
532
|
+
options.signal?.throwIfAborted();
|
|
533
|
+
return executable;
|
|
534
|
+
} catch (cause) {
|
|
535
|
+
options.signal?.throwIfAborted();
|
|
536
|
+
if (cause.code === "ENOENT") return;
|
|
537
|
+
throw Object.assign(ownershipError("The running WeChat DevTools executable could not be verified through procfs."), { cause });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
const result = await runInspection("ps", [
|
|
541
|
+
"-p",
|
|
542
|
+
String(pid),
|
|
543
|
+
"-o",
|
|
544
|
+
"comm="
|
|
545
|
+
], options);
|
|
546
|
+
if (result.exitCode === 1 && !result.stdout.trim()) return;
|
|
547
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) throw ownershipError("The running WeChat DevTools process identity could not be verified.");
|
|
548
|
+
return result.stdout.trim();
|
|
549
|
+
}
|
|
550
|
+
async function readWindowsProcesses(script, options) {
|
|
551
|
+
const result = await runInspection("powershell.exe", [
|
|
552
|
+
"-NoProfile",
|
|
553
|
+
"-NonInteractive",
|
|
554
|
+
"-Command",
|
|
555
|
+
withPowerShellUtf8Output(script)
|
|
556
|
+
], options);
|
|
557
|
+
if (result.exitCode !== 0) throw ownershipError("Windows could not verify the WeChat DevTools process identity.");
|
|
558
|
+
let parsed;
|
|
559
|
+
try {
|
|
560
|
+
parsed = JSON.parse(result.stdout.trim() || "[]");
|
|
561
|
+
} catch (cause) {
|
|
562
|
+
throw Object.assign(ownershipError("Windows returned unreadable DevTools process identities."), { cause });
|
|
563
|
+
}
|
|
564
|
+
return (Array.isArray(parsed) ? parsed : [parsed]).map((value) => {
|
|
565
|
+
if (!value || typeof value !== "object") throw ownershipError("Windows returned an invalid DevTools process identity.");
|
|
566
|
+
const entry = value;
|
|
567
|
+
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.");
|
|
568
|
+
return {
|
|
569
|
+
pid: entry.ProcessId,
|
|
570
|
+
executable: entry.ExecutablePath
|
|
571
|
+
};
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
/** 启动前验证共享宿主身份;无活跃宿主可以继续,其他安装不得被隐式接管。 */
|
|
575
|
+
async function assertWechatDevtoolsHost(target, options = {}) {
|
|
576
|
+
options.signal?.throwIfAborted();
|
|
577
|
+
const platform = options.platform ?? process.platform;
|
|
578
|
+
if (platform === "win32") {
|
|
579
|
+
const entries = await readWindowsProcesses("@(Get-CimInstance Win32_Process -Filter \"Name='wechatdevtools.exe' OR Name='wechatwebdevtools.exe'\" | Select-Object ProcessId,ExecutablePath) | ConvertTo-Json -Compress", options);
|
|
580
|
+
for (const entry of entries) assertExecutable(target, entry.executable, platform);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
for (const directory of /* @__PURE__ */ new Set([path.dirname(target.profileDir), target.profileDir])) {
|
|
584
|
+
let link;
|
|
585
|
+
try {
|
|
586
|
+
link = await fs.readlink(path.join(directory, "SingletonLock"));
|
|
587
|
+
} catch (error) {
|
|
588
|
+
if (error.code === "ENOENT") continue;
|
|
589
|
+
throw ownershipError("The shared DevTools host lock could not be verified.");
|
|
590
|
+
}
|
|
591
|
+
const match = /-(\d+)$/.exec(link);
|
|
592
|
+
if (!match) throw ownershipError("The shared DevTools host lock has an unknown owner.");
|
|
593
|
+
const executable = await readUnixExecutable(Number(match[1]), options);
|
|
594
|
+
if (executable) assertExecutable(target, executable, platform);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/** HTTP 与 WebSocket 的显式端口均需由所选安装持有,端口号本身不是宿主身份。 */
|
|
598
|
+
async function assertWechatDevtoolsPort(target, port, options = {}) {
|
|
599
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("Invalid WeChat DevTools port.");
|
|
600
|
+
options.signal?.throwIfAborted();
|
|
601
|
+
const platform = options.platform ?? process.platform;
|
|
602
|
+
if (platform === "win32") {
|
|
603
|
+
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);
|
|
604
|
+
if (!entries.length) throw ownershipError("No verified WeChat DevTools listener owns the selected port.");
|
|
605
|
+
for (const entry of entries) assertExecutable(target, entry.executable, platform);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const result = await runInspection("lsof", [
|
|
609
|
+
"-nP",
|
|
610
|
+
`-iTCP:${port}`,
|
|
611
|
+
"-sTCP:LISTEN",
|
|
612
|
+
"-Fp"
|
|
613
|
+
], options);
|
|
614
|
+
const pids = [...new Set(result.stdout.split(/\r?\n/).filter((line) => /^p\d+$/.test(line)).map((line) => Number(line.slice(1))))];
|
|
615
|
+
if (result.exitCode !== 0 || !pids.length) throw ownershipError("No verified WeChat DevTools listener owns the selected port.");
|
|
616
|
+
for (const pid of pids) {
|
|
617
|
+
const executable = await readUnixExecutable(pid, options);
|
|
618
|
+
if (!executable) throw ownershipError("The selected DevTools listener exited before its identity could be verified.");
|
|
619
|
+
assertExecutable(target, executable, platform);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
//#endregion
|
|
623
|
+
//#region src/devtoolsTarget/index.ts
|
|
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;
|
|
652
|
+
}
|
|
653
|
+
/** 每次操作只解析一次安装,显式选择覆盖继承的配置但不写回全局设置。 */
|
|
654
|
+
async function resolveWechatDevtoolsTarget(options = {}) {
|
|
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
|
+
};
|
|
668
|
+
}
|
|
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();
|
|
683
|
+
}
|
|
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
|
|
691
|
+
//#region src/cli/automator/sessionStore.ts
|
|
692
|
+
const SESSION_DIRECTORY = path.join(os.tmpdir(), "weapp-vite-automator-sessions");
|
|
693
|
+
/** 内部工具复用会话键;不从包入口公开缓存路径契约。 */
|
|
694
|
+
function resolveAutomatorSessionFilePath(options) {
|
|
695
|
+
const key = JSON.stringify([
|
|
696
|
+
options.installationId,
|
|
697
|
+
path.resolve(options.projectPath),
|
|
698
|
+
options.sessionId?.trim() || null,
|
|
699
|
+
options.port || null
|
|
700
|
+
]);
|
|
701
|
+
return path.join(SESSION_DIRECTORY, `${createHash("sha256").update(key).digest("hex")}.json`);
|
|
702
|
+
}
|
|
703
|
+
/** 只保存所选安装的端点,不覆盖其他安装或旧版会话记录。 */
|
|
704
|
+
async function persistAutomatorSession(options) {
|
|
705
|
+
options.signal?.throwIfAborted();
|
|
706
|
+
const filePath = resolveAutomatorSessionFilePath(options);
|
|
707
|
+
const payload = {
|
|
708
|
+
installationId: options.installationId,
|
|
709
|
+
...options.port ? { port: options.port } : {},
|
|
710
|
+
projectPath: path.resolve(options.projectPath),
|
|
711
|
+
...options.sessionId ? { sessionId: options.sessionId } : {},
|
|
712
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
713
|
+
wsEndpoint: options.wsEndpoint
|
|
714
|
+
};
|
|
715
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
716
|
+
options.signal?.throwIfAborted();
|
|
717
|
+
await fs.writeFile(filePath, JSON.stringify(payload, null, 2), {
|
|
718
|
+
encoding: "utf8",
|
|
719
|
+
signal: options.signal
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
async function readSessionFile(filePath, options) {
|
|
723
|
+
try {
|
|
724
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
725
|
+
const payload = JSON.parse(raw);
|
|
726
|
+
if (!payload || typeof payload !== "object") return null;
|
|
727
|
+
const session = payload;
|
|
728
|
+
if (session.installationId !== options.installationId || session.projectPath !== path.resolve(options.projectPath)) return null;
|
|
729
|
+
if (options.sessionId && session.sessionId !== options.sessionId || options.port && session.port !== options.port) return null;
|
|
730
|
+
if (typeof session.wsEndpoint !== "string") return null;
|
|
731
|
+
const endpoint = new URL(session.wsEndpoint);
|
|
732
|
+
if (endpoint.protocol !== "ws:" || endpoint.hostname !== "127.0.0.1" || !endpoint.port || options.port && Number(endpoint.port) !== options.port) return null;
|
|
733
|
+
return session;
|
|
734
|
+
} catch {
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
/** 未携带安装身份、身份冲突或端口不符的缓存均不可用于连接。 */
|
|
739
|
+
async function readPersistedAutomatorSession(options) {
|
|
740
|
+
const direct = await readSessionFile(resolveAutomatorSessionFilePath(options), options);
|
|
741
|
+
if (direct || !options.sessionId || options.port) return direct;
|
|
742
|
+
try {
|
|
743
|
+
const entries = await fs.readdir(SESSION_DIRECTORY);
|
|
744
|
+
const matches = [];
|
|
745
|
+
for (const entry of entries) {
|
|
746
|
+
if (!entry.endsWith(".json")) continue;
|
|
747
|
+
const session = await readSessionFile(path.join(SESSION_DIRECTORY, entry), options);
|
|
748
|
+
if (session) matches.push(session);
|
|
749
|
+
}
|
|
750
|
+
return matches.length === 1 ? matches[0] : null;
|
|
751
|
+
} catch {
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
//#endregion
|
|
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 };
|