@napuketto/loader 0.0.12 → 0.0.13
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/dist/index.cjs +74 -3
- package/dist/index.d.cts +22 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +22 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +74 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -290,7 +290,7 @@ async function extractInstaller(installerPath, destDir, sevenZipPath) {
|
|
|
290
290
|
"-y"
|
|
291
291
|
], { maxBuffer: 67108864 }, (err, _stdout, stderr) => {
|
|
292
292
|
if (err !== null) {
|
|
293
|
-
const hint = err.code === "ENOENT" ? `未找到 7z 可执行文件: ${sevenZip}\n(Windows 请安装 7-Zip 或用 NAPUTO_7Z_PATH 指定;Linux
|
|
293
|
+
const hint = err.code === "ENOENT" ? `未找到 7z 可执行文件: ${sevenZip}\n(Windows 请安装 7-Zip 或用 NAPUTO_7Z_PATH 指定;Linux 自动下载失败时请安装 p7zip-full 或用 NAPUTO_7Z_PATH 指定)` : "(QQ 安装包是 NSIS 格式,需完整版 7z 而非 7za)";
|
|
294
294
|
reject(/* @__PURE__ */ new Error(`7z 解包失败: ${installerPath}\n${stderr.trim()}\n${hint}`));
|
|
295
295
|
return;
|
|
296
296
|
}
|
|
@@ -453,6 +453,16 @@ const DRIVE_LETTER_RE = /^[A-Za-z]:/;
|
|
|
453
453
|
function isLinux() {
|
|
454
454
|
return process.platform === "linux";
|
|
455
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Unix PATH(冒号分隔)→ wine Windows PATH(分号分隔 + 全部 Z:\ 条目)。
|
|
458
|
+
*
|
|
459
|
+
* ⚠️ 混入 Unix PATH 会让 wine 按错误分隔符拆分盘符路径(Z:\...),stub 目录
|
|
460
|
+
* 丢失 → wrapper.node 依赖的 QQNT.dll 找不到(2026-08-14 生产实测
|
|
461
|
+
* "import_dll Library QQNT.dll not found")。wine 场景 PATH 必须纯 Windows 风格。
|
|
462
|
+
*/
|
|
463
|
+
function unixPathToWinePath(unixPath) {
|
|
464
|
+
return unixPath.split(":").filter((entry) => entry !== "").map((entry) => toWinePath(entry)).join(";");
|
|
465
|
+
}
|
|
456
466
|
/** wine 可执行文件(Linux 上 spawn 用;可被 NAPUTO_WINE 覆盖)。 */
|
|
457
467
|
function wineBinary() {
|
|
458
468
|
return process.env["NAPUTO_WINE"] ?? "wine";
|
|
@@ -539,10 +549,12 @@ async function resolveNodeExecutable(options) {
|
|
|
539
549
|
/** 注入 PATH(stub 目录 + wrapper.node 目录前置)与 STUB_DIR(wine 场景转 Z:\)。 */
|
|
540
550
|
function applyPathEnv(env, opts) {
|
|
541
551
|
const p = (linuxPath) => opts.useWine ? toWinePath(linuxPath) : linuxPath;
|
|
552
|
+
const unixPath = node_process.default.env["PATH"] ?? "";
|
|
553
|
+
const winPath = opts.useWine ? unixPathToWinePath(unixPath) : unixPath;
|
|
542
554
|
env["PATH"] = [
|
|
543
555
|
p(opts.stub),
|
|
544
556
|
p((0, node_path.dirname)(opts.wrapperPath)),
|
|
545
|
-
|
|
557
|
+
winPath
|
|
546
558
|
].filter((entry) => entry !== void 0 && entry !== "").join(";");
|
|
547
559
|
env[ENV.STUB_DIR] = p(opts.stub);
|
|
548
560
|
}
|
|
@@ -668,6 +680,8 @@ function resolveDownloadUrl(release) {
|
|
|
668
680
|
*/
|
|
669
681
|
/** 数据根下 QQ 文件缓存目录名(<数据根>/qq-files/<版本>/,P1 下载解包产物)。 */
|
|
670
682
|
const QQ_FILES_DIR_NAME = "qq-files";
|
|
683
|
+
/** 7-Zip 官方 Linux 版版本号(tar.xz 文件名组成部分,2026-08-14 起自动下载)。 */
|
|
684
|
+
const SEVEN_ZIP_LINUX_VERSION = "2409";
|
|
671
685
|
/** 注册表 UninstallString 查询(QQ 官方安装路径)。 */
|
|
672
686
|
const REG_QUERY_RE = /"([^"]+)"/;
|
|
673
687
|
/** Windows 盘符路径正则(`C:/...` / `C:\...`,wslMappedPath 映射用,模块级常量)。 */
|
|
@@ -781,6 +795,59 @@ function tryResolveCached(dataRoot) {
|
|
|
781
795
|
return null;
|
|
782
796
|
}
|
|
783
797
|
/**
|
|
798
|
+
* 7-Zip 官方 Linux 版下载地址(纯函数,可单测)。
|
|
799
|
+
* 环境变量 NAPUTO_7Z_URL 可覆盖(同 QQ 下载 NAPUTO_QQ_URL 模式)。
|
|
800
|
+
*/
|
|
801
|
+
function linuxSevenZipUrl() {
|
|
802
|
+
return node_process.default.env["NAPUTO_7Z_URL"] ?? `https://www.7-zip.org/a/7z${SEVEN_ZIP_LINUX_VERSION}-linux-x64.tar.xz`;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* 确保 Linux 7zz 就绪(2026-08-14 生产修复):自动下载 7-Zip 官方 Linux 版。
|
|
806
|
+
*
|
|
807
|
+
* 背景:内置 assets/7zip 的 7z.exe 是 Windows PE(Linux 不可用),此前 Linux 依赖
|
|
808
|
+
* 系统 p7zip-full——生产环境未安装 → `7z 解包失败: No such file or directory`。
|
|
809
|
+
* 7zz 是 7-Zip 完整版(支持 NSIS 解包,同 7z.exe 能力),官方静态二进制,
|
|
810
|
+
* 免 root 免系统包,下载到 <数据根>/runtime/7zip/7zz(与 win-node 同模式)。
|
|
811
|
+
*
|
|
812
|
+
* 幂等:缓存已存在直接返回;tar -xJf 解压(Linux 自带 tar;需 xz-utils)。
|
|
813
|
+
*/
|
|
814
|
+
async function ensureLinuxSevenZip(options = {}) {
|
|
815
|
+
const dataRoot = resolveDataRootLight(options.dataRoot);
|
|
816
|
+
const cacheDir = (0, node_path.join)(dataRoot, "runtime", "7zip");
|
|
817
|
+
const exe = (0, node_path.join)(cacheDir, "7zz");
|
|
818
|
+
if ((0, node_fs.existsSync)(exe)) return {
|
|
819
|
+
exe,
|
|
820
|
+
source: "数据根缓存 runtime/7zip"
|
|
821
|
+
};
|
|
822
|
+
const archive = (0, node_path.join)(cacheDir, `7z${SEVEN_ZIP_LINUX_VERSION}-linux-x64.tar.xz`);
|
|
823
|
+
(0, node_fs.mkdirSync)(cacheDir, { recursive: true });
|
|
824
|
+
await downloadFile({
|
|
825
|
+
dest: archive,
|
|
826
|
+
url: linuxSevenZipUrl()
|
|
827
|
+
});
|
|
828
|
+
try {
|
|
829
|
+
try {
|
|
830
|
+
(0, node_child_process.execFileSync)("tar", [
|
|
831
|
+
"-xJf",
|
|
832
|
+
archive,
|
|
833
|
+
"-C",
|
|
834
|
+
cacheDir
|
|
835
|
+
], { stdio: "pipe" });
|
|
836
|
+
} catch (err) {
|
|
837
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
838
|
+
throw new Error(`7z linux 解压失败: ${message}(需 tar + xz-utils;或设 NAPUTO_7Z_PATH 指定系统 7z)`);
|
|
839
|
+
}
|
|
840
|
+
} finally {
|
|
841
|
+
await (0, node_fs_promises.rm)(archive, { force: true });
|
|
842
|
+
}
|
|
843
|
+
if (!(0, node_fs.existsSync)(exe)) throw new Error(`7z linux 解压后未找到 7zz: ${exe}`);
|
|
844
|
+
await (0, node_fs_promises.chmod)(exe, 493);
|
|
845
|
+
return {
|
|
846
|
+
exe,
|
|
847
|
+
source: "自动下载(7-Zip 官方 linux 版)"
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
784
851
|
* 确保 QQ 原生文件就绪(P1):幂等缓存检查 → 下载官方安装包 → sha256 校验
|
|
785
852
|
* → 7z 解包 → 提取 wrapper.node/QQNT.dll → 缓存。返回缓存版本 QqInstallInfo。
|
|
786
853
|
*/
|
|
@@ -801,9 +868,11 @@ async function ensureQqFiles(options = {}) {
|
|
|
801
868
|
url,
|
|
802
869
|
expectedSha256: release.sha256
|
|
803
870
|
});
|
|
871
|
+
let sevenZipPath = options.sevenZipPath;
|
|
872
|
+
if (sevenZipPath === void 0 && node_process.default.platform === "linux") sevenZipPath = (await ensureLinuxSevenZip({ dataRoot })).exe;
|
|
804
873
|
const extractedDir = (0, node_path.join)(tmpDir, `qq-extracted-${version}`);
|
|
805
874
|
try {
|
|
806
|
-
await extractInstaller(installerPath, extractedDir,
|
|
875
|
+
await extractInstaller(installerPath, extractedDir, sevenZipPath);
|
|
807
876
|
return await extractWrapperFiles(extractedDir, version, cacheRoot);
|
|
808
877
|
} catch (err) {
|
|
809
878
|
await clearCacheVersion(cacheRoot, version);
|
|
@@ -849,6 +918,7 @@ exports.checkInstanceLock = checkInstanceLock;
|
|
|
849
918
|
exports.clearCacheVersion = clearCacheVersion;
|
|
850
919
|
exports.defaultStubDir = defaultStubDir;
|
|
851
920
|
exports.downloadFile = downloadFile;
|
|
921
|
+
exports.ensureLinuxSevenZip = ensureLinuxSevenZip;
|
|
852
922
|
exports.ensureQqFiles = ensureQqFiles;
|
|
853
923
|
exports.ensureWinNode = ensureWinNode;
|
|
854
924
|
exports.extractInstaller = extractInstaller;
|
|
@@ -857,6 +927,7 @@ exports.findSevenZip = findSevenZip;
|
|
|
857
927
|
exports.isLinux = isLinux;
|
|
858
928
|
exports.latestRelease = latestRelease;
|
|
859
929
|
exports.launchSelfHost = launchSelfHost;
|
|
930
|
+
exports.linuxSevenZipUrl = linuxSevenZipUrl;
|
|
860
931
|
exports.loadQqReleases = loadQqReleases;
|
|
861
932
|
exports.locateQqPath = locateQqPath;
|
|
862
933
|
exports.nodeZipUrl = nodeZipUrl;
|
package/dist/index.d.cts
CHANGED
|
@@ -101,6 +101,27 @@ declare function resolveQqInstall(qqPath?: string): QqInstallInfo;
|
|
|
101
101
|
* ⚠️ async:自动下载为异步流程(P1 起);纯定位场景用 resolveQqInstall(同步)。
|
|
102
102
|
*/
|
|
103
103
|
declare function resolveQqFiles(options?: ResolveQqFilesOptions): Promise<QqInstallInfo>;
|
|
104
|
+
/**
|
|
105
|
+
* 7-Zip 官方 Linux 版下载地址(纯函数,可单测)。
|
|
106
|
+
* 环境变量 NAPUTO_7Z_URL 可覆盖(同 QQ 下载 NAPUTO_QQ_URL 模式)。
|
|
107
|
+
*/
|
|
108
|
+
declare function linuxSevenZipUrl(): string;
|
|
109
|
+
/**
|
|
110
|
+
* 确保 Linux 7zz 就绪(2026-08-14 生产修复):自动下载 7-Zip 官方 Linux 版。
|
|
111
|
+
*
|
|
112
|
+
* 背景:内置 assets/7zip 的 7z.exe 是 Windows PE(Linux 不可用),此前 Linux 依赖
|
|
113
|
+
* 系统 p7zip-full——生产环境未安装 → `7z 解包失败: No such file or directory`。
|
|
114
|
+
* 7zz 是 7-Zip 完整版(支持 NSIS 解包,同 7z.exe 能力),官方静态二进制,
|
|
115
|
+
* 免 root 免系统包,下载到 <数据根>/runtime/7zip/7zz(与 win-node 同模式)。
|
|
116
|
+
*
|
|
117
|
+
* 幂等:缓存已存在直接返回;tar -xJf 解压(Linux 自带 tar;需 xz-utils)。
|
|
118
|
+
*/
|
|
119
|
+
declare function ensureLinuxSevenZip(options?: {
|
|
120
|
+
dataRoot?: string;
|
|
121
|
+
}): Promise<{
|
|
122
|
+
exe: string;
|
|
123
|
+
source: string;
|
|
124
|
+
}>;
|
|
104
125
|
/**
|
|
105
126
|
* 确保 QQ 原生文件就绪(P1):幂等缓存检查 → 下载官方安装包 → sha256 校验
|
|
106
127
|
* → 7z 解包 → 提取 wrapper.node/QQNT.dll → 缓存。返回缓存版本 QqInstallInfo。
|
|
@@ -361,5 +382,5 @@ declare function buildSpawnCommand(options: {
|
|
|
361
382
|
selfHostPath: string;
|
|
362
383
|
}): SpawnCommand;
|
|
363
384
|
//#endregion
|
|
364
|
-
export { DownloadError, type DownloadOptions, type DownloadResult, ENV, type ExtractOptions, INSTANCE_LOCK_FILE, type InstanceLockCheck, type InstanceLockInfo, type LaunchOptions, type LaunchResult, QQ_FILES_DIR_NAME, type QqFileSource, type QqInstallInfo, type QqReleaseEntry, QqReleasesError, type QqReleasesFile, type ResolveQqFilesOptions, type SevenZipResult, type SpawnCommand, WIN_NODE_DIR_NAME, type WinNodeInfo, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, latestRelease, launchSelfHost, loadQqReleases, locateQqPath, nodeZipUrl, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
|
|
385
|
+
export { DownloadError, type DownloadOptions, type DownloadResult, ENV, type ExtractOptions, INSTANCE_LOCK_FILE, type InstanceLockCheck, type InstanceLockInfo, type LaunchOptions, type LaunchResult, QQ_FILES_DIR_NAME, type QqFileSource, type QqInstallInfo, type QqReleaseEntry, QqReleasesError, type QqReleasesFile, type ResolveQqFilesOptions, type SevenZipResult, type SpawnCommand, WIN_NODE_DIR_NAME, type WinNodeInfo, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureLinuxSevenZip, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, latestRelease, launchSelfHost, linuxSevenZipUrl, loadQqReleases, locateQqPath, nodeZipUrl, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
|
|
365
386
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/instance-lock.ts","../src/locate-qq.ts","../src/launcher.ts","../src/qq-download.ts","../src/qq-extract.ts","../src/qq-releases.ts","../src/win-node.ts","../src/wine.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;cA2Ba;;UAGI;;EAEb;;EAEA;;EAEA;;;UAIa;;EAEb;;EAEA;;EAEA;;;;;;;iBAQY,kBAAkB,kBAAkB;;;;;iBA+BpC,oBAAoB,iBAAiB;;;;;iBA4BrC,oBAAoB;;iBAapB,oBAAoB;;;;cC5GvB;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/instance-lock.ts","../src/locate-qq.ts","../src/launcher.ts","../src/qq-download.ts","../src/qq-extract.ts","../src/qq-releases.ts","../src/win-node.ts","../src/wine.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;cA2Ba;;UAGI;;EAEb;;EAEA;;EAEA;;;UAIa;;EAEb;;EAEA;;EAEA;;;;;;;iBAQY,kBAAkB,kBAAkB;;;;;iBA+BpC,oBAAoB,iBAAiB;;;;;iBA4BrC,oBAAoB;;iBAapB,oBAAoB;;;;cC5GvB;;KAmFD;;UAGK;;EAEb;;EAEA;;EAEA;;EAEA;;EAEA,QAAQ;;;UAIK;;EAEb;;EAEA;;EAEA;;EAEA;;EAEA;;;iBAIY;;iBAwCA,iBAAiB,kBAAkB;;;;;;iBAa7B,eAAe,UAAS,wBAA6B,QAAQ;;;;;iBA0DnE;;;;;;;;;;;iBAiBM,oBAClB;EAAW;IACZ;EAAU;EAAa;;;;;;iBAmCJ,cAClB;;EAEI;;EAEA;IAEL,QAAQ;;;UCzRM;;EAEb,IAAI;;EAEJ;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;EAQA;;;;;;EAMA,QAAQ;;UAGK;;EAEb,OAAO,kBAAkB;;EAEzB;;;;;;;;;;;;;iBAckB,eAAe,SAAS,gBAAgB,QAAQ;;;;;iBA0FtD;;cA4CH;WACT;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;UCzNa;;EAEb;;EAEA;;EAEA;;EAEA;;;UAIa;;EAEb;;EAEA;;;cAIS,sBAAsB;EAC/B,YAAY;;;;;;iBA8CM,aAAa,SAAS,kBAAkB,QAAQ;;;;UCrDrD;;EAEb;;EAEA;;;UAIa;;EAEb;;EAEA;;EAEA;;EAEA;;;;;;;iBAQY,gBAAgB;;iBAuBV,iBAClB,uBACA,iBACA,wBACD;;;;;;iBA+BmB,oBAClB,sBACA,iBACA,oBACD,QAAQ;;iBAkDW,kBAAkB,mBAAmB,kBAAkB;;;;UC3J5D;;EAEb;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIa;EACb;EACA,OAAO;;;cAIE,wBAAwB;EACjC,YAAY;;;iBAYA,kBAAkB;;iBAoBlB,cAAc,UAAU,iBAAiB;;;;;iBAYzC,mBAAmB,SAAS;;;;cC3D/B;;UAGI;;EAEb;;EAEA;;;iBAIY,WAAW;;;;;iBAQL,cAClB;;EAEI;;EAEA;;EAEA;IAEL,QAAQ;;;;;;;;;;;;;;;iBCpCK,WAAW;;iBAiBX;;iBAoBA;;UAKC;;EAEb;;EAEA;;;iBAIY,kBAAkB;;EAE9B,WAAW,OAAO;;EAElB;;EAEA;;EAEA;IACA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -101,6 +101,27 @@ declare function resolveQqInstall(qqPath?: string): QqInstallInfo;
|
|
|
101
101
|
* ⚠️ async:自动下载为异步流程(P1 起);纯定位场景用 resolveQqInstall(同步)。
|
|
102
102
|
*/
|
|
103
103
|
declare function resolveQqFiles(options?: ResolveQqFilesOptions): Promise<QqInstallInfo>;
|
|
104
|
+
/**
|
|
105
|
+
* 7-Zip 官方 Linux 版下载地址(纯函数,可单测)。
|
|
106
|
+
* 环境变量 NAPUTO_7Z_URL 可覆盖(同 QQ 下载 NAPUTO_QQ_URL 模式)。
|
|
107
|
+
*/
|
|
108
|
+
declare function linuxSevenZipUrl(): string;
|
|
109
|
+
/**
|
|
110
|
+
* 确保 Linux 7zz 就绪(2026-08-14 生产修复):自动下载 7-Zip 官方 Linux 版。
|
|
111
|
+
*
|
|
112
|
+
* 背景:内置 assets/7zip 的 7z.exe 是 Windows PE(Linux 不可用),此前 Linux 依赖
|
|
113
|
+
* 系统 p7zip-full——生产环境未安装 → `7z 解包失败: No such file or directory`。
|
|
114
|
+
* 7zz 是 7-Zip 完整版(支持 NSIS 解包,同 7z.exe 能力),官方静态二进制,
|
|
115
|
+
* 免 root 免系统包,下载到 <数据根>/runtime/7zip/7zz(与 win-node 同模式)。
|
|
116
|
+
*
|
|
117
|
+
* 幂等:缓存已存在直接返回;tar -xJf 解压(Linux 自带 tar;需 xz-utils)。
|
|
118
|
+
*/
|
|
119
|
+
declare function ensureLinuxSevenZip(options?: {
|
|
120
|
+
dataRoot?: string;
|
|
121
|
+
}): Promise<{
|
|
122
|
+
exe: string;
|
|
123
|
+
source: string;
|
|
124
|
+
}>;
|
|
104
125
|
/**
|
|
105
126
|
* 确保 QQ 原生文件就绪(P1):幂等缓存检查 → 下载官方安装包 → sha256 校验
|
|
106
127
|
* → 7z 解包 → 提取 wrapper.node/QQNT.dll → 缓存。返回缓存版本 QqInstallInfo。
|
|
@@ -361,5 +382,5 @@ declare function buildSpawnCommand(options: {
|
|
|
361
382
|
selfHostPath: string;
|
|
362
383
|
}): SpawnCommand;
|
|
363
384
|
//#endregion
|
|
364
|
-
export { DownloadError, type DownloadOptions, type DownloadResult, ENV, type ExtractOptions, INSTANCE_LOCK_FILE, type InstanceLockCheck, type InstanceLockInfo, type LaunchOptions, type LaunchResult, QQ_FILES_DIR_NAME, type QqFileSource, type QqInstallInfo, type QqReleaseEntry, QqReleasesError, type QqReleasesFile, type ResolveQqFilesOptions, type SevenZipResult, type SpawnCommand, WIN_NODE_DIR_NAME, type WinNodeInfo, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, latestRelease, launchSelfHost, loadQqReleases, locateQqPath, nodeZipUrl, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
|
|
385
|
+
export { DownloadError, type DownloadOptions, type DownloadResult, ENV, type ExtractOptions, INSTANCE_LOCK_FILE, type InstanceLockCheck, type InstanceLockInfo, type LaunchOptions, type LaunchResult, QQ_FILES_DIR_NAME, type QqFileSource, type QqInstallInfo, type QqReleaseEntry, QqReleasesError, type QqReleasesFile, type ResolveQqFilesOptions, type SevenZipResult, type SpawnCommand, WIN_NODE_DIR_NAME, type WinNodeInfo, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureLinuxSevenZip, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, latestRelease, launchSelfHost, linuxSevenZipUrl, loadQqReleases, locateQqPath, nodeZipUrl, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
|
|
365
386
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/instance-lock.ts","../src/locate-qq.ts","../src/launcher.ts","../src/qq-download.ts","../src/qq-extract.ts","../src/qq-releases.ts","../src/win-node.ts","../src/wine.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;cA2Ba;;UAGI;;EAEb;;EAEA;;EAEA;;;UAIa;;EAEb;;EAEA;;EAEA;;;;;;;iBAQY,kBAAkB,kBAAkB;;;;;iBA+BpC,oBAAoB,iBAAiB;;;;;iBA4BrC,oBAAoB;;iBAapB,oBAAoB;;;;cC5GvB;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/instance-lock.ts","../src/locate-qq.ts","../src/launcher.ts","../src/qq-download.ts","../src/qq-extract.ts","../src/qq-releases.ts","../src/win-node.ts","../src/wine.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;cA2Ba;;UAGI;;EAEb;;EAEA;;EAEA;;;UAIa;;EAEb;;EAEA;;EAEA;;;;;;;iBAQY,kBAAkB,kBAAkB;;;;;iBA+BpC,oBAAoB,iBAAiB;;;;;iBA4BrC,oBAAoB;;iBAapB,oBAAoB;;;;cC5GvB;;KAmFD;;UAGK;;EAEb;;EAEA;;EAEA;;EAEA;;EAEA,QAAQ;;;UAIK;;EAEb;;EAEA;;EAEA;;EAEA;;EAEA;;;iBAIY;;iBAwCA,iBAAiB,kBAAkB;;;;;;iBAa7B,eAAe,UAAS,wBAA6B,QAAQ;;;;;iBA0DnE;;;;;;;;;;;iBAiBM,oBAClB;EAAW;IACZ;EAAU;EAAa;;;;;;iBAmCJ,cAClB;;EAEI;;EAEA;IAEL,QAAQ;;;UCzRM;;EAEb,IAAI;;EAEJ;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;EAQA;;;;;;EAMA,QAAQ;;UAGK;;EAEb,OAAO,kBAAkB;;EAEzB;;;;;;;;;;;;;iBAckB,eAAe,SAAS,gBAAgB,QAAQ;;;;;iBA0FtD;;cA4CH;WACT;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;UCzNa;;EAEb;;EAEA;;EAEA;;EAEA;;;UAIa;;EAEb;;EAEA;;;cAIS,sBAAsB;EAC/B,YAAY;;;;;;iBA8CM,aAAa,SAAS,kBAAkB,QAAQ;;;;UCrDrD;;EAEb;;EAEA;;;UAIa;;EAEb;;EAEA;;EAEA;;EAEA;;;;;;;iBAQY,gBAAgB;;iBAuBV,iBAClB,uBACA,iBACA,wBACD;;;;;;iBA+BmB,oBAClB,sBACA,iBACA,oBACD,QAAQ;;iBAkDW,kBAAkB,mBAAmB,kBAAkB;;;;UC3J5D;;EAEb;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIa;EACb;EACA,OAAO;;;cAIE,wBAAwB;EACjC,YAAY;;;iBAYA,kBAAkB;;iBAoBlB,cAAc,UAAU,iBAAiB;;;;;iBAYzC,mBAAmB,SAAS;;;;cC3D/B;;UAGI;;EAEb;;EAEA;;;iBAIY,WAAW;;;;;iBAQL,cAClB;;EAEI;;EAEA;;EAEA;IAEL,QAAQ;;;;;;;;;;;;;;;iBCpCK,WAAW;;iBAiBX;;iBAoBA;;UAKC;;EAEb;;EAEA;;;iBAIY,kBAAkB;;EAE9B,WAAW,OAAO;;EAElB;;EAEA;;EAEA;IACA"}
|
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
3
3
|
import process$1 from "node:process";
|
|
4
4
|
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { copyFile, mkdir, readdir, rm } from "node:fs/promises";
|
|
6
|
+
import { chmod, copyFile, mkdir, readdir, rm } from "node:fs/promises";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { get } from "node:https";
|
|
9
9
|
//#region src/instance-lock.ts
|
|
@@ -266,7 +266,7 @@ async function extractInstaller(installerPath, destDir, sevenZipPath) {
|
|
|
266
266
|
"-y"
|
|
267
267
|
], { maxBuffer: 67108864 }, (err, _stdout, stderr) => {
|
|
268
268
|
if (err !== null) {
|
|
269
|
-
const hint = err.code === "ENOENT" ? `未找到 7z 可执行文件: ${sevenZip}\n(Windows 请安装 7-Zip 或用 NAPUTO_7Z_PATH 指定;Linux
|
|
269
|
+
const hint = err.code === "ENOENT" ? `未找到 7z 可执行文件: ${sevenZip}\n(Windows 请安装 7-Zip 或用 NAPUTO_7Z_PATH 指定;Linux 自动下载失败时请安装 p7zip-full 或用 NAPUTO_7Z_PATH 指定)` : "(QQ 安装包是 NSIS 格式,需完整版 7z 而非 7za)";
|
|
270
270
|
reject(/* @__PURE__ */ new Error(`7z 解包失败: ${installerPath}\n${stderr.trim()}\n${hint}`));
|
|
271
271
|
return;
|
|
272
272
|
}
|
|
@@ -429,6 +429,16 @@ const DRIVE_LETTER_RE = /^[A-Za-z]:/;
|
|
|
429
429
|
function isLinux() {
|
|
430
430
|
return process.platform === "linux";
|
|
431
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* Unix PATH(冒号分隔)→ wine Windows PATH(分号分隔 + 全部 Z:\ 条目)。
|
|
434
|
+
*
|
|
435
|
+
* ⚠️ 混入 Unix PATH 会让 wine 按错误分隔符拆分盘符路径(Z:\...),stub 目录
|
|
436
|
+
* 丢失 → wrapper.node 依赖的 QQNT.dll 找不到(2026-08-14 生产实测
|
|
437
|
+
* "import_dll Library QQNT.dll not found")。wine 场景 PATH 必须纯 Windows 风格。
|
|
438
|
+
*/
|
|
439
|
+
function unixPathToWinePath(unixPath) {
|
|
440
|
+
return unixPath.split(":").filter((entry) => entry !== "").map((entry) => toWinePath(entry)).join(";");
|
|
441
|
+
}
|
|
432
442
|
/** wine 可执行文件(Linux 上 spawn 用;可被 NAPUTO_WINE 覆盖)。 */
|
|
433
443
|
function wineBinary() {
|
|
434
444
|
return process.env["NAPUTO_WINE"] ?? "wine";
|
|
@@ -515,10 +525,12 @@ async function resolveNodeExecutable(options) {
|
|
|
515
525
|
/** 注入 PATH(stub 目录 + wrapper.node 目录前置)与 STUB_DIR(wine 场景转 Z:\)。 */
|
|
516
526
|
function applyPathEnv(env, opts) {
|
|
517
527
|
const p = (linuxPath) => opts.useWine ? toWinePath(linuxPath) : linuxPath;
|
|
528
|
+
const unixPath = process$1.env["PATH"] ?? "";
|
|
529
|
+
const winPath = opts.useWine ? unixPathToWinePath(unixPath) : unixPath;
|
|
518
530
|
env["PATH"] = [
|
|
519
531
|
p(opts.stub),
|
|
520
532
|
p(dirname(opts.wrapperPath)),
|
|
521
|
-
|
|
533
|
+
winPath
|
|
522
534
|
].filter((entry) => entry !== void 0 && entry !== "").join(";");
|
|
523
535
|
env[ENV.STUB_DIR] = p(opts.stub);
|
|
524
536
|
}
|
|
@@ -644,6 +656,8 @@ function resolveDownloadUrl(release) {
|
|
|
644
656
|
*/
|
|
645
657
|
/** 数据根下 QQ 文件缓存目录名(<数据根>/qq-files/<版本>/,P1 下载解包产物)。 */
|
|
646
658
|
const QQ_FILES_DIR_NAME = "qq-files";
|
|
659
|
+
/** 7-Zip 官方 Linux 版版本号(tar.xz 文件名组成部分,2026-08-14 起自动下载)。 */
|
|
660
|
+
const SEVEN_ZIP_LINUX_VERSION = "2409";
|
|
647
661
|
/** 注册表 UninstallString 查询(QQ 官方安装路径)。 */
|
|
648
662
|
const REG_QUERY_RE = /"([^"]+)"/;
|
|
649
663
|
/** Windows 盘符路径正则(`C:/...` / `C:\...`,wslMappedPath 映射用,模块级常量)。 */
|
|
@@ -757,6 +771,59 @@ function tryResolveCached(dataRoot) {
|
|
|
757
771
|
return null;
|
|
758
772
|
}
|
|
759
773
|
/**
|
|
774
|
+
* 7-Zip 官方 Linux 版下载地址(纯函数,可单测)。
|
|
775
|
+
* 环境变量 NAPUTO_7Z_URL 可覆盖(同 QQ 下载 NAPUTO_QQ_URL 模式)。
|
|
776
|
+
*/
|
|
777
|
+
function linuxSevenZipUrl() {
|
|
778
|
+
return process$1.env["NAPUTO_7Z_URL"] ?? `https://www.7-zip.org/a/7z${SEVEN_ZIP_LINUX_VERSION}-linux-x64.tar.xz`;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* 确保 Linux 7zz 就绪(2026-08-14 生产修复):自动下载 7-Zip 官方 Linux 版。
|
|
782
|
+
*
|
|
783
|
+
* 背景:内置 assets/7zip 的 7z.exe 是 Windows PE(Linux 不可用),此前 Linux 依赖
|
|
784
|
+
* 系统 p7zip-full——生产环境未安装 → `7z 解包失败: No such file or directory`。
|
|
785
|
+
* 7zz 是 7-Zip 完整版(支持 NSIS 解包,同 7z.exe 能力),官方静态二进制,
|
|
786
|
+
* 免 root 免系统包,下载到 <数据根>/runtime/7zip/7zz(与 win-node 同模式)。
|
|
787
|
+
*
|
|
788
|
+
* 幂等:缓存已存在直接返回;tar -xJf 解压(Linux 自带 tar;需 xz-utils)。
|
|
789
|
+
*/
|
|
790
|
+
async function ensureLinuxSevenZip(options = {}) {
|
|
791
|
+
const dataRoot = resolveDataRootLight(options.dataRoot);
|
|
792
|
+
const cacheDir = join(dataRoot, "runtime", "7zip");
|
|
793
|
+
const exe = join(cacheDir, "7zz");
|
|
794
|
+
if (existsSync(exe)) return {
|
|
795
|
+
exe,
|
|
796
|
+
source: "数据根缓存 runtime/7zip"
|
|
797
|
+
};
|
|
798
|
+
const archive = join(cacheDir, `7z${SEVEN_ZIP_LINUX_VERSION}-linux-x64.tar.xz`);
|
|
799
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
800
|
+
await downloadFile({
|
|
801
|
+
dest: archive,
|
|
802
|
+
url: linuxSevenZipUrl()
|
|
803
|
+
});
|
|
804
|
+
try {
|
|
805
|
+
try {
|
|
806
|
+
execFileSync("tar", [
|
|
807
|
+
"-xJf",
|
|
808
|
+
archive,
|
|
809
|
+
"-C",
|
|
810
|
+
cacheDir
|
|
811
|
+
], { stdio: "pipe" });
|
|
812
|
+
} catch (err) {
|
|
813
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
814
|
+
throw new Error(`7z linux 解压失败: ${message}(需 tar + xz-utils;或设 NAPUTO_7Z_PATH 指定系统 7z)`);
|
|
815
|
+
}
|
|
816
|
+
} finally {
|
|
817
|
+
await rm(archive, { force: true });
|
|
818
|
+
}
|
|
819
|
+
if (!existsSync(exe)) throw new Error(`7z linux 解压后未找到 7zz: ${exe}`);
|
|
820
|
+
await chmod(exe, 493);
|
|
821
|
+
return {
|
|
822
|
+
exe,
|
|
823
|
+
source: "自动下载(7-Zip 官方 linux 版)"
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
760
827
|
* 确保 QQ 原生文件就绪(P1):幂等缓存检查 → 下载官方安装包 → sha256 校验
|
|
761
828
|
* → 7z 解包 → 提取 wrapper.node/QQNT.dll → 缓存。返回缓存版本 QqInstallInfo。
|
|
762
829
|
*/
|
|
@@ -777,9 +844,11 @@ async function ensureQqFiles(options = {}) {
|
|
|
777
844
|
url,
|
|
778
845
|
expectedSha256: release.sha256
|
|
779
846
|
});
|
|
847
|
+
let sevenZipPath = options.sevenZipPath;
|
|
848
|
+
if (sevenZipPath === void 0 && process$1.platform === "linux") sevenZipPath = (await ensureLinuxSevenZip({ dataRoot })).exe;
|
|
780
849
|
const extractedDir = join(tmpDir, `qq-extracted-${version}`);
|
|
781
850
|
try {
|
|
782
|
-
await extractInstaller(installerPath, extractedDir,
|
|
851
|
+
await extractInstaller(installerPath, extractedDir, sevenZipPath);
|
|
783
852
|
return await extractWrapperFiles(extractedDir, version, cacheRoot);
|
|
784
853
|
} catch (err) {
|
|
785
854
|
await clearCacheVersion(cacheRoot, version);
|
|
@@ -813,6 +882,6 @@ function findProjectRoot(start) {
|
|
|
813
882
|
}
|
|
814
883
|
}
|
|
815
884
|
//#endregion
|
|
816
|
-
export { DownloadError, ENV, INSTANCE_LOCK_FILE, QQ_FILES_DIR_NAME, QqReleasesError, WIN_NODE_DIR_NAME, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, latestRelease, launchSelfHost, loadQqReleases, locateQqPath, nodeZipUrl, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
|
|
885
|
+
export { DownloadError, ENV, INSTANCE_LOCK_FILE, QQ_FILES_DIR_NAME, QqReleasesError, WIN_NODE_DIR_NAME, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureLinuxSevenZip, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, latestRelease, launchSelfHost, linuxSevenZipUrl, loadQqReleases, locateQqPath, nodeZipUrl, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
|
|
817
886
|
|
|
818
887
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["process","process","process","process","findProjectRoot","process","wineBinary","process","process"],"sources":["../src/instance-lock.ts","../src/qq-download.ts","../src/qq-extract.ts","../src/win-node.ts","../src/wine.ts","../src/launcher.ts","../src/qq-releases.ts","../src/locate-qq.ts"],"sourcesContent":["/**\n * instance-lock.ts:单实例锁(2026-08-07 根治「多实例抢数据目录锁挂起」)\n *\n * 背景(实测):同一账号数据目录(~/.napuketto/<uin>)只能被一个实例持有——\n * QQ 原生层(MMKV / 登录单例 / 数据库)有锁,第二个实例抢不到锁会卡在\n * loginService.initConfig 之后无响应(挂起)。此前 create-napukettoqq 生成的\n * 副本实例常驻占锁,导致用户后续启动全部卡死。\n *\n * 方案:以数据目录为粒度加锁文件 `<dataDir>/instance.lock`(JSON 内容:\n * pid / startedAt / cmdline 摘要)。启动前 checkInstanceLock:\n * - 锁不存在 → 可启动(然后 acquireInstanceLock 写入)\n * - 锁存在但 PID 已死 → 崩溃残留,自动清理(rm + 重新获取)\n * - 锁存在且 PID 存活 → 已被占用,拒绝启动(返回占用 PID 供提示)\n *\n * 退出清理:进程正常退出时 releaseInstanceLock 删除锁文件;崩溃/强杀残留\n * 由下次启动的「PID 存活检测」兜底(PID 已死 → 残留视为过期)。\n *\n * 放置说明:放 loader 而非 kernel——self-host.cjs 是 rolldown 内联 bundle\n * (只内联 src/host/ 依赖树),静态 import kernel 会留 external 缺口;\n * 放 loader 则 self-host(内联)与 cli(@napuketto/loader 导入)双入口可用。\n */\n\nimport { readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\n\n/** 锁文件名(置于数据目录内)。 */\nexport const INSTANCE_LOCK_FILE = \"instance.lock\";\n\n/** 锁文件内容。 */\nexport interface InstanceLockInfo {\n /** 持有实例的进程 PID。 */\n pid: number;\n /** 获取锁的时间(epoch ms)。 */\n startedAt: number;\n /** 命令行摘要(诊断用,可选)。 */\n cmdline?: string;\n}\n\n/** 数据目录锁检查结果。 */\nexport interface InstanceLockCheck {\n /** 是否被其他实例占用。 */\n occupied: boolean;\n /** 占用进程 PID(occupied=true 时有效)。 */\n pid: number | null;\n /** 锁文件完整路径(提示/清理用)。 */\n lockPath: string;\n}\n\n/**\n * 检测指定数据目录是否已被其他实例占用。\n *\n * @param dataDir 数据目录(账号级,如 ~/.napuketto/3567141148)\n */\nexport function checkInstanceLock(dataDir: string): InstanceLockCheck {\n const lockPath = join(dataDir, INSTANCE_LOCK_FILE);\n let info: InstanceLockInfo | null = null;\n try {\n info = JSON.parse(readFileSync(lockPath, \"utf-8\")) as InstanceLockInfo;\n } catch {\n // 文件不存在 / 内容损坏 → 视为可启动(损坏锁由 acquire 覆盖)\n return { occupied: false, pid: null, lockPath };\n }\n if (typeof info.pid !== \"number\" || Number.isNaN(info.pid) || info.pid <= 0) {\n return { occupied: false, pid: null, lockPath };\n }\n // PID 存活判定(Windows 下 process.kill(pid, 0) 探测)\n let alive = false;\n try {\n process.kill(info.pid, 0);\n alive = true;\n } catch {\n alive = false;\n }\n if (!alive) {\n // 崩溃残留:持有者已死,可安全接管\n return { occupied: false, pid: null, lockPath };\n }\n return { occupied: true, pid: info.pid, lockPath };\n}\n\n/**\n * 获取数据目录锁。占用(其他实例存活)返回 false;成功或清理残留后返回 true。\n * 幂等:本进程已持有则直接返回 true(不重复写)。\n */\nexport function acquireInstanceLock(dataDir: string, cmdline?: string): boolean {\n const { occupied, pid, lockPath } = checkInstanceLock(dataDir);\n if (occupied) {\n // 已占用:除非占用者是自己(PID 复用保护),否则拒绝\n if (pid === process.pid) {\n return true;\n }\n return false;\n }\n // 残留锁(持有者已死 / 无锁):写入\n const info: InstanceLockInfo = {\n pid: process.pid,\n startedAt: Date.now(),\n ...(cmdline !== undefined && cmdline !== \"\" ? { cmdline } : {}),\n };\n try {\n rmSync(lockPath, { force: true });\n writeFileSync(lockPath, JSON.stringify(info), \"utf-8\");\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 释放数据目录锁(正常退出时调用)。仅当持有者是本进程时才删除,\n * 避免误删新持有者的锁(PID 复用 / 快速重启场景)。\n */\nexport function releaseInstanceLock(dataDir: string): void {\n const lockPath = join(dataDir, INSTANCE_LOCK_FILE);\n try {\n const info = JSON.parse(readFileSync(lockPath, \"utf-8\")) as InstanceLockInfo;\n if (info.pid === process.pid) {\n rmSync(lockPath, { force: true });\n }\n } catch {\n // 文件不存在 / 内容损坏 → 无需清理\n }\n}\n\n/** 注册进程退出清理(SIGINT/SIGTERM/exit 均触发;崩溃强杀由残留检测兜底)。 */\nexport function registerLockCleanup(dataDir: string): void {\n process.on(\"exit\", () => {\n releaseInstanceLock(dataDir);\n });\n}\n","/**\n * qq-download.ts:QQ 官方安装包下载器(P1)。\n *\n * 无第三方依赖:Node 内置 https + crypto(sha256)。设计文档 §2.2。\n * - https GET 流式写文件(低内存占用,313MB 安装包不整载入内存)\n * - 下载同时流式累加 sha256,完成后与清单比对(防链接漂移劫持 / 下载不完整)\n * - 支持 302 重定向(QQ CDN 可能跳转)\n * - NAPUTO_QQ_URL 环境变量可覆盖下载地址(用户拿到新链接时用)\n */\nimport { createHash } from \"node:crypto\";\nimport { createWriteStream, mkdirSync } from \"node:fs\";\nimport { rm } from \"node:fs/promises\";\nimport type { IncomingMessage } from \"node:http\";\nimport { get, type RequestOptions } from \"node:https\";\nimport { dirname } from \"node:path\";\nimport process from \"node:process\";\n\n/** 下载选项。 */\nexport interface DownloadOptions {\n /** 目标文件绝对路径。 */\n dest: string;\n /** 期望 sha256(十六进制小写);缺省/空串 = 不校验(首次下载尚无参考值)。 */\n expectedSha256?: string;\n /** 覆盖下载地址(缺省读 NAPUTO_QQ_URL)。 */\n url?: string;\n /** 超时(毫秒,默认 60s 无数据则中断)。 */\n timeoutMs?: number;\n}\n\n/** 下载结果。 */\nexport interface DownloadResult {\n /** 实际 sha256(十六进制小写)。 */\n sha256: string;\n /** 是否通过期望值校验(无期望值时恒 true)。 */\n verified: boolean;\n}\n\n/** 下载中断(超时 / 非 2xx / 网络错误)抛出的错误。 */\nexport class DownloadError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DownloadError\";\n }\n}\n\n/** 发起一次 https GET,返回响应(跟随重定向,最多 5 跳)。 */\nfunction httpsGet(url: string, timeoutMs: number): Promise<IncomingMessage> {\n return new Promise((resolve, reject) => {\n const request = (currentUrl: string, redirects: number): void => {\n const opts: RequestOptions = {\n headers: { \"user-agent\": \"napuketto-loader/0.0.x\" },\n };\n const req = get(currentUrl, opts, (res) => {\n const status = res.statusCode ?? 0;\n if (status >= 300 && status < 400 && res.headers[\"location\"] !== undefined) {\n res.resume(); // 释放连接\n if (redirects >= 5) {\n reject(new DownloadError(`重定向次数过多: ${currentUrl}`));\n return;\n }\n request(new URL(res.headers[\"location\"], currentUrl).toString(), redirects + 1);\n return;\n }\n if (status !== 200) {\n res.resume();\n reject(new DownloadError(`HTTP ${status}: ${currentUrl}`));\n return;\n }\n resolve(res);\n });\n req.setTimeout(timeoutMs, () => {\n req.destroy(new DownloadError(`下载超时(${timeoutMs}ms): ${currentUrl}`));\n });\n req.on(\"error\", (err) => {\n reject(err instanceof Error ? err : new DownloadError(String(err)));\n });\n };\n request(url, 0);\n });\n}\n\n/**\n * 下载文件并(可选)校验 sha256。失败时清理半成品文件。\n * 返回实际 sha256(无论是否校验),便于调用方回填清单。\n */\nexport async function downloadFile(options: DownloadOptions): Promise<DownloadResult> {\n const url = options.url ?? process.env[\"NAPUTO_QQ_URL\"];\n if (url === undefined || url === \"\") {\n throw new DownloadError(\"下载地址为空:请设置 NAPUTO_QQ_URL 或提供清单 URL\");\n }\n const timeoutMs = options.timeoutMs ?? 60_000;\n // 目标目录必须提前建好(createWriteStream 不自动建目录)\n mkdirSync(dirname(options.dest), { recursive: true });\n const hash = createHash(\"sha256\");\n const fileStream = createWriteStream(options.dest);\n let fileFailed = false;\n\n try {\n const res = await httpsGet(url, timeoutMs);\n await new Promise<void>((resolve, reject) => {\n res.on(\"data\", (chunk: Buffer) => {\n hash.update(chunk);\n });\n res.on(\"error\", (err: Error) => {\n fileFailed = true;\n reject(err instanceof Error ? err : new DownloadError(String(err)));\n });\n fileStream.on(\"error\", (err) => {\n fileFailed = true;\n reject(err);\n });\n fileStream.on(\"finish\", () => {\n resolve();\n });\n res.pipe(fileStream);\n });\n } catch (err) {\n fileStream.destroy();\n if (!fileFailed) {\n await rm(options.dest, { force: true });\n }\n throw err instanceof Error ? err : new DownloadError(String(err));\n }\n\n const sha256 = hash.digest(\"hex\");\n const expected = options.expectedSha256;\n const verified =\n expected === undefined || expected === \"\"\n ? true\n : sha256.toLowerCase() === expected.toLowerCase();\n if (!verified) {\n await rm(options.dest, { force: true });\n throw new DownloadError(\n `sha256 校验失败: ${options.dest}\\n期望 ${expected}\\n实际 ${sha256}`,\n );\n }\n return { sha256, verified };\n}\n","/**\n * qq-extract.ts:QQ 官方安装包解包与提取(P1)。\n *\n * ⚠️ 格式事实(2026-08-12 实测修正):QQ 官方安装包是 NSIS 自解压格式,\n * **7za standalone 不支持 NSIS**(只支持 7z/zip/tar 等)——必须用完整版\n * 7z(7z.exe + 7z.dll)。设计文档 §2.3 原「内置 7za.exe」修正为:\n * - Windows:内置 assets/7zip/7z.exe + 7z.dll(7-Zip 官方,LGPL 合规可分发)\n * - Linux/Docker:系统 p7zip-full(7z 完整版)\n *\n * ⚠️ 提取策略(2026-08-12 实测修正,与设计 §2.3 不同):\n * 1. NSIS 解包产物在 <extracted>/Files/(NSIS 内部布局),Files/ = 安装根\n * 2. 真实 QQNT.dll(214MB)位于 versions/<v>/QQNT.dll,**自建宿主不需要**\n * ——stub QQNT.dll 已替代其宿主符号职责(wrapper.node 的 v8/node/napi/qq_magic\n * 符号由 stub 转发到 node.exe)\n * 3. 只提取 resources/app 顶层 *.node + *.dll(wrapper.node 的直接/传递/delay-load\n * 依赖全覆盖,版本无关;跳过 wmpfsdk/avsdk 等大子目录,共约 235MB)\n *\n * 提取目标(与安装目录同构,resolveFromRoot 可直接消费):\n * <数据根>/qq-files/<版本>/versions/<版本>/resources/app/ ← *.node + *.dll\n */\nimport { execFile } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { copyFile, mkdir, readdir, rm } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport process from \"node:process\";\nimport { fileURLToPath } from \"node:url\";\nimport type { QqInstallInfo } from \"./locate-qq.js\";\n\n/** resources/app 下需要提取的文件扩展名(.node 原生模块 + .dll 依赖)。 */\nconst EXTRACT_EXTENSIONS = new Set([\".node\", \".dll\"]);\n\n/** 7-Zip 可执行文件来源。 */\nexport interface SevenZipResult {\n /** 7z 可执行文件绝对路径。 */\n exe: string;\n /** 来源说明(内置资产 / 系统 / 环境变量)。 */\n source: string;\n}\n\n/** 提取选项。 */\nexport interface ExtractOptions {\n /** 安装包绝对路径。 */\n installerPath: string;\n /** 目标版本目录(如 9.9.33-51802)。 */\n version: string;\n /** 缓存根目录(<数据根>/qq-files;版本子目录将建在其下)。 */\n cacheRoot: string;\n /** 7z 可执行文件路径(缺省自动探测)。 */\n sevenZipPath?: string;\n}\n\n/**\n * 探测 7z 可执行文件:\n * NAPUTO_7Z_PATH 环境变量 > 内置资产(assets/7zip/7z.exe,Windows)> 系统 PATH 7z。\n * Linux 上用 p7zip 提供的 7z 命令(系统 PATH)。\n */\nexport function findSevenZip(): SevenZipResult {\n const envPath = process.env[\"NAPUTO_7Z_PATH\"];\n if (envPath !== undefined && envPath !== \"\" && existsSync(envPath)) {\n return { exe: envPath, source: \"环境变量 NAPUTO_7Z_PATH\" };\n }\n // 内置资产:dist 与 assets 同层(tsdown 构建后 dist/,资产在包根 assets/)\n if (process.platform === \"win32\") {\n const bundled = join(\n dirname(fileURLToPath(import.meta.url)),\n \"..\",\n \"assets\",\n \"7zip\",\n \"7z.exe\",\n );\n if (existsSync(bundled)) {\n return { exe: bundled, source: \"内置资产 assets/7zip\" };\n }\n }\n // 系统 PATH(Windows 装了 7-Zip 有 7z.exe;Linux p7zip-full 提供 7z 命令)\n return { exe: \"7z\", source: \"系统 PATH\" };\n}\n\n/** 解包安装包到指定目录(spawn 7z x,失败抛错)。 */\nexport async function extractInstaller(\n installerPath: string,\n destDir: string,\n sevenZipPath?: string,\n): Promise<void> {\n const sevenZip = sevenZipPath ?? findSevenZip().exe;\n await mkdir(destDir, { recursive: true });\n await new Promise<void>((resolve, reject) => {\n execFile(\n sevenZip,\n [\"x\", installerPath, `-o${destDir}`, \"-y\"],\n { maxBuffer: 64 * 1024 * 1024 },\n (err, _stdout, stderr) => {\n if (err !== null) {\n const code = (err as NodeJS.ErrnoException).code;\n const hint =\n code === \"ENOENT\"\n ? `未找到 7z 可执行文件: ${sevenZip}\\n` +\n \"(Windows 请安装 7-Zip 或用 NAPUTO_7Z_PATH 指定;Linux 请安装 p7zip-full)\"\n : \"(QQ 安装包是 NSIS 格式,需完整版 7z 而非 7za)\";\n reject(new Error(`7z 解包失败: ${installerPath}\\n${stderr.trim()}\\n${hint}`));\n return;\n }\n resolve();\n },\n );\n });\n}\n\n/**\n * 从解包目录提取运行所需文件到缓存:\n * resources/app 顶层全部 *.node + *.dll(保持同目录结构,PATH 前置即可解析依赖)。\n * 幂等:目标 wrapper.node 已存在则跳过。\n */\nexport async function extractWrapperFiles(\n extractedDir: string,\n version: string,\n cacheRoot: string,\n): Promise<QqInstallInfo> {\n // NSIS 解包产物在 <extracted>/Files/(安装根),resources/app 在其下\n const appDir = join(extractedDir, \"Files\", \"versions\", version, \"resources\", \"app\");\n if (!existsSync(join(appDir, \"wrapper.node\"))) {\n throw new Error(`解包产物缺少 wrapper.node: ${appDir}`);\n }\n\n // 目标结构(与安装目录同构):\n // <cacheRoot>/<version>/versions/<version>/resources/app/*.{node,dll}\n const destAppDir = join(cacheRoot, version, \"versions\", version, \"resources\", \"app\");\n const destWrapper = join(destAppDir, \"wrapper.node\");\n\n // 幂等:已提取过则跳过\n if (existsSync(destWrapper)) {\n return buildCachedInfo(cacheRoot, version);\n }\n\n await mkdir(destAppDir, { recursive: true });\n const entries = await readdir(appDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isFile()) {\n continue; // 跳过子目录(wmpfsdk/avsdk 等大目录不需要)\n }\n const dot = entry.name.lastIndexOf(\".\");\n const ext = dot >= 0 ? entry.name.slice(dot).toLowerCase() : \"\";\n if (!EXTRACT_EXTENSIONS.has(ext)) {\n continue; // 只提取 .node + .dll\n }\n await copyFile(join(appDir, entry.name), join(destAppDir, entry.name));\n }\n if (!existsSync(destWrapper)) {\n throw new Error(`提取失败:wrapper.node 未复制到 ${destWrapper}`);\n }\n\n return buildCachedInfo(cacheRoot, version);\n}\n\n/** 构造 cached 来源的 QqInstallInfo(wrapperPath 指向缓存中 wrapper.node)。 */\nfunction buildCachedInfo(cacheRoot: string, version: string): QqInstallInfo {\n const versionDir = join(cacheRoot, version);\n return {\n qqPath: join(versionDir, \"QQ.exe\"), // 语义占位(缓存目录无 QQ.exe)\n installDir: versionDir,\n version,\n wrapperPath: join(versionDir, \"versions\", version, \"resources\", \"app\", \"wrapper.node\"),\n source: \"cached\",\n };\n}\n\n/** 清理缓存版本目录(下载/解包失败残留,供调用方兜底)。 */\nexport async function clearCacheVersion(cacheRoot: string, version: string): Promise<void> {\n await rm(join(cacheRoot, version), { recursive: true, force: true });\n}\n","/**\n * win-node.ts:Windows 版 node.exe 获取(P2,Linux/wine 场景用)。\n *\n * wine 只能跑 Windows PE 程序——自建宿主在 Linux 上必须用 Windows 版 node.exe\n * 作为宿主(wrapper.node 是 PE 原生模块,只能被 Windows node.exe dlopen)。\n * 来源:nodejs.org 官方 zip(开源软件官方发行版,但为保守起见同样不内置,\n * 运行时下载到 <数据根>/runtime/win-node/,可被 NAPUTO_WIN_NODE_PATH 覆盖)。\n *\n * 设计文档 §3.1。与 qq-download.ts 同构:https 下载 + 解压 + 缓存幂等。\n */\nimport { existsSync, mkdirSync } from \"node:fs\";\nimport { copyFile, readdir, rm } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { downloadFile } from \"./qq-download.js\";\nimport { extractInstaller } from \"./qq-extract.js\";\n\n/** 目标 Windows node.exe 可执行文件名(zip 内)。 */\nconst WIN_NODE_EXE = \"node.exe\";\n\n/** 运行时缓存子目录名(<数据根>/runtime/win-node/)。 */\nexport const WIN_NODE_DIR_NAME = \"win-node\";\n\n/** Windows node.exe 定位结果。 */\nexport interface WinNodeInfo {\n /** node.exe 绝对路径(Windows 视角,供 wine 调用)。 */\n exePath: string;\n /** 已下载的完整版 node zip 版本号(如 v24.16.0)。 */\n version: string;\n}\n\n/** 解析 nodejs.org zip URL(与 dist 目录命名一致)。 */\nexport function nodeZipUrl(version: string): string {\n return `https://nodejs.org/dist/${version}/node-${version}-win-x64.zip`;\n}\n\n/**\n * 确保 Windows 版 node.exe 就绪(下载 + 解压 + 缓存)。\n * 幂等:<数据根>/runtime/win-node/<version>/node.exe 已存在则直接返回。\n */\nexport async function ensureWinNode(\n options: {\n /** 数据根(缓存 <数据根>/runtime/win-node/)。 */\n dataRoot?: string;\n /** node 版本(缺省读 NAPUTO_WIN_NODE_VERSION,再兜底 latest v24)。 */\n version?: string;\n /** 显式 node.exe 路径(覆盖缓存逻辑,读 NAPUTO_WIN_NODE_PATH)。 */\n exePath?: string;\n } = {},\n): Promise<WinNodeInfo> {\n // 显式路径覆盖(NAPUTO_WIN_NODE_PATH / exePath 参数)\n const explicit = options.exePath ?? process.env[\"NAPUTO_WIN_NODE_PATH\"];\n if (explicit !== undefined && explicit !== \"\" && existsSync(explicit)) {\n return { exePath: resolve(explicit), version: \"explicit\" };\n }\n\n const version =\n options.version ?? process.env[\"NAPUTO_WIN_NODE_VERSION\"] ?? DEFAULT_WIN_NODE_VERSION;\n const dataRoot = resolveDataRoot(options.dataRoot);\n const cacheDir = join(dataRoot, \"runtime\", WIN_NODE_DIR_NAME, version);\n const exePath = join(cacheDir, WIN_NODE_EXE);\n if (existsSync(exePath)) {\n return { exePath, version };\n }\n\n // 下载 zip + 解压(用 P1 的 7z 解 zip;7z 支持 zip)\n const zipPath = join(cacheDir, `node-${version}-win-x64.zip`);\n const url = process.env[\"NAPUTO_WIN_NODE_URL\"] ?? nodeZipUrl(version);\n mkdirSync(cacheDir, { recursive: true });\n try {\n await downloadFile({ dest: zipPath, url });\n await unzipNode(zipPath, cacheDir);\n } finally {\n await rm(zipPath, { force: true });\n }\n if (!existsSync(exePath)) {\n throw new Error(`解压后未找到 node.exe: ${exePath}`);\n }\n return { exePath, version };\n}\n\n/** 解压 node zip(zip 内为 node-<version>-win-x64/ 目录,取其中 node.exe 移到缓存根)。 */\nasync function unzipNode(zipPath: string, cacheDir: string): Promise<void> {\n const tmpDir = join(cacheDir, \"_zip\");\n await extractInstaller(zipPath, tmpDir);\n // zip 顶层是 node-<version>-win-x64/,找其中 node.exe\n const top = await readdir(tmpDir);\n const nodeDir = top.find((n) => n.startsWith(\"node-\"));\n const src =\n nodeDir === undefined ? join(tmpDir, WIN_NODE_EXE) : join(tmpDir, nodeDir, WIN_NODE_EXE);\n if (!existsSync(src)) {\n throw new Error(`node zip 解压后未找到 node.exe: ${src}`);\n }\n await copyFile(src, join(cacheDir, WIN_NODE_EXE));\n await rm(tmpDir, { recursive: true, force: true });\n}\n\n/** 默认 Windows node 版本(latest v24,LTS 线;可被环境变量/参数覆盖)。 */\nconst DEFAULT_WIN_NODE_VERSION = \"v24.16.0\";\n\n/** 轻量数据根解析(与 locate-qq 内同构,避免循环依赖)。 */\nfunction resolveDataRoot(dataRoot?: string): string {\n const explicit = dataRoot ?? process.env[\"NAPKETTO_DATA\"];\n if (explicit !== undefined && explicit !== \"\") {\n return resolve(explicit);\n }\n const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();\n return join(projectRoot, \".napuketto\");\n}\n\n/** 向上探测项目根(含 pnpm-workspace.yaml 或 package.json 的目录)。 */\nfunction findProjectRoot(start: string): string | null {\n let dir = resolve(start);\n for (;;) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\")) || existsSync(join(dir, \"package.json\"))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n return null;\n }\n dir = parent;\n }\n}\n","/**\n * wine.ts:Linux 上经 wine 运行 Windows 版 node.exe 的路径与 spawn 组装(P2)。\n *\n * 设计文档 §3.2(2026-08-12 拍板确认)。核心认知:\n * - Windows 版 node.exe 在 wine 内把 Linux 根目录挂载在 `Z:\\`\n * - process.dlopen / 环境变量路径都是 wine 视角的 Windows 路径\n * - 传给 wine 子进程的**所有路径**(wrapperPath / cfgDir / stubDir / self-host.cjs / PATH)\n * 都要过 toWinePath 转成 `Z:\\...`\n *\n * 纯函数设计:不 import node:child_process,方便 Windows 上单测断言(P2 纯逻辑层)。\n */\n\n/** Linux 绝对路径 → wine `Z:\\` 路径(Windows 版 node.exe 视角)。幂等:已是 Windows 路径(含盘符)原样返回。 */\nexport function toWinePath(linuxPath: string): string {\n // 已是 Windows 风格(盘符 + 反斜杠)→ 原样(幂等保护,调用方不应传这种但防御性保留)\n if (DRIVE_LETTER_RE.test(linuxPath)) {\n return linuxPath;\n }\n // /app/.napuketto/qq-files/9.9.33/wrapper.node → Z:\\app\\.napuketto\\qq-files\\9.9.33\\wrapper.node\n // 去前导 /,正斜杠转反斜杠\n return `Z:\\\\${linuxPath.replaceAll(\"/\", \"\\\\\").replace(LEADING_SLASH_RE, \"\")}`;\n}\n\n/** 前导斜杠(toWinePath 去根用,顶层常量避免每次调用重建)。 */\nconst LEADING_SLASH_RE = /^\\\\/;\n\n/** 盘符前缀(C: / Z: 等,识别已转换的 Windows 路径)。 */\nconst DRIVE_LETTER_RE = /^[A-Za-z]:/;\n\n/** 判断当前是否 Linux(需 wine 的场景)。 */\nexport function isLinux(): boolean {\n return process.platform === \"linux\";\n}\n\n/** wine 可执行文件(Linux 上 spawn 用;可被 NAPUTO_WINE 覆盖)。 */\nexport function wineBinary(): string {\n return process.env[\"NAPUTO_WINE\"] ?? \"wine\";\n}\n\n/** spawn 命令与参数(平台分支;纯函数,可单测)。 */\nexport interface SpawnCommand {\n /** 命令(win32 = node.exe;linux = wine)。 */\n command: string;\n /** 参数(win32 = [selfHostPath];linux = [winNodePath, selfHostPath])。 */\n args: string[];\n}\n\n/** 组装 spawn 命令(win32 vs linux 分支)。 */\nexport function buildSpawnCommand(options: {\n /** 平台(缺省 process.platform,测试注入)。 */\n platform?: NodeJS.Platform;\n /** Windows 本机 node 路径(win32 用,缺省 process.execPath)。 */\n winNodePath?: string;\n /** wine 命令(linux 用,缺省 wineBinary())。 */\n wine?: string;\n /** 自建宿主入口(self-host.cjs,传给 wine/win node)。 */\n selfHostPath: string;\n}): SpawnCommand {\n const platform = options.platform ?? process.platform;\n if (platform === \"win32\") {\n return {\n command: options.winNodePath ?? process.execPath,\n args: [options.selfHostPath],\n };\n }\n // linux 及其他(wine 场景)\n return {\n command: options.wine ?? wineBinary(),\n args: [options.winNodePath ?? \"node.exe\", options.selfHostPath],\n };\n}\n","/**\n * launcher:自建宿主引导(2026-08-07 唯一路线)——spawn 标准 node 跑 self-host.cjs。\n *\n * 历史(V1/路线 B 事实链,已归档 archive/):\n * 1. wrapper.node 只能在 QQ 定制版 Electron 里注册(实测纯 Node/普通 Electron 均 self-register 失败)\n * 2. QQ 是打包应用,禁 NODE_OPTIONS(实测 stderr)\n * → 旧方案:NapukettoBootMain.exe 启动 QQ + 注入 NapukettoWinBootHook.dll(已废弃)\n * → 现方案:stub QQNT.dll 转发宿主符号 → 标准 node 直接 dlopen wrapper.node(自建宿主)\n *\n * P2(2026-08-12):平台分支——win32 本机 node;linux 经 wine 跑 Windows 版 node.exe\n * (ensureWinNode 下载)。所有传给 wine 子进程的路径过 toWinePath(Z:\\)。\n */\nimport { type StdioOptions, spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { fileURLToPath } from \"node:url\";\nimport type { QqInstallInfo } from \"./locate-qq.js\";\nimport { ensureWinNode } from \"./win-node.js\";\nimport { buildSpawnCommand, isLinux, toWinePath } from \"./wine.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport interface LaunchOptions {\n /** QQ 安装信息(qqPath/version/wrapperPath)。 */\n qq: QqInstallInfo;\n /** kernel 入口(.mjs,self-host.cjs 里 import)。 */\n kernelEntry: string;\n /** 配置目录(账号数据目录,NAPUTO_CFG_DIR)。 */\n cfgDir: string;\n /** 全局配置文件路径(项目根 napuketto.toml,注入 NAPKETTO_CONFIG 供装配链读取)。 */\n configPath?: string;\n /** adapter 入口(.mjs,协议装配用)。 */\n adapterEntry?: string;\n /** network 入口(.mjs,协议装配用)。 */\n networkEntry?: string;\n /** 自建宿主:标准 node + stub QQNT.dll 直接引导,不拉起 QQ(2026-08-07 唯一路线)。 */\n selfHost?: boolean;\n /** stub QQNT.dll 目录(PATH 前置,转发 napi_*、uv_* 符号到 node.exe)。 */\n stubDir?: string;\n /** 强制指定快速登录账号(注入 NAPUTO_QUICK_UIN;cli `-q <uin>` 透传,2026-08-07)。 */\n quickUin?: string;\n /** IPC 子进程模式(=1:stdout 走 JSON 行协议 + stdin 收 action/control,koishi 插件驱动)。 */\n ipc?: boolean;\n /** 自建宿主入口(默认 dist/host/self-host.cjs)。 */\n selfHostEntry?: string;\n /** Windows 版 node.exe 路径(linux 场景覆盖;缺省 ensureWinNode 下载)。 */\n winNodePath?: string;\n /**\n * 子进程工作目录(缺省继承父进程 cwd)。\n *\n * ⚠️ 必须指向数据根(~/.napuketto):QQ 原生层在账号上下文就绪前会\n * fallback 到进程 cwd 落盘(如频道库 guild1.db),不指定则污染调用方\n * 目录(实测写入项目根,guild1.db 08-07)。cli 传 dataRoot 根治。\n */\n cwd?: string;\n /**\n * 子进程 stdio(默认 \"inherit\")。cli 传 [\"inherit\",\"pipe\",\"pipe\"] 并接管\n * 子进程 stdout/stderr:逐行过滤 wrapper 原生噪音(MMKV 刷屏、Node 符号\n * 查找失败警告),其余转发——原生 printf 直写 fd 的字节无法从 JS 层拦截。\n */\n stdio?: StdioOptions;\n}\n\nexport interface LaunchResult {\n /** 子进程句柄(自建宿主 node 进程)。 */\n child: ReturnType<typeof spawn>;\n /** 自建宿主入口路径(self-host.cjs)。 */\n bootJsPath: string;\n}\n\n/**\n * 启动自建宿主(路线 A):spawn 标准 node 直接跑 self-host.cjs,不拉起 QQ / 不注入。\n *\n * 关键(HANDOVER-V7 技术数据):wrapper.node 从 QQNT.dll 导入 99 符号\n * (napi_*×40 + uv_*×56 + qq_magic×1 + v8/node mangled×2)——标准 node 无这些宿主\n * 符号,必须 PATH 前置 stub QQNT.dll 目录(转发到 node.exe)+ QQ resources\\app\n * (wrapper.node 同目录依赖 DLL)才能 dlopen 成功。\n *\n * P2 平台分支:win32 用本机 node;linux 用 wine + Windows 版 node.exe\n * (ensureWinNode 自动下载,路径过 toWinePath)。async(win-node 可能需下载)。\n */\nexport async function launchSelfHost(options: LaunchOptions): Promise<LaunchResult> {\n const selfHostPath =\n options.selfHostEntry ?? join(__dirname, \"..\", \"dist\", \"host\", \"self-host.cjs\");\n if (!existsSync(selfHostPath)) {\n throw new Error(\n `self-host.cjs 缺失: ${selfHostPath}(先运行 pnpm --filter @napuketto/loader build)`,\n );\n }\n\n // stub QQNT.dll 校验(自建宿主必需:PATH 前置 stub 转发 napi_* 到 node.exe,\n // 否则 wrapper.node dlopen 失败)。默认 loader 包内闭源 submodule native/stub-test-env,\n // 缺失时提示 --stub-dir / NAPUTO_STUB_DIR 指定。\n const stub = options.stubDir ?? defaultStubDir();\n if (!existsSync(join(stub, \"QQNT.dll\"))) {\n throw new Error(\n `stub QQNT.dll 未找到: ${stub}(自建宿主必需:stub 转发 QQNT.dll 符号到 node.exe;` +\n \"请用 --stub-dir 或环境变量 NAPUTO_STUB_DIR 指定 stub 目录)\",\n );\n }\n\n // 平台分支:win32 本机 node;linux wine + win-node(下载)\n const { useWine, winNodePath } = await resolveNodeExecutable(options);\n\n const env = buildLaunchEnv(options, useWine);\n applyPathEnv(env, { stub, wrapperPath: options.qq.wrapperPath, useWine });\n\n // 标准 node 直接跑入口(不拉起 QQ,不注入)\n // cwd 显式指向数据根(cli 传入):QQ 原生层 fallback 落盘(guild1.db 等)\n // 不再污染项目根目录(实测 08-07,GPro 模块初始化早于账号上下文)。\n if (options.cwd !== undefined) {\n mkdirSync(options.cwd, { recursive: true });\n }\n // spawn 命令:win32 = node.exe selfHostPath;linux = wine winNodePath selfHostPath\n // wine 场景 selfHostPath 也需转 Windows 路径(wine 内 node 读参数)\n const { command, args } = buildSpawnCommand({\n ...(useWine && winNodePath !== undefined\n ? { winNodePath }\n : { winNodePath: process.execPath }),\n selfHostPath: useWine ? toWinePath(selfHostPath) : selfHostPath,\n wine: wineBinary(),\n });\n const child = spawn(command, args, {\n // exactOptionalPropertyTypes:未传 cwd 时不显式写入 undefined(继承父进程)\n ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),\n env,\n stdio: options.stdio ?? \"inherit\",\n windowsHide: false,\n });\n\n return { child, bootJsPath: selfHostPath };\n}\n\n/** 平台分支解析 node 可执行:win32 本机;linux wine + Windows 版 node.exe。 */\nasync function resolveNodeExecutable(options: LaunchOptions): Promise<{\n useWine: boolean;\n winNodePath: string | undefined;\n}> {\n if (!isLinux()) {\n return { useWine: false, winNodePath: undefined };\n }\n const winNode = await ensureWinNode({\n ...(options.cwd !== undefined ? { dataRoot: options.cwd } : {}),\n ...(options.winNodePath !== undefined ? { exePath: options.winNodePath } : {}),\n });\n return { useWine: true, winNodePath: winNode.exePath };\n}\n\n/** 注入 PATH(stub 目录 + wrapper.node 目录前置)与 STUB_DIR(wine 场景转 Z:\\)。 */\nfunction applyPathEnv(\n env: Record<string, string>,\n opts: { stub: string; wrapperPath: string; useWine: boolean },\n): void {\n const p = (linuxPath: string): string => (opts.useWine ? toWinePath(linuxPath) : linuxPath);\n const pathEntries = [p(opts.stub), p(dirname(opts.wrapperPath)), process.env[\"PATH\"] ?? \"\"]\n .filter((entry) => entry !== undefined && entry !== \"\")\n .join(\";\");\n env[\"PATH\"] = pathEntries;\n env[ENV.STUB_DIR] = p(opts.stub);\n}\n\n/**\n * 默认 stub QQNT.dll 目录(loader 包内闭源 native/build/stub-test-env,开发机默认)。\n * src 与 native 同层,构建后 dist 与 native 同层——`../native` 恒正确。\n */\nexport function defaultStubDir(): string {\n return join(__dirname, \"..\", \"native\", \"build\", \"stub-test-env\");\n}\n\n/** 装配自建宿主环境变量(wine 场景路径转 Z:\\;win32 原样)。 */\nfunction buildLaunchEnv(options: LaunchOptions, useWine: boolean): Record<string, string> {\n // 配置目录兜底\n const cfg = resolve(options.cfgDir);\n mkdirSync(cfg, { recursive: true });\n\n // wine 场景:传给子进程的路径全转 Z:\\(node.exe 在 wine 内按 Windows 路径读)\n const p = (linuxPath: string): string => (useWine ? toWinePath(linuxPath) : linuxPath);\n const env: Record<string, string> = {\n ...process.env,\n [ENV.QQ_PATH]: p(options.qq.qqPath),\n [ENV.KERNEL_ENTRY]: p(resolve(options.kernelEntry)),\n [ENV.CFG_DIR]: p(cfg),\n [ENV.QQ_VERSION]: options.qq.version,\n [ENV.WRAPPER_PATH]: p(options.qq.wrapperPath),\n };\n // 可选注入(对象展开,保持低复杂度)\n const optional: Record<string, string> = {\n ...(options.selfHost === true ? { [ENV.SELF_HOST]: \"1\" } : {}),\n ...(options.adapterEntry !== undefined\n ? { [ENV.ADAPTER_ENTRY]: p(resolve(options.adapterEntry)) }\n : {}),\n ...(options.networkEntry !== undefined\n ? { [ENV.NETWORK_ENTRY]: p(resolve(options.networkEntry)) }\n : {}),\n ...(options.configPath !== undefined\n ? { [ENV.CONFIG_PATH]: p(resolve(options.configPath)) }\n : {}),\n ...(options.quickUin !== undefined ? { [ENV.QUICK_UIN]: options.quickUin } : {}),\n ...(options.ipc === true ? { [ENV.IPC]: \"1\" } : {}),\n };\n return { ...env, ...optional };\n}\n\n/** wine 可执行文件(linux 场景;NAPUTO_WINE 可覆盖)。 */\nfunction wineBinary(): string {\n return process.env[\"NAPUTO_WINE\"] ?? \"wine\";\n}\n\n/** 环境变量名(self-host.cjs 与 kernel 引导读取)。 */\nexport const ENV = {\n QQ_PATH: \"NAPUTO_QQ_PATH\",\n KERNEL_ENTRY: \"NAPUTO_KERNEL_ENTRY\",\n CFG_DIR: \"NAPUTO_CFG_DIR\",\n QQ_VERSION: \"NAPUTO_QQ_VERSION\",\n WRAPPER_PATH: \"NAPUTO_WRAPPER_PATH\",\n /** 自建宿主:标准 node + stub QQNT.dll 引导(self-host.cjs 分支)。 */\n SELF_HOST: \"NAPUTO_SELF_HOST\",\n /** stub QQNT.dll 目录(launchSelfHost 注入,self-host.cjs 诊断用)。 */\n STUB_DIR: \"NAPUTO_STUB_DIR\",\n /** adapter 包入口(协议装配用)。 */\n ADAPTER_ENTRY: \"NAPUTO_ADAPTER_ENTRY\",\n /** network 包入口(协议装配用)。 */\n NETWORK_ENTRY: \"NAPUTO_NETWORK_ENTRY\",\n /** 全局配置文件路径(项目根 napuketto.toml,boot-protocols 读取)。 */\n CONFIG_PATH: \"NAPKETTO_CONFIG\",\n /** 强制指定快速登录账号(cli `-q <uin>` 透传,bootstrap 登录用)。 */\n QUICK_UIN: \"NAPUTO_QUICK_UIN\",\n /** IPC 子进程模式(koishi 插件驱动:stdout JSON 行 + stdin action/control)。 */\n IPC: \"NAPUTO_IPC\",\n} as const;\n","/**\n * qq-releases.ts:版本清单读取(P1)。\n *\n * 运行时从包根 qq-releases.json 读取(发布时 files 含它,安装后位于\n * node_modules/@napuketto/loader/qq-releases.json)。清单由 CI/社区 PR 维护\n * (设计文档 §2.2)。NAPUTO_QQ_URL 可运行时覆盖下载地址。\n */\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport process from \"node:process\";\nimport { fileURLToPath } from \"node:url\";\n\n/** 清单中单个版本条目。 */\nexport interface QqReleaseEntry {\n /** 版本目录名(如 9.9.33-51802)。 */\n version: string;\n /** 官方安装包下载 URL。 */\n url: string;\n /** sha256(十六进制小写);空串 = 尚未校验(首次录入时 CI 计算后填入)。 */\n sha256: string;\n /** appid(预知信息,运行时仍从 major.node 解析)。 */\n appid: number;\n /** 来源标记(official = 官方)。 */\n source: string;\n /** 构建日期(YYYY-MM-DD,可选)。 */\n buildDate?: string;\n}\n\n/** 清单文件结构。 */\nexport interface QqReleasesFile {\n schema: number;\n known: QqReleaseEntry[];\n}\n\n/** 清单解析失败。 */\nexport class QqReleasesError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"QqReleasesError\";\n }\n}\n\n/** 包根路径(dist 与包根同层:dist/index.mjs → ../qq-releases.json)。 */\nfunction packageRoot(): string {\n return dirname(dirname(fileURLToPath(import.meta.url)));\n}\n\n/** 读版本清单(同步,启动期调用一次即可)。 */\nexport function loadQqReleases(): QqReleasesFile {\n const file = join(packageRoot(), \"qq-releases.json\");\n let raw: string;\n try {\n raw = readFileSync(file, \"utf8\");\n } catch {\n throw new QqReleasesError(`版本清单缺失: ${file}(@napuketto/loader 包安装不完整?)`);\n }\n try {\n const parsed = JSON.parse(raw) as Partial<QqReleasesFile>;\n if (parsed.schema !== 1 || !Array.isArray(parsed.known) || parsed.known.length === 0) {\n throw new Error(\"结构不合法\");\n }\n return parsed as QqReleasesFile;\n } catch {\n throw new QqReleasesError(`版本清单解析失败: ${file}`);\n }\n}\n\n/** 取最新已知可用版本(known 数组按录入顺序,取最后一项 = 最新)。 */\nexport function latestRelease(releases: QqReleasesFile): QqReleaseEntry {\n const [latest] = [...releases.known].reverse();\n if (latest === undefined) {\n throw new QqReleasesError(\"版本清单为空\");\n }\n return latest;\n}\n\n/**\n * 解析实际下载 URL:NAPUTO_QQ_URL 环境变量覆盖 > 清单 URL。\n * 供 downloadFile 使用(也支持按版本查询,当前固定取最新)。\n */\nexport function resolveDownloadUrl(release: QqReleaseEntry): string {\n const override = process.env[\"NAPUTO_QQ_URL\"];\n return override !== undefined && override !== \"\" ? override : release.url;\n}\n","/**\n * 定位 QQ 原生文件(wrapper.node)来源(P0:多级来源纯定位,不下载)。\n *\n * 来源优先级(resolveQqFiles):\n * L0: NAPUTO_QQ_FILES / qqFilesDir —— 显式指定含 versions/ 结构的文件根(缓存/拷贝)\n * L1: 本机 QQ 安装 —— NAPUTO_QQ_PATH / 注册表 / 常见路径(既有逻辑保留)\n * L2: 数据根缓存 <数据根>/qq-files/<版本> —— 下载解包产物(P1 落地后才有)\n */\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, mkdirSync, readdirSync, statSync } from \"node:fs\";\nimport { rm } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { downloadFile } from \"./qq-download.js\";\nimport { clearCacheVersion, extractInstaller, extractWrapperFiles } from \"./qq-extract.js\";\nimport { latestRelease, loadQqReleases, resolveDownloadUrl } from \"./qq-releases.js\";\n\n/** 数据根下 QQ 文件缓存目录名(<数据根>/qq-files/<版本>/,P1 下载解包产物)。 */\nexport const QQ_FILES_DIR_NAME = \"qq-files\";\n\n/** 注册表 UninstallString 查询(QQ 官方安装路径)。 */\nconst REG_QUERY_RE = /\"([^\"]+)\"/;\n\n/** Windows 盘符路径正则(`C:/...` / `C:\\...`,wslMappedPath 映射用,模块级常量)。 */\nconst DRIVE_PATH_RE = /^([A-Za-z]):[\\\\/](.*)$/;\n\n/** 注册表 UninstallString 查询(QQ 官方安装路径)。 */\nfunction findQqViaRegistry(): string | null {\n try {\n const out = execFileSync(\"reg\", [\n \"query\",\n \"HKLM\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\QQ\",\n \"/v\",\n \"UninstallString\",\n ]).toString();\n const m = out.match(REG_QUERY_RE);\n const exe = m?.[1];\n if (exe !== undefined) {\n // UninstallString 形如 \"C:\\...\\uninst.exe\" 或带参数\n const dir = exe.replaceAll('\"', \"\").slice(0, exe.lastIndexOf(\"\\\\\"));\n const qq = join(dir, \"QQ.exe\");\n if (existsSync(qq)) {\n return qq;\n }\n }\n } catch {\n // 注册表查询失败,走常见路径\n }\n return null;\n}\n\n/** 常见安装路径探测。 */\nfunction findQqViaCommonPaths(): string | null {\n const localAppData = process.env[\"LOCALAPPDATA\"] ?? \"\";\n const candidates = [\n \"C:/Program Files/Tencent/QQNT/QQ.exe\",\n \"C:/Program Files (x86)/Tencent/QQNT/QQ.exe\",\n join(localAppData, \"Programs\", \"Tencent\", \"QQNT\", \"QQ.exe\"),\n // 开发机目录(2026-08-07 环境事实:QQ 9.9.33-51802 在 C:\\Dev\\QQBot-Dev\\QQNT)\n \"C:/Dev/QQBot-Dev/QQNT/QQ.exe\",\n ];\n for (const p of candidates) {\n // WSL 场景:Windows 盘挂载在 /mnt/<盘符>/(如 C:\\ → /mnt/c/),\n // 同一候选同时探测 Linux 侧映射路径(2026-08-13 koishi-app 实测报「未找到 QQ.exe」)。\n const wsl = wslMappedPath(p);\n if (existsSync(wsl)) {\n return wsl;\n }\n if (existsSync(p)) {\n return p;\n }\n }\n return null;\n}\n\n/**\n * Windows 路径 → WSL 挂载路径(`C:/...` → `/mnt/c/...`;非 Linux 或非盘符路径原样)。\n * WSL 默认把 Windows 盘符挂载到 `/mnt/<小写盘符>/`,Linux 侧 `existsSync` 必须用映射路径。\n * platform 参数可注入(测试用;缺省 process.platform)。\n * 导出便于单测(locate-qq.test.ts)。\n * ⚠️ 不用 join 拼 `/mnt/...`:测试在 Windows 跑,win32 join 会产生反斜杠;目标路径\n * 是 Linux 侧路径(WSL 挂载点),必须恒正斜杠。\n */\nexport function wslMappedPath(\n winPath: string,\n platform: NodeJS.Platform = process.platform,\n): string {\n if (platform !== \"linux\") {\n return winPath;\n }\n const [, drive, rest] = DRIVE_PATH_RE.exec(winPath) ?? [];\n if (drive === undefined || rest === undefined) {\n return winPath;\n }\n return `/mnt/${drive.toLowerCase()}/${rest.replaceAll(\"\\\\\", \"/\")}`;\n}\n\n/** QQ 文件来源。 */\nexport type QqFileSource = \"local\" | \"cached\";\n\n/** QQ 安装目录探测结果。 */\nexport interface QqInstallInfo {\n /** QQ.exe 绝对路径(cached 来源为语义占位——缓存目录无 QQ.exe,当前无消费方)。 */\n qqPath: string;\n /** 安装根目录 / 文件根目录。 */\n installDir: string;\n /** 当前版本目录名(如 9.9.31-49919)。 */\n version: string;\n /** wrapper.node 绝对路径。 */\n wrapperPath: string;\n /** 来源:local = 本机安装;cached = NAPUTO_QQ_FILES / 数据根缓存。 */\n source: QqFileSource;\n}\n\n/** resolveQqFiles 选项。 */\nexport interface ResolveQqFilesOptions {\n /** 显式文件根(L0;缺省读 NAPUTO_QQ_FILES)。 */\n qqFilesDir?: string;\n /** 显式 QQ.exe 路径(L1;缺省读 NAPUTO_QQ_PATH)。 */\n qqPath?: string;\n /** 数据根(L2 缓存扫描;缺省轻量解析 NAPKETTO_DATA ?? 项目根/.napuketto)。 */\n dataRoot?: string;\n /** L0/L1/L2 全部失败时自动进入下载流程(P1;默认 true,测试可关)。 */\n autoDownload?: boolean;\n /** 7z 可执行文件路径(解包用;缺省自动探测)。 */\n sevenZipPath?: string;\n}\n\n/** 找 QQ.exe 路径。 */\nexport function locateQqPath(): string {\n const viaReg = findQqViaRegistry();\n if (viaReg) {\n return viaReg;\n }\n const viaCommon = findQqViaCommonPaths();\n if (viaCommon) {\n return viaCommon;\n }\n throw new Error(\"未找到 QQ.exe(请先安装 QQ,或通过 NAPUTO_QQ_PATH 指定)\");\n}\n\n/** 从文件根定位最新版本 wrapper.node(local/cached 共用)。 */\nfunction resolveFromRoot(rootDir: string, source: QqFileSource): QqInstallInfo {\n const versionsDir = join(rootDir, \"versions\");\n if (!existsSync(versionsDir)) {\n throw new Error(`QQ 版本目录不存在: ${versionsDir}`);\n }\n const versions = readdirSync(versionsDir)\n .filter((v) => statSync(join(versionsDir, v)).isDirectory())\n .sort()\n .reverse();\n const [first] = versions;\n if (first === undefined) {\n throw new Error(`QQ 版本目录为空: ${versionsDir}`);\n }\n const wrapperPath = join(versionsDir, first, \"resources\", \"app\", \"wrapper.node\");\n if (!existsSync(wrapperPath)) {\n throw new Error(`未找到 wrapper.node: ${wrapperPath}`);\n }\n return {\n qqPath: join(rootDir, \"QQ.exe\"),\n installDir: rootDir,\n version: first,\n wrapperPath,\n source,\n };\n}\n\n/** 从 QQ.exe 路径推导安装目录,并探测当前版本(L1:本机安装)。 */\nexport function resolveQqInstall(qqPath?: string): QqInstallInfo {\n const qq = qqPath ?? process.env[\"NAPUTO_QQ_PATH\"] ?? locateQqPath();\n // Windows 安装结构:<installDir>/versions/<版本>/resources/app/wrapper.node\n // ⚠️ dirname 跨平台:Linux/WSL 路径用正斜杠(lastIndexOf(\"\\\\\") 会切错,2026-08-13 实测)\n const installDir = dirname(qq);\n return resolveFromRoot(installDir, \"local\");\n}\n\n/**\n * 多级来源定位 QQ 原生文件:\n * L0 → L1 → L2 依次尝试,全部失败且 autoDownload 时自动进入下载流程(ensureQqFiles)。\n * ⚠️ async:自动下载为异步流程(P1 起);纯定位场景用 resolveQqInstall(同步)。\n */\nexport async function resolveQqFiles(options: ResolveQqFilesOptions = {}): Promise<QqInstallInfo> {\n // L0:显式文件根(参数 > 环境变量)\n const explicitDir = options.qqFilesDir ?? process.env[\"NAPUTO_QQ_FILES\"];\n if (explicitDir !== undefined && explicitDir !== \"\") {\n return resolveFromRoot(resolve(explicitDir), \"cached\");\n }\n // L1:本机安装(参数 > 环境变量 > 注册表/常见路径)\n const qqPath = options.qqPath ?? process.env[\"NAPUTO_QQ_PATH\"];\n if (qqPath !== undefined && qqPath !== \"\") {\n return resolveQqInstall(qqPath);\n }\n try {\n return resolveQqInstall();\n } catch {\n // 本机未安装 → 落 L2\n }\n // L2:数据根缓存(P1 下载解包产物;结构 <数据根>/qq-files/<版本>/)\n const dataRoot = resolveDataRootLight(options.dataRoot);\n const cached = tryResolveCached(dataRoot);\n if (cached !== null) {\n return cached;\n }\n // 全部缺失 → 自动下载(P1)\n if (options.autoDownload !== false) {\n const sevenZipPath =\n options.sevenZipPath !== undefined ? { sevenZipPath: options.sevenZipPath } : {};\n return ensureQqFiles({ dataRoot, ...sevenZipPath });\n }\n throw new Error(\n \"未找到 QQ 原生文件(wrapper.node):请安装 QQ、设置 NAPUTO_QQ_PATH/NAPUTO_QQ_FILES,\" +\n \"或使用下载流程(ensureQqFiles)自动获取\",\n );\n}\n\n/** 扫描数据根缓存(L2);命中返回,未命中返回 null。 */\nfunction tryResolveCached(dataRoot: string): QqInstallInfo | null {\n const cacheRoot = join(dataRoot, QQ_FILES_DIR_NAME);\n if (!existsSync(cacheRoot)) {\n return null;\n }\n const versionDirs = readdirSync(cacheRoot)\n .filter((v) => statSync(join(cacheRoot, v)).isDirectory())\n .sort()\n .reverse();\n for (const versionDir of versionDirs) {\n try {\n return resolveFromRoot(join(cacheRoot, versionDir), \"cached\");\n } catch {\n // 该版本目录结构不完整(如下载中断残留),尝试下一个\n }\n }\n return null;\n}\n\n/**\n * 确保 QQ 原生文件就绪(P1):幂等缓存检查 → 下载官方安装包 → sha256 校验\n * → 7z 解包 → 提取 wrapper.node/QQNT.dll → 缓存。返回缓存版本 QqInstallInfo。\n */\nexport async function ensureQqFiles(\n options: {\n /** 数据根(缓存 <数据根>/qq-files/)。 */\n dataRoot?: string;\n /** 7z 路径(缺省自动探测)。 */\n sevenZipPath?: string;\n } = {},\n): Promise<QqInstallInfo> {\n const dataRoot = resolveDataRootLight(options.dataRoot);\n const cacheRoot = join(dataRoot, QQ_FILES_DIR_NAME);\n mkdirSync(cacheRoot, { recursive: true });\n\n // 幂等:缓存已有完整版本 → 直接返回\n const existing = tryResolveCached(dataRoot);\n if (existing !== null) {\n return existing;\n }\n\n // 1. 版本清单 → 最新可用版本\n const releases = loadQqReleases();\n const release = latestRelease(releases);\n const url = resolveDownloadUrl(release);\n const version = release.version;\n\n // 2. 下载 + sha256 校验(清单无参考值时下载器跳过校验,完整性由解包/加载兜底)\n const tmpDir = join(dataRoot, \"tmp\");\n mkdirSync(tmpDir, { recursive: true });\n const installerPath = join(tmpDir, `qq-installer-${version}.exe`);\n await downloadFile({ dest: installerPath, url, expectedSha256: release.sha256 });\n\n // 3. 解包 + 提取(失败清理缓存残留;临时文件无论如何清理)\n const extractedDir = join(tmpDir, `qq-extracted-${version}`);\n try {\n await extractInstaller(installerPath, extractedDir, options.sevenZipPath);\n const info = await extractWrapperFiles(extractedDir, version, cacheRoot);\n return info;\n } catch (err) {\n await clearCacheVersion(cacheRoot, version);\n throw err;\n } finally {\n await rm(installerPath, { force: true });\n await rm(extractedDir, { recursive: true, force: true });\n }\n}\n\n/**\n * 轻量数据根解析(loader 侧,避免新增 kernel 依赖;P2 若需完整语义再切换)。\n * 优先级:显式参数 > NAPKETTO_DATA > 项目根/.napuketto > cwd 兜底。\n */\nfunction resolveDataRootLight(dataRoot?: string): string {\n const explicit = dataRoot ?? process.env[\"NAPKETTO_DATA\"];\n if (explicit !== undefined && explicit !== \"\") {\n return resolve(explicit);\n }\n const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();\n return join(projectRoot, \".napuketto\");\n}\n\n/** 向上探测项目根(含 pnpm-workspace.yaml 或 package.json 的目录)。 */\nfunction findProjectRoot(start: string): string | null {\n let dir = resolve(start);\n for (;;) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\")) || existsSync(join(dir, \"package.json\"))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n return null;\n }\n dir = parent;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["process","process","process","process","findProjectRoot","process","wineBinary","process","process"],"sources":["../src/instance-lock.ts","../src/qq-download.ts","../src/qq-extract.ts","../src/win-node.ts","../src/wine.ts","../src/launcher.ts","../src/qq-releases.ts","../src/locate-qq.ts"],"sourcesContent":["/**\n * instance-lock.ts:单实例锁(2026-08-07 根治「多实例抢数据目录锁挂起」)\n *\n * 背景(实测):同一账号数据目录(~/.napuketto/<uin>)只能被一个实例持有——\n * QQ 原生层(MMKV / 登录单例 / 数据库)有锁,第二个实例抢不到锁会卡在\n * loginService.initConfig 之后无响应(挂起)。此前 create-napukettoqq 生成的\n * 副本实例常驻占锁,导致用户后续启动全部卡死。\n *\n * 方案:以数据目录为粒度加锁文件 `<dataDir>/instance.lock`(JSON 内容:\n * pid / startedAt / cmdline 摘要)。启动前 checkInstanceLock:\n * - 锁不存在 → 可启动(然后 acquireInstanceLock 写入)\n * - 锁存在但 PID 已死 → 崩溃残留,自动清理(rm + 重新获取)\n * - 锁存在且 PID 存活 → 已被占用,拒绝启动(返回占用 PID 供提示)\n *\n * 退出清理:进程正常退出时 releaseInstanceLock 删除锁文件;崩溃/强杀残留\n * 由下次启动的「PID 存活检测」兜底(PID 已死 → 残留视为过期)。\n *\n * 放置说明:放 loader 而非 kernel——self-host.cjs 是 rolldown 内联 bundle\n * (只内联 src/host/ 依赖树),静态 import kernel 会留 external 缺口;\n * 放 loader 则 self-host(内联)与 cli(@napuketto/loader 导入)双入口可用。\n */\n\nimport { readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\n\n/** 锁文件名(置于数据目录内)。 */\nexport const INSTANCE_LOCK_FILE = \"instance.lock\";\n\n/** 锁文件内容。 */\nexport interface InstanceLockInfo {\n /** 持有实例的进程 PID。 */\n pid: number;\n /** 获取锁的时间(epoch ms)。 */\n startedAt: number;\n /** 命令行摘要(诊断用,可选)。 */\n cmdline?: string;\n}\n\n/** 数据目录锁检查结果。 */\nexport interface InstanceLockCheck {\n /** 是否被其他实例占用。 */\n occupied: boolean;\n /** 占用进程 PID(occupied=true 时有效)。 */\n pid: number | null;\n /** 锁文件完整路径(提示/清理用)。 */\n lockPath: string;\n}\n\n/**\n * 检测指定数据目录是否已被其他实例占用。\n *\n * @param dataDir 数据目录(账号级,如 ~/.napuketto/3567141148)\n */\nexport function checkInstanceLock(dataDir: string): InstanceLockCheck {\n const lockPath = join(dataDir, INSTANCE_LOCK_FILE);\n let info: InstanceLockInfo | null = null;\n try {\n info = JSON.parse(readFileSync(lockPath, \"utf-8\")) as InstanceLockInfo;\n } catch {\n // 文件不存在 / 内容损坏 → 视为可启动(损坏锁由 acquire 覆盖)\n return { occupied: false, pid: null, lockPath };\n }\n if (typeof info.pid !== \"number\" || Number.isNaN(info.pid) || info.pid <= 0) {\n return { occupied: false, pid: null, lockPath };\n }\n // PID 存活判定(Windows 下 process.kill(pid, 0) 探测)\n let alive = false;\n try {\n process.kill(info.pid, 0);\n alive = true;\n } catch {\n alive = false;\n }\n if (!alive) {\n // 崩溃残留:持有者已死,可安全接管\n return { occupied: false, pid: null, lockPath };\n }\n return { occupied: true, pid: info.pid, lockPath };\n}\n\n/**\n * 获取数据目录锁。占用(其他实例存活)返回 false;成功或清理残留后返回 true。\n * 幂等:本进程已持有则直接返回 true(不重复写)。\n */\nexport function acquireInstanceLock(dataDir: string, cmdline?: string): boolean {\n const { occupied, pid, lockPath } = checkInstanceLock(dataDir);\n if (occupied) {\n // 已占用:除非占用者是自己(PID 复用保护),否则拒绝\n if (pid === process.pid) {\n return true;\n }\n return false;\n }\n // 残留锁(持有者已死 / 无锁):写入\n const info: InstanceLockInfo = {\n pid: process.pid,\n startedAt: Date.now(),\n ...(cmdline !== undefined && cmdline !== \"\" ? { cmdline } : {}),\n };\n try {\n rmSync(lockPath, { force: true });\n writeFileSync(lockPath, JSON.stringify(info), \"utf-8\");\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 释放数据目录锁(正常退出时调用)。仅当持有者是本进程时才删除,\n * 避免误删新持有者的锁(PID 复用 / 快速重启场景)。\n */\nexport function releaseInstanceLock(dataDir: string): void {\n const lockPath = join(dataDir, INSTANCE_LOCK_FILE);\n try {\n const info = JSON.parse(readFileSync(lockPath, \"utf-8\")) as InstanceLockInfo;\n if (info.pid === process.pid) {\n rmSync(lockPath, { force: true });\n }\n } catch {\n // 文件不存在 / 内容损坏 → 无需清理\n }\n}\n\n/** 注册进程退出清理(SIGINT/SIGTERM/exit 均触发;崩溃强杀由残留检测兜底)。 */\nexport function registerLockCleanup(dataDir: string): void {\n process.on(\"exit\", () => {\n releaseInstanceLock(dataDir);\n });\n}\n","/**\n * qq-download.ts:QQ 官方安装包下载器(P1)。\n *\n * 无第三方依赖:Node 内置 https + crypto(sha256)。设计文档 §2.2。\n * - https GET 流式写文件(低内存占用,313MB 安装包不整载入内存)\n * - 下载同时流式累加 sha256,完成后与清单比对(防链接漂移劫持 / 下载不完整)\n * - 支持 302 重定向(QQ CDN 可能跳转)\n * - NAPUTO_QQ_URL 环境变量可覆盖下载地址(用户拿到新链接时用)\n */\nimport { createHash } from \"node:crypto\";\nimport { createWriteStream, mkdirSync } from \"node:fs\";\nimport { rm } from \"node:fs/promises\";\nimport type { IncomingMessage } from \"node:http\";\nimport { get, type RequestOptions } from \"node:https\";\nimport { dirname } from \"node:path\";\nimport process from \"node:process\";\n\n/** 下载选项。 */\nexport interface DownloadOptions {\n /** 目标文件绝对路径。 */\n dest: string;\n /** 期望 sha256(十六进制小写);缺省/空串 = 不校验(首次下载尚无参考值)。 */\n expectedSha256?: string;\n /** 覆盖下载地址(缺省读 NAPUTO_QQ_URL)。 */\n url?: string;\n /** 超时(毫秒,默认 60s 无数据则中断)。 */\n timeoutMs?: number;\n}\n\n/** 下载结果。 */\nexport interface DownloadResult {\n /** 实际 sha256(十六进制小写)。 */\n sha256: string;\n /** 是否通过期望值校验(无期望值时恒 true)。 */\n verified: boolean;\n}\n\n/** 下载中断(超时 / 非 2xx / 网络错误)抛出的错误。 */\nexport class DownloadError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DownloadError\";\n }\n}\n\n/** 发起一次 https GET,返回响应(跟随重定向,最多 5 跳)。 */\nfunction httpsGet(url: string, timeoutMs: number): Promise<IncomingMessage> {\n return new Promise((resolve, reject) => {\n const request = (currentUrl: string, redirects: number): void => {\n const opts: RequestOptions = {\n headers: { \"user-agent\": \"napuketto-loader/0.0.x\" },\n };\n const req = get(currentUrl, opts, (res) => {\n const status = res.statusCode ?? 0;\n if (status >= 300 && status < 400 && res.headers[\"location\"] !== undefined) {\n res.resume(); // 释放连接\n if (redirects >= 5) {\n reject(new DownloadError(`重定向次数过多: ${currentUrl}`));\n return;\n }\n request(new URL(res.headers[\"location\"], currentUrl).toString(), redirects + 1);\n return;\n }\n if (status !== 200) {\n res.resume();\n reject(new DownloadError(`HTTP ${status}: ${currentUrl}`));\n return;\n }\n resolve(res);\n });\n req.setTimeout(timeoutMs, () => {\n req.destroy(new DownloadError(`下载超时(${timeoutMs}ms): ${currentUrl}`));\n });\n req.on(\"error\", (err) => {\n reject(err instanceof Error ? err : new DownloadError(String(err)));\n });\n };\n request(url, 0);\n });\n}\n\n/**\n * 下载文件并(可选)校验 sha256。失败时清理半成品文件。\n * 返回实际 sha256(无论是否校验),便于调用方回填清单。\n */\nexport async function downloadFile(options: DownloadOptions): Promise<DownloadResult> {\n const url = options.url ?? process.env[\"NAPUTO_QQ_URL\"];\n if (url === undefined || url === \"\") {\n throw new DownloadError(\"下载地址为空:请设置 NAPUTO_QQ_URL 或提供清单 URL\");\n }\n const timeoutMs = options.timeoutMs ?? 60_000;\n // 目标目录必须提前建好(createWriteStream 不自动建目录)\n mkdirSync(dirname(options.dest), { recursive: true });\n const hash = createHash(\"sha256\");\n const fileStream = createWriteStream(options.dest);\n let fileFailed = false;\n\n try {\n const res = await httpsGet(url, timeoutMs);\n await new Promise<void>((resolve, reject) => {\n res.on(\"data\", (chunk: Buffer) => {\n hash.update(chunk);\n });\n res.on(\"error\", (err: Error) => {\n fileFailed = true;\n reject(err instanceof Error ? err : new DownloadError(String(err)));\n });\n fileStream.on(\"error\", (err) => {\n fileFailed = true;\n reject(err);\n });\n fileStream.on(\"finish\", () => {\n resolve();\n });\n res.pipe(fileStream);\n });\n } catch (err) {\n fileStream.destroy();\n if (!fileFailed) {\n await rm(options.dest, { force: true });\n }\n throw err instanceof Error ? err : new DownloadError(String(err));\n }\n\n const sha256 = hash.digest(\"hex\");\n const expected = options.expectedSha256;\n const verified =\n expected === undefined || expected === \"\"\n ? true\n : sha256.toLowerCase() === expected.toLowerCase();\n if (!verified) {\n await rm(options.dest, { force: true });\n throw new DownloadError(\n `sha256 校验失败: ${options.dest}\\n期望 ${expected}\\n实际 ${sha256}`,\n );\n }\n return { sha256, verified };\n}\n","/**\n * qq-extract.ts:QQ 官方安装包解包与提取(P1)。\n *\n * ⚠️ 格式事实(2026-08-12 实测修正):QQ 官方安装包是 NSIS 自解压格式,\n * **7za standalone 不支持 NSIS**(只支持 7z/zip/tar 等)——必须用完整版\n * 7z(7z.exe + 7z.dll)。设计文档 §2.3 原「内置 7za.exe」修正为:\n * - Windows:内置 assets/7zip/7z.exe + 7z.dll(7-Zip 官方,LGPL 合规可分发)\n * - Linux/Docker:系统 p7zip-full(7z 完整版)\n *\n * ⚠️ 提取策略(2026-08-12 实测修正,与设计 §2.3 不同):\n * 1. NSIS 解包产物在 <extracted>/Files/(NSIS 内部布局),Files/ = 安装根\n * 2. 真实 QQNT.dll(214MB)位于 versions/<v>/QQNT.dll,**自建宿主不需要**\n * ——stub QQNT.dll 已替代其宿主符号职责(wrapper.node 的 v8/node/napi/qq_magic\n * 符号由 stub 转发到 node.exe)\n * 3. 只提取 resources/app 顶层 *.node + *.dll(wrapper.node 的直接/传递/delay-load\n * 依赖全覆盖,版本无关;跳过 wmpfsdk/avsdk 等大子目录,共约 235MB)\n *\n * 提取目标(与安装目录同构,resolveFromRoot 可直接消费):\n * <数据根>/qq-files/<版本>/versions/<版本>/resources/app/ ← *.node + *.dll\n */\nimport { execFile } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { copyFile, mkdir, readdir, rm } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport process from \"node:process\";\nimport { fileURLToPath } from \"node:url\";\nimport type { QqInstallInfo } from \"./locate-qq.js\";\n\n/** resources/app 下需要提取的文件扩展名(.node 原生模块 + .dll 依赖)。 */\nconst EXTRACT_EXTENSIONS = new Set([\".node\", \".dll\"]);\n\n/** 7-Zip 可执行文件来源。 */\nexport interface SevenZipResult {\n /** 7z 可执行文件绝对路径。 */\n exe: string;\n /** 来源说明(内置资产 / 系统 / 环境变量)。 */\n source: string;\n}\n\n/** 提取选项。 */\nexport interface ExtractOptions {\n /** 安装包绝对路径。 */\n installerPath: string;\n /** 目标版本目录(如 9.9.33-51802)。 */\n version: string;\n /** 缓存根目录(<数据根>/qq-files;版本子目录将建在其下)。 */\n cacheRoot: string;\n /** 7z 可执行文件路径(缺省自动探测)。 */\n sevenZipPath?: string;\n}\n\n/**\n * 探测 7z 可执行文件:\n * NAPUTO_7Z_PATH 环境变量 > 内置资产(assets/7zip/7z.exe,Windows)> 系统 PATH 7z。\n * Linux 上用 p7zip 提供的 7z 命令(系统 PATH)。\n */\nexport function findSevenZip(): SevenZipResult {\n const envPath = process.env[\"NAPUTO_7Z_PATH\"];\n if (envPath !== undefined && envPath !== \"\" && existsSync(envPath)) {\n return { exe: envPath, source: \"环境变量 NAPUTO_7Z_PATH\" };\n }\n // 内置资产:dist 与 assets 同层(tsdown 构建后 dist/,资产在包根 assets/)\n if (process.platform === \"win32\") {\n const bundled = join(\n dirname(fileURLToPath(import.meta.url)),\n \"..\",\n \"assets\",\n \"7zip\",\n \"7z.exe\",\n );\n if (existsSync(bundled)) {\n return { exe: bundled, source: \"内置资产 assets/7zip\" };\n }\n }\n // 系统 PATH(Windows 装了 7-Zip 有 7z.exe;Linux p7zip-full 提供 7z 命令)\n return { exe: \"7z\", source: \"系统 PATH\" };\n}\n\n/** 解包安装包到指定目录(spawn 7z x,失败抛错)。 */\nexport async function extractInstaller(\n installerPath: string,\n destDir: string,\n sevenZipPath?: string,\n): Promise<void> {\n const sevenZip = sevenZipPath ?? findSevenZip().exe;\n await mkdir(destDir, { recursive: true });\n await new Promise<void>((resolve, reject) => {\n execFile(\n sevenZip,\n [\"x\", installerPath, `-o${destDir}`, \"-y\"],\n { maxBuffer: 64 * 1024 * 1024 },\n (err, _stdout, stderr) => {\n if (err !== null) {\n const code = (err as NodeJS.ErrnoException).code;\n const hint =\n code === \"ENOENT\"\n ? `未找到 7z 可执行文件: ${sevenZip}\\n` +\n \"(Windows 请安装 7-Zip 或用 NAPUTO_7Z_PATH 指定;\" +\n \"Linux 自动下载失败时请安装 p7zip-full 或用 NAPUTO_7Z_PATH 指定)\"\n : \"(QQ 安装包是 NSIS 格式,需完整版 7z 而非 7za)\";\n reject(new Error(`7z 解包失败: ${installerPath}\\n${stderr.trim()}\\n${hint}`));\n return;\n }\n resolve();\n },\n );\n });\n}\n\n/**\n * 从解包目录提取运行所需文件到缓存:\n * resources/app 顶层全部 *.node + *.dll(保持同目录结构,PATH 前置即可解析依赖)。\n * 幂等:目标 wrapper.node 已存在则跳过。\n */\nexport async function extractWrapperFiles(\n extractedDir: string,\n version: string,\n cacheRoot: string,\n): Promise<QqInstallInfo> {\n // NSIS 解包产物在 <extracted>/Files/(安装根),resources/app 在其下\n const appDir = join(extractedDir, \"Files\", \"versions\", version, \"resources\", \"app\");\n if (!existsSync(join(appDir, \"wrapper.node\"))) {\n throw new Error(`解包产物缺少 wrapper.node: ${appDir}`);\n }\n\n // 目标结构(与安装目录同构):\n // <cacheRoot>/<version>/versions/<version>/resources/app/*.{node,dll}\n const destAppDir = join(cacheRoot, version, \"versions\", version, \"resources\", \"app\");\n const destWrapper = join(destAppDir, \"wrapper.node\");\n\n // 幂等:已提取过则跳过\n if (existsSync(destWrapper)) {\n return buildCachedInfo(cacheRoot, version);\n }\n\n await mkdir(destAppDir, { recursive: true });\n const entries = await readdir(appDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isFile()) {\n continue; // 跳过子目录(wmpfsdk/avsdk 等大目录不需要)\n }\n const dot = entry.name.lastIndexOf(\".\");\n const ext = dot >= 0 ? entry.name.slice(dot).toLowerCase() : \"\";\n if (!EXTRACT_EXTENSIONS.has(ext)) {\n continue; // 只提取 .node + .dll\n }\n await copyFile(join(appDir, entry.name), join(destAppDir, entry.name));\n }\n if (!existsSync(destWrapper)) {\n throw new Error(`提取失败:wrapper.node 未复制到 ${destWrapper}`);\n }\n\n return buildCachedInfo(cacheRoot, version);\n}\n\n/** 构造 cached 来源的 QqInstallInfo(wrapperPath 指向缓存中 wrapper.node)。 */\nfunction buildCachedInfo(cacheRoot: string, version: string): QqInstallInfo {\n const versionDir = join(cacheRoot, version);\n return {\n qqPath: join(versionDir, \"QQ.exe\"), // 语义占位(缓存目录无 QQ.exe)\n installDir: versionDir,\n version,\n wrapperPath: join(versionDir, \"versions\", version, \"resources\", \"app\", \"wrapper.node\"),\n source: \"cached\",\n };\n}\n\n/** 清理缓存版本目录(下载/解包失败残留,供调用方兜底)。 */\nexport async function clearCacheVersion(cacheRoot: string, version: string): Promise<void> {\n await rm(join(cacheRoot, version), { recursive: true, force: true });\n}\n","/**\n * win-node.ts:Windows 版 node.exe 获取(P2,Linux/wine 场景用)。\n *\n * wine 只能跑 Windows PE 程序——自建宿主在 Linux 上必须用 Windows 版 node.exe\n * 作为宿主(wrapper.node 是 PE 原生模块,只能被 Windows node.exe dlopen)。\n * 来源:nodejs.org 官方 zip(开源软件官方发行版,但为保守起见同样不内置,\n * 运行时下载到 <数据根>/runtime/win-node/,可被 NAPUTO_WIN_NODE_PATH 覆盖)。\n *\n * 设计文档 §3.1。与 qq-download.ts 同构:https 下载 + 解压 + 缓存幂等。\n */\nimport { existsSync, mkdirSync } from \"node:fs\";\nimport { copyFile, readdir, rm } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { downloadFile } from \"./qq-download.js\";\nimport { extractInstaller } from \"./qq-extract.js\";\n\n/** 目标 Windows node.exe 可执行文件名(zip 内)。 */\nconst WIN_NODE_EXE = \"node.exe\";\n\n/** 运行时缓存子目录名(<数据根>/runtime/win-node/)。 */\nexport const WIN_NODE_DIR_NAME = \"win-node\";\n\n/** Windows node.exe 定位结果。 */\nexport interface WinNodeInfo {\n /** node.exe 绝对路径(Windows 视角,供 wine 调用)。 */\n exePath: string;\n /** 已下载的完整版 node zip 版本号(如 v24.16.0)。 */\n version: string;\n}\n\n/** 解析 nodejs.org zip URL(与 dist 目录命名一致)。 */\nexport function nodeZipUrl(version: string): string {\n return `https://nodejs.org/dist/${version}/node-${version}-win-x64.zip`;\n}\n\n/**\n * 确保 Windows 版 node.exe 就绪(下载 + 解压 + 缓存)。\n * 幂等:<数据根>/runtime/win-node/<version>/node.exe 已存在则直接返回。\n */\nexport async function ensureWinNode(\n options: {\n /** 数据根(缓存 <数据根>/runtime/win-node/)。 */\n dataRoot?: string;\n /** node 版本(缺省读 NAPUTO_WIN_NODE_VERSION,再兜底 latest v24)。 */\n version?: string;\n /** 显式 node.exe 路径(覆盖缓存逻辑,读 NAPUTO_WIN_NODE_PATH)。 */\n exePath?: string;\n } = {},\n): Promise<WinNodeInfo> {\n // 显式路径覆盖(NAPUTO_WIN_NODE_PATH / exePath 参数)\n const explicit = options.exePath ?? process.env[\"NAPUTO_WIN_NODE_PATH\"];\n if (explicit !== undefined && explicit !== \"\" && existsSync(explicit)) {\n return { exePath: resolve(explicit), version: \"explicit\" };\n }\n\n const version =\n options.version ?? process.env[\"NAPUTO_WIN_NODE_VERSION\"] ?? DEFAULT_WIN_NODE_VERSION;\n const dataRoot = resolveDataRoot(options.dataRoot);\n const cacheDir = join(dataRoot, \"runtime\", WIN_NODE_DIR_NAME, version);\n const exePath = join(cacheDir, WIN_NODE_EXE);\n if (existsSync(exePath)) {\n return { exePath, version };\n }\n\n // 下载 zip + 解压(用 P1 的 7z 解 zip;7z 支持 zip)\n const zipPath = join(cacheDir, `node-${version}-win-x64.zip`);\n const url = process.env[\"NAPUTO_WIN_NODE_URL\"] ?? nodeZipUrl(version);\n mkdirSync(cacheDir, { recursive: true });\n try {\n await downloadFile({ dest: zipPath, url });\n await unzipNode(zipPath, cacheDir);\n } finally {\n await rm(zipPath, { force: true });\n }\n if (!existsSync(exePath)) {\n throw new Error(`解压后未找到 node.exe: ${exePath}`);\n }\n return { exePath, version };\n}\n\n/** 解压 node zip(zip 内为 node-<version>-win-x64/ 目录,取其中 node.exe 移到缓存根)。 */\nasync function unzipNode(zipPath: string, cacheDir: string): Promise<void> {\n const tmpDir = join(cacheDir, \"_zip\");\n await extractInstaller(zipPath, tmpDir);\n // zip 顶层是 node-<version>-win-x64/,找其中 node.exe\n const top = await readdir(tmpDir);\n const nodeDir = top.find((n) => n.startsWith(\"node-\"));\n const src =\n nodeDir === undefined ? join(tmpDir, WIN_NODE_EXE) : join(tmpDir, nodeDir, WIN_NODE_EXE);\n if (!existsSync(src)) {\n throw new Error(`node zip 解压后未找到 node.exe: ${src}`);\n }\n await copyFile(src, join(cacheDir, WIN_NODE_EXE));\n await rm(tmpDir, { recursive: true, force: true });\n}\n\n/** 默认 Windows node 版本(latest v24,LTS 线;可被环境变量/参数覆盖)。 */\nconst DEFAULT_WIN_NODE_VERSION = \"v24.16.0\";\n\n/** 轻量数据根解析(与 locate-qq 内同构,避免循环依赖)。 */\nfunction resolveDataRoot(dataRoot?: string): string {\n const explicit = dataRoot ?? process.env[\"NAPKETTO_DATA\"];\n if (explicit !== undefined && explicit !== \"\") {\n return resolve(explicit);\n }\n const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();\n return join(projectRoot, \".napuketto\");\n}\n\n/** 向上探测项目根(含 pnpm-workspace.yaml 或 package.json 的目录)。 */\nfunction findProjectRoot(start: string): string | null {\n let dir = resolve(start);\n for (;;) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\")) || existsSync(join(dir, \"package.json\"))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n return null;\n }\n dir = parent;\n }\n}\n","/**\n * wine.ts:Linux 上经 wine 运行 Windows 版 node.exe 的路径与 spawn 组装(P2)。\n *\n * 设计文档 §3.2(2026-08-12 拍板确认)。核心认知:\n * - Windows 版 node.exe 在 wine 内把 Linux 根目录挂载在 `Z:\\`\n * - process.dlopen / 环境变量路径都是 wine 视角的 Windows 路径\n * - 传给 wine 子进程的**所有路径**(wrapperPath / cfgDir / stubDir / self-host.cjs / PATH)\n * 都要过 toWinePath 转成 `Z:\\...`\n *\n * 纯函数设计:不 import node:child_process,方便 Windows 上单测断言(P2 纯逻辑层)。\n */\n\n/** Linux 绝对路径 → wine `Z:\\` 路径(Windows 版 node.exe 视角)。幂等:已是 Windows 路径(含盘符)原样返回。 */\nexport function toWinePath(linuxPath: string): string {\n // 已是 Windows 风格(盘符 + 反斜杠)→ 原样(幂等保护,调用方不应传这种但防御性保留)\n if (DRIVE_LETTER_RE.test(linuxPath)) {\n return linuxPath;\n }\n // /app/.napuketto/qq-files/9.9.33/wrapper.node → Z:\\app\\.napuketto\\qq-files\\9.9.33\\wrapper.node\n // 去前导 /,正斜杠转反斜杠\n return `Z:\\\\${linuxPath.replaceAll(\"/\", \"\\\\\").replace(LEADING_SLASH_RE, \"\")}`;\n}\n\n/** 前导斜杠(toWinePath 去根用,顶层常量避免每次调用重建)。 */\nconst LEADING_SLASH_RE = /^\\\\/;\n\n/** 盘符前缀(C: / Z: 等,识别已转换的 Windows 路径)。 */\nconst DRIVE_LETTER_RE = /^[A-Za-z]:/;\n\n/** 判断当前是否 Linux(需 wine 的场景)。 */\nexport function isLinux(): boolean {\n return process.platform === \"linux\";\n}\n\n/**\n * Unix PATH(冒号分隔)→ wine Windows PATH(分号分隔 + 全部 Z:\\ 条目)。\n *\n * ⚠️ 混入 Unix PATH 会让 wine 按错误分隔符拆分盘符路径(Z:\\...),stub 目录\n * 丢失 → wrapper.node 依赖的 QQNT.dll 找不到(2026-08-14 生产实测\n * \"import_dll Library QQNT.dll not found\")。wine 场景 PATH 必须纯 Windows 风格。\n */\nexport function unixPathToWinePath(unixPath: string): string {\n return unixPath\n .split(\":\")\n .filter((entry) => entry !== \"\")\n .map((entry) => toWinePath(entry))\n .join(\";\");\n}\n\n/** wine 可执行文件(Linux 上 spawn 用;可被 NAPUTO_WINE 覆盖)。 */\nexport function wineBinary(): string {\n return process.env[\"NAPUTO_WINE\"] ?? \"wine\";\n}\n\n/** spawn 命令与参数(平台分支;纯函数,可单测)。 */\nexport interface SpawnCommand {\n /** 命令(win32 = node.exe;linux = wine)。 */\n command: string;\n /** 参数(win32 = [selfHostPath];linux = [winNodePath, selfHostPath])。 */\n args: string[];\n}\n\n/** 组装 spawn 命令(win32 vs linux 分支)。 */\nexport function buildSpawnCommand(options: {\n /** 平台(缺省 process.platform,测试注入)。 */\n platform?: NodeJS.Platform;\n /** Windows 本机 node 路径(win32 用,缺省 process.execPath)。 */\n winNodePath?: string;\n /** wine 命令(linux 用,缺省 wineBinary())。 */\n wine?: string;\n /** 自建宿主入口(self-host.cjs,传给 wine/win node)。 */\n selfHostPath: string;\n}): SpawnCommand {\n const platform = options.platform ?? process.platform;\n if (platform === \"win32\") {\n return {\n command: options.winNodePath ?? process.execPath,\n args: [options.selfHostPath],\n };\n }\n // linux 及其他(wine 场景)\n return {\n command: options.wine ?? wineBinary(),\n args: [options.winNodePath ?? \"node.exe\", options.selfHostPath],\n };\n}\n","/**\n * launcher:自建宿主引导(2026-08-07 唯一路线)——spawn 标准 node 跑 self-host.cjs。\n *\n * 历史(V1/路线 B 事实链,已归档 archive/):\n * 1. wrapper.node 只能在 QQ 定制版 Electron 里注册(实测纯 Node/普通 Electron 均 self-register 失败)\n * 2. QQ 是打包应用,禁 NODE_OPTIONS(实测 stderr)\n * → 旧方案:NapukettoBootMain.exe 启动 QQ + 注入 NapukettoWinBootHook.dll(已废弃)\n * → 现方案:stub QQNT.dll 转发宿主符号 → 标准 node 直接 dlopen wrapper.node(自建宿主)\n *\n * P2(2026-08-12):平台分支——win32 本机 node;linux 经 wine 跑 Windows 版 node.exe\n * (ensureWinNode 下载)。所有传给 wine 子进程的路径过 toWinePath(Z:\\)。\n */\nimport { type StdioOptions, spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { fileURLToPath } from \"node:url\";\nimport type { QqInstallInfo } from \"./locate-qq.js\";\nimport { ensureWinNode } from \"./win-node.js\";\nimport { buildSpawnCommand, isLinux, toWinePath, unixPathToWinePath } from \"./wine.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport interface LaunchOptions {\n /** QQ 安装信息(qqPath/version/wrapperPath)。 */\n qq: QqInstallInfo;\n /** kernel 入口(.mjs,self-host.cjs 里 import)。 */\n kernelEntry: string;\n /** 配置目录(账号数据目录,NAPUTO_CFG_DIR)。 */\n cfgDir: string;\n /** 全局配置文件路径(项目根 napuketto.toml,注入 NAPKETTO_CONFIG 供装配链读取)。 */\n configPath?: string;\n /** adapter 入口(.mjs,协议装配用)。 */\n adapterEntry?: string;\n /** network 入口(.mjs,协议装配用)。 */\n networkEntry?: string;\n /** 自建宿主:标准 node + stub QQNT.dll 直接引导,不拉起 QQ(2026-08-07 唯一路线)。 */\n selfHost?: boolean;\n /** stub QQNT.dll 目录(PATH 前置,转发 napi_*、uv_* 符号到 node.exe)。 */\n stubDir?: string;\n /** 强制指定快速登录账号(注入 NAPUTO_QUICK_UIN;cli `-q <uin>` 透传,2026-08-07)。 */\n quickUin?: string;\n /** IPC 子进程模式(=1:stdout 走 JSON 行协议 + stdin 收 action/control,koishi 插件驱动)。 */\n ipc?: boolean;\n /** 自建宿主入口(默认 dist/host/self-host.cjs)。 */\n selfHostEntry?: string;\n /** Windows 版 node.exe 路径(linux 场景覆盖;缺省 ensureWinNode 下载)。 */\n winNodePath?: string;\n /**\n * 子进程工作目录(缺省继承父进程 cwd)。\n *\n * ⚠️ 必须指向数据根(~/.napuketto):QQ 原生层在账号上下文就绪前会\n * fallback 到进程 cwd 落盘(如频道库 guild1.db),不指定则污染调用方\n * 目录(实测写入项目根,guild1.db 08-07)。cli 传 dataRoot 根治。\n */\n cwd?: string;\n /**\n * 子进程 stdio(默认 \"inherit\")。cli 传 [\"inherit\",\"pipe\",\"pipe\"] 并接管\n * 子进程 stdout/stderr:逐行过滤 wrapper 原生噪音(MMKV 刷屏、Node 符号\n * 查找失败警告),其余转发——原生 printf 直写 fd 的字节无法从 JS 层拦截。\n */\n stdio?: StdioOptions;\n}\n\nexport interface LaunchResult {\n /** 子进程句柄(自建宿主 node 进程)。 */\n child: ReturnType<typeof spawn>;\n /** 自建宿主入口路径(self-host.cjs)。 */\n bootJsPath: string;\n}\n\n/**\n * 启动自建宿主(路线 A):spawn 标准 node 直接跑 self-host.cjs,不拉起 QQ / 不注入。\n *\n * 关键(HANDOVER-V7 技术数据):wrapper.node 从 QQNT.dll 导入 99 符号\n * (napi_*×40 + uv_*×56 + qq_magic×1 + v8/node mangled×2)——标准 node 无这些宿主\n * 符号,必须 PATH 前置 stub QQNT.dll 目录(转发到 node.exe)+ QQ resources\\app\n * (wrapper.node 同目录依赖 DLL)才能 dlopen 成功。\n *\n * P2 平台分支:win32 用本机 node;linux 用 wine + Windows 版 node.exe\n * (ensureWinNode 自动下载,路径过 toWinePath)。async(win-node 可能需下载)。\n */\nexport async function launchSelfHost(options: LaunchOptions): Promise<LaunchResult> {\n const selfHostPath =\n options.selfHostEntry ?? join(__dirname, \"..\", \"dist\", \"host\", \"self-host.cjs\");\n if (!existsSync(selfHostPath)) {\n throw new Error(\n `self-host.cjs 缺失: ${selfHostPath}(先运行 pnpm --filter @napuketto/loader build)`,\n );\n }\n\n // stub QQNT.dll 校验(自建宿主必需:PATH 前置 stub 转发 napi_* 到 node.exe,\n // 否则 wrapper.node dlopen 失败)。默认 loader 包内闭源 submodule native/stub-test-env,\n // 缺失时提示 --stub-dir / NAPUTO_STUB_DIR 指定。\n const stub = options.stubDir ?? defaultStubDir();\n if (!existsSync(join(stub, \"QQNT.dll\"))) {\n throw new Error(\n `stub QQNT.dll 未找到: ${stub}(自建宿主必需:stub 转发 QQNT.dll 符号到 node.exe;` +\n \"请用 --stub-dir 或环境变量 NAPUTO_STUB_DIR 指定 stub 目录)\",\n );\n }\n\n // 平台分支:win32 本机 node;linux wine + win-node(下载)\n const { useWine, winNodePath } = await resolveNodeExecutable(options);\n\n const env = buildLaunchEnv(options, useWine);\n applyPathEnv(env, { stub, wrapperPath: options.qq.wrapperPath, useWine });\n\n // 标准 node 直接跑入口(不拉起 QQ,不注入)\n // cwd 显式指向数据根(cli 传入):QQ 原生层 fallback 落盘(guild1.db 等)\n // 不再污染项目根目录(实测 08-07,GPro 模块初始化早于账号上下文)。\n if (options.cwd !== undefined) {\n mkdirSync(options.cwd, { recursive: true });\n }\n // spawn 命令:win32 = node.exe selfHostPath;linux = wine winNodePath selfHostPath\n // wine 场景 selfHostPath 也需转 Windows 路径(wine 内 node 读参数)\n const { command, args } = buildSpawnCommand({\n ...(useWine && winNodePath !== undefined\n ? { winNodePath }\n : { winNodePath: process.execPath }),\n selfHostPath: useWine ? toWinePath(selfHostPath) : selfHostPath,\n wine: wineBinary(),\n });\n const child = spawn(command, args, {\n // exactOptionalPropertyTypes:未传 cwd 时不显式写入 undefined(继承父进程)\n ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),\n env,\n stdio: options.stdio ?? \"inherit\",\n windowsHide: false,\n });\n\n return { child, bootJsPath: selfHostPath };\n}\n\n/** 平台分支解析 node 可执行:win32 本机;linux wine + Windows 版 node.exe。 */\nasync function resolveNodeExecutable(options: LaunchOptions): Promise<{\n useWine: boolean;\n winNodePath: string | undefined;\n}> {\n if (!isLinux()) {\n return { useWine: false, winNodePath: undefined };\n }\n const winNode = await ensureWinNode({\n ...(options.cwd !== undefined ? { dataRoot: options.cwd } : {}),\n ...(options.winNodePath !== undefined ? { exePath: options.winNodePath } : {}),\n });\n return { useWine: true, winNodePath: winNode.exePath };\n}\n\n/** 注入 PATH(stub 目录 + wrapper.node 目录前置)与 STUB_DIR(wine 场景转 Z:\\)。 */\nfunction applyPathEnv(\n env: Record<string, string>,\n opts: { stub: string; wrapperPath: string; useWine: boolean },\n): void {\n const p = (linuxPath: string): string => (opts.useWine ? toWinePath(linuxPath) : linuxPath);\n // ⚠️ wine 场景 PATH 必须纯 Windows 风格(全部 Z:\\ 条目 + 分号分隔):混入\n // Unix PATH(冒号分隔)会让 wine 按错误分隔符拆分,盘符路径(Z:\\...)被拆散,\n // stub 目录丢失 → wrapper.node 依赖的 QQNT.dll 找不到(2026-08-14 生产实测\n // \"import_dll Library QQNT.dll not found\")。Unix PATH 逐条转 Z:\\ 再并入。\n const unixPath = process.env[\"PATH\"] ?? \"\";\n const winPath = opts.useWine ? unixPathToWinePath(unixPath) : unixPath;\n const pathEntries = [p(opts.stub), p(dirname(opts.wrapperPath)), winPath]\n .filter((entry) => entry !== undefined && entry !== \"\")\n .join(\";\");\n env[\"PATH\"] = pathEntries;\n env[ENV.STUB_DIR] = p(opts.stub);\n}\n\n/**\n * 默认 stub QQNT.dll 目录(loader 包内闭源 native/build/stub-test-env,开发机默认)。\n * src 与 native 同层,构建后 dist 与 native 同层——`../native` 恒正确。\n */\nexport function defaultStubDir(): string {\n return join(__dirname, \"..\", \"native\", \"build\", \"stub-test-env\");\n}\n\n/** 装配自建宿主环境变量(wine 场景路径转 Z:\\;win32 原样)。 */\nfunction buildLaunchEnv(options: LaunchOptions, useWine: boolean): Record<string, string> {\n // 配置目录兜底\n const cfg = resolve(options.cfgDir);\n mkdirSync(cfg, { recursive: true });\n\n // wine 场景:传给子进程的路径全转 Z:\\(node.exe 在 wine 内按 Windows 路径读)\n const p = (linuxPath: string): string => (useWine ? toWinePath(linuxPath) : linuxPath);\n const env: Record<string, string> = {\n ...process.env,\n [ENV.QQ_PATH]: p(options.qq.qqPath),\n [ENV.KERNEL_ENTRY]: p(resolve(options.kernelEntry)),\n [ENV.CFG_DIR]: p(cfg),\n [ENV.QQ_VERSION]: options.qq.version,\n [ENV.WRAPPER_PATH]: p(options.qq.wrapperPath),\n };\n // 可选注入(对象展开,保持低复杂度)\n const optional: Record<string, string> = {\n ...(options.selfHost === true ? { [ENV.SELF_HOST]: \"1\" } : {}),\n ...(options.adapterEntry !== undefined\n ? { [ENV.ADAPTER_ENTRY]: p(resolve(options.adapterEntry)) }\n : {}),\n ...(options.networkEntry !== undefined\n ? { [ENV.NETWORK_ENTRY]: p(resolve(options.networkEntry)) }\n : {}),\n ...(options.configPath !== undefined\n ? { [ENV.CONFIG_PATH]: p(resolve(options.configPath)) }\n : {}),\n ...(options.quickUin !== undefined ? { [ENV.QUICK_UIN]: options.quickUin } : {}),\n ...(options.ipc === true ? { [ENV.IPC]: \"1\" } : {}),\n };\n return { ...env, ...optional };\n}\n\n/** wine 可执行文件(linux 场景;NAPUTO_WINE 可覆盖)。 */\nfunction wineBinary(): string {\n return process.env[\"NAPUTO_WINE\"] ?? \"wine\";\n}\n\n/** 环境变量名(self-host.cjs 与 kernel 引导读取)。 */\nexport const ENV = {\n QQ_PATH: \"NAPUTO_QQ_PATH\",\n KERNEL_ENTRY: \"NAPUTO_KERNEL_ENTRY\",\n CFG_DIR: \"NAPUTO_CFG_DIR\",\n QQ_VERSION: \"NAPUTO_QQ_VERSION\",\n WRAPPER_PATH: \"NAPUTO_WRAPPER_PATH\",\n /** 自建宿主:标准 node + stub QQNT.dll 引导(self-host.cjs 分支)。 */\n SELF_HOST: \"NAPUTO_SELF_HOST\",\n /** stub QQNT.dll 目录(launchSelfHost 注入,self-host.cjs 诊断用)。 */\n STUB_DIR: \"NAPUTO_STUB_DIR\",\n /** adapter 包入口(协议装配用)。 */\n ADAPTER_ENTRY: \"NAPUTO_ADAPTER_ENTRY\",\n /** network 包入口(协议装配用)。 */\n NETWORK_ENTRY: \"NAPUTO_NETWORK_ENTRY\",\n /** 全局配置文件路径(项目根 napuketto.toml,boot-protocols 读取)。 */\n CONFIG_PATH: \"NAPKETTO_CONFIG\",\n /** 强制指定快速登录账号(cli `-q <uin>` 透传,bootstrap 登录用)。 */\n QUICK_UIN: \"NAPUTO_QUICK_UIN\",\n /** IPC 子进程模式(koishi 插件驱动:stdout JSON 行 + stdin action/control)。 */\n IPC: \"NAPUTO_IPC\",\n} as const;\n","/**\n * qq-releases.ts:版本清单读取(P1)。\n *\n * 运行时从包根 qq-releases.json 读取(发布时 files 含它,安装后位于\n * node_modules/@napuketto/loader/qq-releases.json)。清单由 CI/社区 PR 维护\n * (设计文档 §2.2)。NAPUTO_QQ_URL 可运行时覆盖下载地址。\n */\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport process from \"node:process\";\nimport { fileURLToPath } from \"node:url\";\n\n/** 清单中单个版本条目。 */\nexport interface QqReleaseEntry {\n /** 版本目录名(如 9.9.33-51802)。 */\n version: string;\n /** 官方安装包下载 URL。 */\n url: string;\n /** sha256(十六进制小写);空串 = 尚未校验(首次录入时 CI 计算后填入)。 */\n sha256: string;\n /** appid(预知信息,运行时仍从 major.node 解析)。 */\n appid: number;\n /** 来源标记(official = 官方)。 */\n source: string;\n /** 构建日期(YYYY-MM-DD,可选)。 */\n buildDate?: string;\n}\n\n/** 清单文件结构。 */\nexport interface QqReleasesFile {\n schema: number;\n known: QqReleaseEntry[];\n}\n\n/** 清单解析失败。 */\nexport class QqReleasesError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"QqReleasesError\";\n }\n}\n\n/** 包根路径(dist 与包根同层:dist/index.mjs → ../qq-releases.json)。 */\nfunction packageRoot(): string {\n return dirname(dirname(fileURLToPath(import.meta.url)));\n}\n\n/** 读版本清单(同步,启动期调用一次即可)。 */\nexport function loadQqReleases(): QqReleasesFile {\n const file = join(packageRoot(), \"qq-releases.json\");\n let raw: string;\n try {\n raw = readFileSync(file, \"utf8\");\n } catch {\n throw new QqReleasesError(`版本清单缺失: ${file}(@napuketto/loader 包安装不完整?)`);\n }\n try {\n const parsed = JSON.parse(raw) as Partial<QqReleasesFile>;\n if (parsed.schema !== 1 || !Array.isArray(parsed.known) || parsed.known.length === 0) {\n throw new Error(\"结构不合法\");\n }\n return parsed as QqReleasesFile;\n } catch {\n throw new QqReleasesError(`版本清单解析失败: ${file}`);\n }\n}\n\n/** 取最新已知可用版本(known 数组按录入顺序,取最后一项 = 最新)。 */\nexport function latestRelease(releases: QqReleasesFile): QqReleaseEntry {\n const [latest] = [...releases.known].reverse();\n if (latest === undefined) {\n throw new QqReleasesError(\"版本清单为空\");\n }\n return latest;\n}\n\n/**\n * 解析实际下载 URL:NAPUTO_QQ_URL 环境变量覆盖 > 清单 URL。\n * 供 downloadFile 使用(也支持按版本查询,当前固定取最新)。\n */\nexport function resolveDownloadUrl(release: QqReleaseEntry): string {\n const override = process.env[\"NAPUTO_QQ_URL\"];\n return override !== undefined && override !== \"\" ? override : release.url;\n}\n","/**\n * 定位 QQ 原生文件(wrapper.node)来源(P0:多级来源纯定位,不下载)。\n *\n * 来源优先级(resolveQqFiles):\n * L0: NAPUTO_QQ_FILES / qqFilesDir —— 显式指定含 versions/ 结构的文件根(缓存/拷贝)\n * L1: 本机 QQ 安装 —— NAPUTO_QQ_PATH / 注册表 / 常见路径(既有逻辑保留)\n * L2: 数据根缓存 <数据根>/qq-files/<版本> —— 下载解包产物(P1 落地后才有)\n */\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, mkdirSync, readdirSync, statSync } from \"node:fs\";\nimport { chmod, rm } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { downloadFile } from \"./qq-download.js\";\nimport { clearCacheVersion, extractInstaller, extractWrapperFiles } from \"./qq-extract.js\";\nimport { latestRelease, loadQqReleases, resolveDownloadUrl } from \"./qq-releases.js\";\n\n/** 数据根下 QQ 文件缓存目录名(<数据根>/qq-files/<版本>/,P1 下载解包产物)。 */\nexport const QQ_FILES_DIR_NAME = \"qq-files\";\n\n/** 7-Zip 官方 Linux 版版本号(tar.xz 文件名组成部分,2026-08-14 起自动下载)。 */\nconst SEVEN_ZIP_LINUX_VERSION = \"2409\";\n\n/** 注册表 UninstallString 查询(QQ 官方安装路径)。 */\nconst REG_QUERY_RE = /\"([^\"]+)\"/;\n\n/** Windows 盘符路径正则(`C:/...` / `C:\\...`,wslMappedPath 映射用,模块级常量)。 */\nconst DRIVE_PATH_RE = /^([A-Za-z]):[\\\\/](.*)$/;\n\n/** 注册表 UninstallString 查询(QQ 官方安装路径)。 */\nfunction findQqViaRegistry(): string | null {\n try {\n const out = execFileSync(\"reg\", [\n \"query\",\n \"HKLM\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\QQ\",\n \"/v\",\n \"UninstallString\",\n ]).toString();\n const m = out.match(REG_QUERY_RE);\n const exe = m?.[1];\n if (exe !== undefined) {\n // UninstallString 形如 \"C:\\...\\uninst.exe\" 或带参数\n const dir = exe.replaceAll('\"', \"\").slice(0, exe.lastIndexOf(\"\\\\\"));\n const qq = join(dir, \"QQ.exe\");\n if (existsSync(qq)) {\n return qq;\n }\n }\n } catch {\n // 注册表查询失败,走常见路径\n }\n return null;\n}\n\n/** 常见安装路径探测。 */\nfunction findQqViaCommonPaths(): string | null {\n const localAppData = process.env[\"LOCALAPPDATA\"] ?? \"\";\n const candidates = [\n \"C:/Program Files/Tencent/QQNT/QQ.exe\",\n \"C:/Program Files (x86)/Tencent/QQNT/QQ.exe\",\n join(localAppData, \"Programs\", \"Tencent\", \"QQNT\", \"QQ.exe\"),\n // 开发机目录(2026-08-07 环境事实:QQ 9.9.33-51802 在 C:\\Dev\\QQBot-Dev\\QQNT)\n \"C:/Dev/QQBot-Dev/QQNT/QQ.exe\",\n ];\n for (const p of candidates) {\n // WSL 场景:Windows 盘挂载在 /mnt/<盘符>/(如 C:\\ → /mnt/c/),\n // 同一候选同时探测 Linux 侧映射路径(2026-08-13 koishi-app 实测报「未找到 QQ.exe」)。\n const wsl = wslMappedPath(p);\n if (existsSync(wsl)) {\n return wsl;\n }\n if (existsSync(p)) {\n return p;\n }\n }\n return null;\n}\n\n/**\n * Windows 路径 → WSL 挂载路径(`C:/...` → `/mnt/c/...`;非 Linux 或非盘符路径原样)。\n * WSL 默认把 Windows 盘符挂载到 `/mnt/<小写盘符>/`,Linux 侧 `existsSync` 必须用映射路径。\n * platform 参数可注入(测试用;缺省 process.platform)。\n * 导出便于单测(locate-qq.test.ts)。\n * ⚠️ 不用 join 拼 `/mnt/...`:测试在 Windows 跑,win32 join 会产生反斜杠;目标路径\n * 是 Linux 侧路径(WSL 挂载点),必须恒正斜杠。\n */\nexport function wslMappedPath(\n winPath: string,\n platform: NodeJS.Platform = process.platform,\n): string {\n if (platform !== \"linux\") {\n return winPath;\n }\n const [, drive, rest] = DRIVE_PATH_RE.exec(winPath) ?? [];\n if (drive === undefined || rest === undefined) {\n return winPath;\n }\n return `/mnt/${drive.toLowerCase()}/${rest.replaceAll(\"\\\\\", \"/\")}`;\n}\n\n/** QQ 文件来源。 */\nexport type QqFileSource = \"local\" | \"cached\";\n\n/** QQ 安装目录探测结果。 */\nexport interface QqInstallInfo {\n /** QQ.exe 绝对路径(cached 来源为语义占位——缓存目录无 QQ.exe,当前无消费方)。 */\n qqPath: string;\n /** 安装根目录 / 文件根目录。 */\n installDir: string;\n /** 当前版本目录名(如 9.9.31-49919)。 */\n version: string;\n /** wrapper.node 绝对路径。 */\n wrapperPath: string;\n /** 来源:local = 本机安装;cached = NAPUTO_QQ_FILES / 数据根缓存。 */\n source: QqFileSource;\n}\n\n/** resolveQqFiles 选项。 */\nexport interface ResolveQqFilesOptions {\n /** 显式文件根(L0;缺省读 NAPUTO_QQ_FILES)。 */\n qqFilesDir?: string;\n /** 显式 QQ.exe 路径(L1;缺省读 NAPUTO_QQ_PATH)。 */\n qqPath?: string;\n /** 数据根(L2 缓存扫描;缺省轻量解析 NAPKETTO_DATA ?? 项目根/.napuketto)。 */\n dataRoot?: string;\n /** L0/L1/L2 全部失败时自动进入下载流程(P1;默认 true,测试可关)。 */\n autoDownload?: boolean;\n /** 7z 可执行文件路径(解包用;缺省自动探测)。 */\n sevenZipPath?: string;\n}\n\n/** 找 QQ.exe 路径。 */\nexport function locateQqPath(): string {\n const viaReg = findQqViaRegistry();\n if (viaReg) {\n return viaReg;\n }\n const viaCommon = findQqViaCommonPaths();\n if (viaCommon) {\n return viaCommon;\n }\n throw new Error(\"未找到 QQ.exe(请先安装 QQ,或通过 NAPUTO_QQ_PATH 指定)\");\n}\n\n/** 从文件根定位最新版本 wrapper.node(local/cached 共用)。 */\nfunction resolveFromRoot(rootDir: string, source: QqFileSource): QqInstallInfo {\n const versionsDir = join(rootDir, \"versions\");\n if (!existsSync(versionsDir)) {\n throw new Error(`QQ 版本目录不存在: ${versionsDir}`);\n }\n const versions = readdirSync(versionsDir)\n .filter((v) => statSync(join(versionsDir, v)).isDirectory())\n .sort()\n .reverse();\n const [first] = versions;\n if (first === undefined) {\n throw new Error(`QQ 版本目录为空: ${versionsDir}`);\n }\n const wrapperPath = join(versionsDir, first, \"resources\", \"app\", \"wrapper.node\");\n if (!existsSync(wrapperPath)) {\n throw new Error(`未找到 wrapper.node: ${wrapperPath}`);\n }\n return {\n qqPath: join(rootDir, \"QQ.exe\"),\n installDir: rootDir,\n version: first,\n wrapperPath,\n source,\n };\n}\n\n/** 从 QQ.exe 路径推导安装目录,并探测当前版本(L1:本机安装)。 */\nexport function resolveQqInstall(qqPath?: string): QqInstallInfo {\n const qq = qqPath ?? process.env[\"NAPUTO_QQ_PATH\"] ?? locateQqPath();\n // Windows 安装结构:<installDir>/versions/<版本>/resources/app/wrapper.node\n // ⚠️ dirname 跨平台:Linux/WSL 路径用正斜杠(lastIndexOf(\"\\\\\") 会切错,2026-08-13 实测)\n const installDir = dirname(qq);\n return resolveFromRoot(installDir, \"local\");\n}\n\n/**\n * 多级来源定位 QQ 原生文件:\n * L0 → L1 → L2 依次尝试,全部失败且 autoDownload 时自动进入下载流程(ensureQqFiles)。\n * ⚠️ async:自动下载为异步流程(P1 起);纯定位场景用 resolveQqInstall(同步)。\n */\nexport async function resolveQqFiles(options: ResolveQqFilesOptions = {}): Promise<QqInstallInfo> {\n // L0:显式文件根(参数 > 环境变量)\n const explicitDir = options.qqFilesDir ?? process.env[\"NAPUTO_QQ_FILES\"];\n if (explicitDir !== undefined && explicitDir !== \"\") {\n return resolveFromRoot(resolve(explicitDir), \"cached\");\n }\n // L1:本机安装(参数 > 环境变量 > 注册表/常见路径)\n const qqPath = options.qqPath ?? process.env[\"NAPUTO_QQ_PATH\"];\n if (qqPath !== undefined && qqPath !== \"\") {\n return resolveQqInstall(qqPath);\n }\n try {\n return resolveQqInstall();\n } catch {\n // 本机未安装 → 落 L2\n }\n // L2:数据根缓存(P1 下载解包产物;结构 <数据根>/qq-files/<版本>/)\n const dataRoot = resolveDataRootLight(options.dataRoot);\n const cached = tryResolveCached(dataRoot);\n if (cached !== null) {\n return cached;\n }\n // 全部缺失 → 自动下载(P1)\n if (options.autoDownload !== false) {\n const sevenZipPath =\n options.sevenZipPath !== undefined ? { sevenZipPath: options.sevenZipPath } : {};\n return ensureQqFiles({ dataRoot, ...sevenZipPath });\n }\n throw new Error(\n \"未找到 QQ 原生文件(wrapper.node):请安装 QQ、设置 NAPUTO_QQ_PATH/NAPUTO_QQ_FILES,\" +\n \"或使用下载流程(ensureQqFiles)自动获取\",\n );\n}\n\n/** 扫描数据根缓存(L2);命中返回,未命中返回 null。 */\nfunction tryResolveCached(dataRoot: string): QqInstallInfo | null {\n const cacheRoot = join(dataRoot, QQ_FILES_DIR_NAME);\n if (!existsSync(cacheRoot)) {\n return null;\n }\n const versionDirs = readdirSync(cacheRoot)\n .filter((v) => statSync(join(cacheRoot, v)).isDirectory())\n .sort()\n .reverse();\n for (const versionDir of versionDirs) {\n try {\n return resolveFromRoot(join(cacheRoot, versionDir), \"cached\");\n } catch {\n // 该版本目录结构不完整(如下载中断残留),尝试下一个\n }\n }\n return null;\n}\n\n/**\n * 7-Zip 官方 Linux 版下载地址(纯函数,可单测)。\n * 环境变量 NAPUTO_7Z_URL 可覆盖(同 QQ 下载 NAPUTO_QQ_URL 模式)。\n */\nexport function linuxSevenZipUrl(): string {\n return (\n process.env[\"NAPUTO_7Z_URL\"] ??\n `https://www.7-zip.org/a/7z${SEVEN_ZIP_LINUX_VERSION}-linux-x64.tar.xz`\n );\n}\n\n/**\n * 确保 Linux 7zz 就绪(2026-08-14 生产修复):自动下载 7-Zip 官方 Linux 版。\n *\n * 背景:内置 assets/7zip 的 7z.exe 是 Windows PE(Linux 不可用),此前 Linux 依赖\n * 系统 p7zip-full——生产环境未安装 → `7z 解包失败: No such file or directory`。\n * 7zz 是 7-Zip 完整版(支持 NSIS 解包,同 7z.exe 能力),官方静态二进制,\n * 免 root 免系统包,下载到 <数据根>/runtime/7zip/7zz(与 win-node 同模式)。\n *\n * 幂等:缓存已存在直接返回;tar -xJf 解压(Linux 自带 tar;需 xz-utils)。\n */\nexport async function ensureLinuxSevenZip(\n options: { dataRoot?: string } = {},\n): Promise<{ exe: string; source: string }> {\n const dataRoot = resolveDataRootLight(options.dataRoot);\n const cacheDir = join(dataRoot, \"runtime\", \"7zip\");\n const exe = join(cacheDir, \"7zz\");\n if (existsSync(exe)) {\n return { exe, source: \"数据根缓存 runtime/7zip\" };\n }\n\n const archive = join(cacheDir, `7z${SEVEN_ZIP_LINUX_VERSION}-linux-x64.tar.xz`);\n mkdirSync(cacheDir, { recursive: true });\n await downloadFile({ dest: archive, url: linuxSevenZipUrl() });\n try {\n // tar -xJf 解压(Linux 自带 tar;7-Zip 官方 tar.xz 根目录含 7zz)\n try {\n execFileSync(\"tar\", [\"-xJf\", archive, \"-C\", cacheDir], { stdio: \"pipe\" });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `7z linux 解压失败: ${message}(需 tar + xz-utils;或设 NAPUTO_7Z_PATH 指定系统 7z)`,\n );\n }\n } finally {\n await rm(archive, { force: true });\n }\n if (!existsSync(exe)) {\n throw new Error(`7z linux 解压后未找到 7zz: ${exe}`);\n }\n await chmod(exe, 0o755); // 官方 tar.xz 内已有执行位,保险设置\n return { exe, source: \"自动下载(7-Zip 官方 linux 版)\" };\n}\n\n/**\n * 确保 QQ 原生文件就绪(P1):幂等缓存检查 → 下载官方安装包 → sha256 校验\n * → 7z 解包 → 提取 wrapper.node/QQNT.dll → 缓存。返回缓存版本 QqInstallInfo。\n */\nexport async function ensureQqFiles(\n options: {\n /** 数据根(缓存 <数据根>/qq-files/)。 */\n dataRoot?: string;\n /** 7z 路径(缺省自动探测)。 */\n sevenZipPath?: string;\n } = {},\n): Promise<QqInstallInfo> {\n const dataRoot = resolveDataRootLight(options.dataRoot);\n const cacheRoot = join(dataRoot, QQ_FILES_DIR_NAME);\n mkdirSync(cacheRoot, { recursive: true });\n\n // 幂等:缓存已有完整版本 → 直接返回\n const existing = tryResolveCached(dataRoot);\n if (existing !== null) {\n return existing;\n }\n\n // 1. 版本清单 → 最新可用版本\n const releases = loadQqReleases();\n const release = latestRelease(releases);\n const url = resolveDownloadUrl(release);\n const version = release.version;\n\n // 2. 下载 + sha256 校验(清单无参考值时下载器跳过校验,完整性由解包/加载兜底)\n const tmpDir = join(dataRoot, \"tmp\");\n mkdirSync(tmpDir, { recursive: true });\n const installerPath = join(tmpDir, `qq-installer-${version}.exe`);\n await downloadFile({ dest: installerPath, url, expectedSha256: release.sha256 });\n\n // 3. 解包 + 提取(失败清理缓存残留;临时文件无论如何清理)\n // Linux 分支:确保 7zz(自动下载 7-Zip 官方 linux 版,2026-08-14 生产修复——\n // 此前依赖系统 p7zip-full,未安装则 7z 解包失败、QQ 文件无法就绪)\n let sevenZipPath = options.sevenZipPath;\n if (sevenZipPath === undefined && process.platform === \"linux\") {\n sevenZipPath = (await ensureLinuxSevenZip({ dataRoot })).exe;\n }\n const extractedDir = join(tmpDir, `qq-extracted-${version}`);\n try {\n await extractInstaller(installerPath, extractedDir, sevenZipPath);\n const info = await extractWrapperFiles(extractedDir, version, cacheRoot);\n return info;\n } catch (err) {\n await clearCacheVersion(cacheRoot, version);\n throw err;\n } finally {\n await rm(installerPath, { force: true });\n await rm(extractedDir, { recursive: true, force: true });\n }\n}\n\n/**\n * 轻量数据根解析(loader 侧,避免新增 kernel 依赖;P2 若需完整语义再切换)。\n * 优先级:显式参数 > NAPKETTO_DATA > 项目根/.napuketto > cwd 兜底。\n */\nfunction resolveDataRootLight(dataRoot?: string): string {\n const explicit = dataRoot ?? process.env[\"NAPKETTO_DATA\"];\n if (explicit !== undefined && explicit !== \"\") {\n return resolve(explicit);\n }\n const projectRoot = findProjectRoot(process.cwd()) ?? process.cwd();\n return join(projectRoot, \".napuketto\");\n}\n\n/** 向上探测项目根(含 pnpm-workspace.yaml 或 package.json 的目录)。 */\nfunction findProjectRoot(start: string): string | null {\n let dir = resolve(start);\n for (;;) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\")) || existsSync(join(dir, \"package.json\"))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n return null;\n }\n dir = parent;\n }\n}\n"],"mappings":"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"}
|