@napuketto/loader 0.0.22 → 0.0.24

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.
@@ -851,6 +851,118 @@ function decodeIpcMessage(line) {
851
851
  }
852
852
  }
853
853
  //#endregion
854
+ //#region src/host/ipc/ipc-ob11.ts
855
+ /**
856
+ * ipc-ob11.ts:OB11 动作容器 IPC 桥(NAPUTO_IPC=1 可选装配,2026-08-27)。
857
+ *
858
+ * 触发条件:IPC 模式 && NAPUTO_ADAPTER_ENTRY + NAPUTO_NETWORK_ENTRY 已注入
859
+ * (koishi 插件 launcher 透传;未注入 = app 层不要 OB11 面,静默跳过)。
860
+ *
861
+ * 装配:动态 import adapter onebot11/core 子路径 + network 入口(assemble-
862
+ * protocols.ts 同款——loader 编译期零 adapter/network 静态依赖),实例化
863
+ * NapukettoOneBot11Adapter 后仅 subscribeOnly()(接收链路,零网络传输),
864
+ * 全部动作名平铺合并进共享 IPC 动作表(OB11 snake_case 与 kernel 点分
865
+ * 命名空间不冲突),OB11 事件经 broadcaster 桥适配器 → sendEvent("ob11")。
866
+ *
867
+ * fail-soft:import/构造失败只 log 降级为纯 kernel 动作面,不阻断登录链路。
868
+ */
869
+ /** Windows 反斜杠(file:// URL 化时替换)。 */
870
+ const BACKSLASH_RE$1 = /\\/g;
871
+ /** adapter 入口 index.mjs 后缀(子路径导出替换用)。 */
872
+ const INDEX_MJS_RE$1 = /index\.mjs$/;
873
+ /** 动态 import(file:// URL;全动态表达式,打包器不静态解析)。 */
874
+ async function importFileUrl(url) {
875
+ return import(url);
876
+ }
877
+ /**
878
+ * OB11 动作桥装配(bootstrap 登录成功、attachIpcServices 之后调用)。
879
+ * 返回停止函数(broadcaster 注销 + adapter 退订;进程级清理用)。
880
+ */
881
+ async function attachOb11IpcBridge(actions, services, deps = {}) {
882
+ const e = deps.env ?? {
883
+ adapterEntry: env.NAPUTO_ADAPTER_ENTRY,
884
+ networkEntry: env.NAPUTO_NETWORK_ENTRY,
885
+ qqVersion: env.NAPUTO_QQ_VERSION,
886
+ dataRoot: env.NAPKETTO_DATA,
887
+ cfgDir: env.NAPUTO_CFG_DIR
888
+ };
889
+ const noop = () => {};
890
+ if (e.adapterEntry === void 0 || e.adapterEntry === "") return noop;
891
+ if (e.networkEntry === void 0 || e.networkEntry === "") return noop;
892
+ const importModule = deps.importModule ?? importFileUrl;
893
+ const emit = deps.emitEvent ?? sendEvent;
894
+ try {
895
+ const toUrl = (p) => `file://${p.replace(BACKSLASH_RE$1, "/")}`;
896
+ const network = await importModule(toUrl(e.networkEntry));
897
+ const onebot11Entry = e.adapterEntry.replace(INDEX_MJS_RE$1, "onebot11/index.mjs");
898
+ const coreEntry = e.adapterEntry.replace(INDEX_MJS_RE$1, "core/index.mjs");
899
+ const adapter = await importModule(toUrl(onebot11Entry));
900
+ const adapterCore = await importModule(toUrl(coreEntry));
901
+ const broadcaster = new network.EventBroadcaster();
902
+ const protocolConfig = new adapterCore.ProtocolConfig({
903
+ path: (0, node_path.join)(e.cfgDir ?? ".", "ob11-ipc.toml"),
904
+ schema: adapter.ob11ConfigSchema,
905
+ defaults: adapter.ob11ConfigSchema.parse({}),
906
+ seed: adapter.ob11ConfigSchema.parse({})
907
+ });
908
+ const ob11 = new adapter.NapukettoOneBot11Adapter({
909
+ config: protocolConfig,
910
+ broadcaster,
911
+ msgChannel: services.channel,
912
+ msgApi: services.msgApi,
913
+ groupApi: services.groupApi,
914
+ groupNotifyApi: services.groupNotifyApi,
915
+ friendApi: services.friendApi,
916
+ ticketApi: services.ticketApi,
917
+ richMediaApi: services.richMediaApi,
918
+ profileApi: services.profileApi,
919
+ profileLikeApi: services.profileLikeApi,
920
+ webApi: services.webApi,
921
+ self: services.self,
922
+ system: {
923
+ appVersion: e.qqVersion || "unknown",
924
+ cleanCache: async () => {
925
+ new services.kernel.PathWrapper({
926
+ dataRoot: e.dataRoot,
927
+ account: services.self.uin
928
+ }).clearCache();
929
+ },
930
+ cacheDir: (0, node_path.join)(e.cfgDir ?? ".", "cache"),
931
+ exit: async () => {
932
+ log("ipc-ob11: bot_exit 触发,退出子进程");
933
+ node_process.default.exit(0);
934
+ },
935
+ restart: async () => {
936
+ log("ipc-ob11: set_restart 触发,退出子进程(driver 重启)");
937
+ node_process.default.exit(0);
938
+ }
939
+ },
940
+ groupCache: services.groupCache
941
+ });
942
+ await ob11.subscribeOnly();
943
+ let mounted = 0;
944
+ for (const name of ob11.registry.names) {
945
+ const act = ob11.registry.get(name);
946
+ if (act === void 0) continue;
947
+ actions.set(name, (params) => act.handle(params));
948
+ mounted++;
949
+ }
950
+ const bridgeAdapter = { send: (event) => {
951
+ const postType = event.post_type;
952
+ emit("ob11", typeof postType === "string" ? postType : "unknown", [event]);
953
+ } };
954
+ broadcaster.register(bridgeAdapter);
955
+ log(`ipc-ob11: OB11 动作桥已挂载(${mounted} 动作,事件经 ob11 通道透出)`);
956
+ return () => {
957
+ broadcaster.unregister(bridgeAdapter);
958
+ ob11.unsubscribeOnly();
959
+ };
960
+ } catch (err) {
961
+ log(`ipc-ob11: OB11 动作桥装配失败(降级纯 kernel 动作面): ${errMsg(err)}`);
962
+ return noop;
963
+ }
964
+ }
965
+ //#endregion
854
966
  //#region src/host/ipc/ipc-server.ts
855
967
  /**
856
968
  * ipc-server.ts:IPC 服务端(self-host 子进程侧)。
@@ -1690,7 +1802,10 @@ async function bootstrapWithCore(kernel, state, bootEnv, Appid) {
1690
1802
  if (env.NAPUTO_IPC === "1") sendStatus("sessioning");
1691
1803
  await runSmokeIfEnabled(kernel, ctx, loginResult);
1692
1804
  const services = await startProtocols(kernel, ctx, loginResult);
1693
- if (env.NAPUTO_IPC === "1" && services !== null && ipcActions !== null) attachIpcServices(ipcActions, services);
1805
+ if (env.NAPUTO_IPC === "1" && services !== null && ipcActions !== null) {
1806
+ attachIpcServices(ipcActions, services);
1807
+ await attachOb11IpcBridge(ipcActions, services);
1808
+ }
1694
1809
  runProbePhase(kernel, ctx);
1695
1810
  }
1696
1811
  //#endregion
package/dist/index.cjs CHANGED
@@ -440,6 +440,16 @@ async function downloadFile(options) {
440
440
  if (!fileFailed) await (0, node_fs_promises.rm)(options.dest, { force: true });
441
441
  throw err instanceof Error ? err : new DownloadError(String(err));
442
442
  }
443
+ let size;
444
+ try {
445
+ size = (0, node_fs.statSync)(options.dest).size;
446
+ } catch {
447
+ throw new DownloadError(`下载完成但文件缺失: ${options.dest}`);
448
+ }
449
+ if (size <= 0) {
450
+ await (0, node_fs_promises.rm)(options.dest, { force: true });
451
+ throw new DownloadError(`下载完成但文件为空: ${options.dest}`);
452
+ }
443
453
  const sha256 = hash.digest("hex");
444
454
  const expected = options.expectedSha256;
445
455
  const verified = expected === void 0 || expected === "" ? true : sha256.toLowerCase() === expected.toLowerCase();
@@ -676,6 +686,22 @@ function unixPathToWinePath(unixPath) {
676
686
  function wineBinary() {
677
687
  return process.env["NAPUTO_WINE"] ?? "wine";
678
688
  }
689
+ /** wine 安装指引(缺失时提示用户如何准备,纯字符串可单测)。 */
690
+ function wineInstallHint() {
691
+ return "Linux/WSL 自建宿主需要 wine 运行 Windows 版 node.exe(wrapper.node 是Windows PE 原生模块,只能被 Windows node.exe dlopen)。\n 安装:sudo apt-get install wine64(或其他发行版对应 wine 包)\n 覆盖:设置环境变量 NAPUTO_WINE 指向 wine 可执行文件路径";
692
+ }
693
+ /**
694
+ * wine 可执行性探测结果 → 可读错误(null = 可用)。纯函数,可单测。
695
+ * 由 launcher 在 spawn 前用 spawnSync 探测 wine(缺失时 throw 可读错误,
696
+ * 避免 spawn 异步 emit 'error' 无监听者导致宿主进程崩溃,2026-08-23 WSL 实测)。
697
+ */
698
+ function wineCheckError(probe) {
699
+ if (probe.status === 0) return null;
700
+ const err = probe.error;
701
+ if (err !== void 0 && err.code === "ENOENT") return `未检测到 wine 可执行文件。${wineInstallHint()}`;
702
+ if (err !== void 0) return `wine 探测失败: ${err.message ?? String(err)}。${wineInstallHint()}`;
703
+ return `wine --version 退出码非 0(status=${String(probe.status)}),wine 可能异常。${wineInstallHint()}`;
704
+ }
679
705
  /** 组装 spawn 命令(win32 vs linux 分支)。 */
680
706
  function buildSpawnCommand(options) {
681
707
  if ((options.platform ?? process.platform) === "win32") return {
@@ -718,6 +744,7 @@ async function launchSelfHost(options) {
718
744
  if (!(0, node_fs.existsSync)(selfHostPath)) throw new Error(`self-host.cjs 缺失: ${selfHostPath}(先运行 pnpm --filter @napuketto/loader build)`);
719
745
  const stub = options.stubDir ?? defaultStubDir();
720
746
  if (!(0, node_fs.existsSync)((0, node_path.join)(stub, "QQNT.dll"))) throw new Error(`stub QQNT.dll 未找到: ${stub}(自建宿主必需:stub 转发 QQNT.dll 符号到 node.exe;请用 --stub-dir 或环境变量 NAPUTO_STUB_DIR 指定 stub 目录)`);
747
+ options.onStage?.(`stub QQNT.dll 就绪:${stub}`);
721
748
  const { useWine, winNodePath } = await resolveNodeExecutable(options);
722
749
  const env = buildLaunchEnv(options, useWine);
723
750
  applyPathEnv(env, {
@@ -729,15 +756,21 @@ async function launchSelfHost(options) {
729
756
  const { command, args } = buildSpawnCommand({
730
757
  ...useWine && winNodePath !== void 0 ? { winNodePath } : { winNodePath: node_process.default.execPath },
731
758
  selfHostPath: useWine ? toWinePath(selfHostPath) : selfHostPath,
732
- wine: wineBinary$1()
759
+ wine: wineBinary()
760
+ });
761
+ options.onStage?.(`启动自建宿主子进程:${command} ${args.join(" ")}`);
762
+ const child = (0, node_child_process.spawn)(command, args, {
763
+ ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
764
+ env,
765
+ stdio: options.stdio ?? "inherit",
766
+ windowsHide: false
767
+ });
768
+ child.once("error", (err) => {
769
+ const message = err.code === "ENOENT" ? `无法启动子进程(命令不存在): ${command}\n${isLinux() ? wineInstallHint() : ""}` : `子进程启动失败: ${err.message}`;
770
+ node_process.default.stderr.write(`[napuketto-loader] ${message}\n`);
733
771
  });
734
772
  return {
735
- child: (0, node_child_process.spawn)(command, args, {
736
- ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
737
- env,
738
- stdio: options.stdio ?? "inherit",
739
- windowsHide: false
740
- }),
773
+ child,
741
774
  bootJsPath: selfHostPath
742
775
  };
743
776
  }
@@ -747,12 +780,18 @@ async function resolveNodeExecutable(options) {
747
780
  useWine: false,
748
781
  winNodePath: void 0
749
782
  };
783
+ const wine = wineBinary();
784
+ const hint = wineCheckError((0, node_child_process.spawnSync)(wine, ["--version"], { stdio: "ignore" }));
785
+ if (hint !== null) throw new Error(hint);
786
+ options.onStage?.("wine 就绪,获取 Windows 版 node.exe(Linux/wine 场景)…");
787
+ const winNode = await ensureWinNode({
788
+ ...options.cwd !== void 0 ? { dataRoot: options.cwd } : {},
789
+ ...options.winNodePath !== void 0 ? { exePath: options.winNodePath } : {}
790
+ });
791
+ options.onStage?.(`Windows 版 node.exe 就绪:${winNode.exePath}(${winNode.version})`);
750
792
  return {
751
793
  useWine: true,
752
- winNodePath: (await ensureWinNode({
753
- ...options.cwd !== void 0 ? { dataRoot: options.cwd } : {},
754
- ...options.winNodePath !== void 0 ? { exePath: options.winNodePath } : {}
755
- })).exePath
794
+ winNodePath: winNode.exePath
756
795
  };
757
796
  }
758
797
  /** 注入 PATH(stub 目录 + wrapper.node 目录前置)与 STUB_DIR(wine 场景转 Z:\)。 */
@@ -800,10 +839,6 @@ function buildLaunchEnv(options, useWine) {
800
839
  ...optional
801
840
  };
802
841
  }
803
- /** wine 可执行文件(linux 场景;NAPUTO_WINE 可覆盖)。 */
804
- function wineBinary$1() {
805
- return node_process.default.env["NAPUTO_WINE"] ?? "wine";
806
- }
807
842
  /** 环境变量名(self-host.cjs 与 kernel 引导读取)。 */
808
843
  const ENV = {
809
844
  QQ_PATH: "NAPUTO_QQ_PATH",
@@ -997,7 +1032,8 @@ async function resolveQqFiles(options = {}) {
997
1032
  if (cached !== null) return cached;
998
1033
  if (options.autoDownload !== false) return ensureQqFiles({
999
1034
  dataRoot,
1000
- ...options.sevenZipPath !== void 0 ? { sevenZipPath: options.sevenZipPath } : {}
1035
+ ...options.sevenZipPath !== void 0 ? { sevenZipPath: options.sevenZipPath } : {},
1036
+ ...options.onStage !== void 0 ? { onStage: options.onStage } : {}
1001
1037
  });
1002
1038
  throw new Error("未找到 QQ 原生文件(wrapper.node):请安装 QQ、设置 NAPUTO_QQ_PATH/NAPUTO_QQ_FILES,或使用下载流程(ensureQqFiles)自动获取");
1003
1039
  }
@@ -1075,30 +1111,43 @@ async function ensureLinuxSevenZip(options = {}) {
1075
1111
  /**
1076
1112
  * 确保 QQ 原生文件就绪(P1):幂等缓存检查 → 下载官方安装包 → sha256 校验
1077
1113
  * → 7z 解包 → 提取 wrapper.node/QQNT.dll → 缓存。返回缓存版本 QqInstallInfo。
1114
+ *
1115
+ * onStage:阶段回调(下载/解包等提示,2026-08-23 起——下载 313MB 全程静默
1116
+ * 的坑)。调用方接 logger.info。
1078
1117
  */
1079
1118
  async function ensureQqFiles(options = {}) {
1080
1119
  const dataRoot = resolveDataRoot(options.dataRoot);
1081
1120
  const cacheRoot = (0, node_path.join)(dataRoot, QQ_FILES_DIR_NAME);
1082
1121
  (0, node_fs.mkdirSync)(cacheRoot, { recursive: true });
1083
1122
  const existing = tryResolveCached(dataRoot);
1084
- if (existing !== null) return existing;
1123
+ if (existing !== null) {
1124
+ options.onStage?.(`QQ 原生文件缓存命中:版本 ${existing.version}`);
1125
+ return existing;
1126
+ }
1085
1127
  const release = latestRelease(loadQqReleases());
1086
1128
  const url = resolveDownloadUrl(release);
1087
1129
  const version = release.version;
1088
1130
  const tmpDir = (0, node_path.join)(dataRoot, "tmp");
1089
1131
  (0, node_fs.mkdirSync)(tmpDir, { recursive: true });
1090
- const installerPath = (0, node_path.join)(tmpDir, `qq-installer-${version}.exe`);
1132
+ const installerPath = (0, node_path.join)(tmpDir, `qq-installer-${version}-${node_process.default.pid}-${Date.now().toString(36)}.exe`);
1133
+ options.onStage?.(`下载 QQ 官方安装包 ${version}(首次使用需下载,约 300MB)…`);
1091
1134
  await downloadFile({
1092
1135
  dest: installerPath,
1093
1136
  url,
1094
1137
  expectedSha256: release.sha256
1095
1138
  });
1139
+ options.onStage?.(`QQ 安装包下载完成,sha256 校验通过(${release.sha256.slice(0, 12)}…)`);
1096
1140
  let sevenZipPath = options.sevenZipPath;
1097
1141
  if (sevenZipPath === void 0 && node_process.default.platform === "linux") sevenZipPath = (await ensureLinuxSevenZip({ dataRoot })).exe;
1098
- const extractedDir = (0, node_path.join)(tmpDir, `qq-extracted-${version}`);
1142
+ const extractedDir = (0, node_path.join)(tmpDir, `qq-extracted-${version}-${node_process.default.pid}`);
1099
1143
  try {
1144
+ if (!(0, node_fs.existsSync)(installerPath)) throw new Error(`下载产物在解包前丢失: ${installerPath}(疑为并发实例清理 tmp,已用唯一文件名规避,请重试)`);
1145
+ options.onStage?.(`7z 解包安装包(${version},需数分钟)…`);
1100
1146
  await extractInstaller(installerPath, extractedDir, sevenZipPath);
1101
- return await extractWrapperFiles(extractedDir, version, cacheRoot);
1147
+ options.onStage?.("提取 wrapper.node 及原生依赖 DLL…");
1148
+ const info = await extractWrapperFiles(extractedDir, version, cacheRoot);
1149
+ options.onStage?.(`QQ 原生文件就绪:缓存版本 ${info.version}`);
1150
+ return info;
1102
1151
  } catch (err) {
1103
1152
  await clearCacheVersion(cacheRoot, version);
1104
1153
  throw err;
package/dist/index.d.cts CHANGED
@@ -431,6 +431,11 @@ interface ResolveQqFilesOptions {
431
431
  autoDownload?: boolean;
432
432
  /** 7z 可执行文件路径(解包用;缺省自动探测)。 */
433
433
  sevenZipPath?: string;
434
+ /**
435
+ * 阶段回调(下载/校验/解包/提取各阶段提示,2026-08-23 WSL 事故后加:
436
+ * 下载 313MB 全程静默,用户以为流程没生效)。调用方接 logger.info。
437
+ */
438
+ onStage?: (message: string) => void;
434
439
  }
435
440
  /** 找 QQ.exe 路径。 */
436
441
  declare function locateQqPath(): string;
@@ -466,12 +471,17 @@ declare function ensureLinuxSevenZip(options?: {
466
471
  /**
467
472
  * 确保 QQ 原生文件就绪(P1):幂等缓存检查 → 下载官方安装包 → sha256 校验
468
473
  * → 7z 解包 → 提取 wrapper.node/QQNT.dll → 缓存。返回缓存版本 QqInstallInfo。
474
+ *
475
+ * onStage:阶段回调(下载/解包等提示,2026-08-23 起——下载 313MB 全程静默
476
+ * 的坑)。调用方接 logger.info。
469
477
  */
470
478
  declare function ensureQqFiles(options?: {
471
479
  /** 数据根(缓存 <数据根>/qq-files/)。 */
472
480
  dataRoot?: string;
473
481
  /** 7z 路径(缺省自动探测)。 */
474
482
  sevenZipPath?: string;
483
+ /** 阶段回调(下载/校验/解包/提取,接 logger.info)。 */
484
+ onStage?: (message: string) => void;
475
485
  }): Promise<QqInstallInfo>;
476
486
  //#endregion
477
487
  //#region src/launcher.d.ts
@@ -514,6 +524,11 @@ interface LaunchOptions {
514
524
  * 查找失败警告),其余转发——原生 printf 直写 fd 的字节无法从 JS 层拦截。
515
525
  */
516
526
  stdio?: StdioOptions;
527
+ /**
528
+ * 阶段回调(stub 校验/win-node 下载/spawn 提示,2026-08-23 起——下载
529
+ * 313MB 安装包与 win-node 全程静默的坑)。调用方接 logger.info。
530
+ */
531
+ onStage?: (message: string) => void;
517
532
  }
518
533
  interface LaunchResult {
519
534
  /** 子进程句柄(自建宿主 node 进程)。 */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/host/ipc/ipc-types.ts","../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":"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"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/host/ipc/ipc-types.ts","../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":"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"}
package/dist/index.d.mts CHANGED
@@ -431,6 +431,11 @@ interface ResolveQqFilesOptions {
431
431
  autoDownload?: boolean;
432
432
  /** 7z 可执行文件路径(解包用;缺省自动探测)。 */
433
433
  sevenZipPath?: string;
434
+ /**
435
+ * 阶段回调(下载/校验/解包/提取各阶段提示,2026-08-23 WSL 事故后加:
436
+ * 下载 313MB 全程静默,用户以为流程没生效)。调用方接 logger.info。
437
+ */
438
+ onStage?: (message: string) => void;
434
439
  }
435
440
  /** 找 QQ.exe 路径。 */
436
441
  declare function locateQqPath(): string;
@@ -466,12 +471,17 @@ declare function ensureLinuxSevenZip(options?: {
466
471
  /**
467
472
  * 确保 QQ 原生文件就绪(P1):幂等缓存检查 → 下载官方安装包 → sha256 校验
468
473
  * → 7z 解包 → 提取 wrapper.node/QQNT.dll → 缓存。返回缓存版本 QqInstallInfo。
474
+ *
475
+ * onStage:阶段回调(下载/解包等提示,2026-08-23 起——下载 313MB 全程静默
476
+ * 的坑)。调用方接 logger.info。
469
477
  */
470
478
  declare function ensureQqFiles(options?: {
471
479
  /** 数据根(缓存 <数据根>/qq-files/)。 */
472
480
  dataRoot?: string;
473
481
  /** 7z 路径(缺省自动探测)。 */
474
482
  sevenZipPath?: string;
483
+ /** 阶段回调(下载/校验/解包/提取,接 logger.info)。 */
484
+ onStage?: (message: string) => void;
475
485
  }): Promise<QqInstallInfo>;
476
486
  //#endregion
477
487
  //#region src/launcher.d.ts
@@ -514,6 +524,11 @@ interface LaunchOptions {
514
524
  * 查找失败警告),其余转发——原生 printf 直写 fd 的字节无法从 JS 层拦截。
515
525
  */
516
526
  stdio?: StdioOptions;
527
+ /**
528
+ * 阶段回调(stub 校验/win-node 下载/spawn 提示,2026-08-23 起——下载
529
+ * 313MB 安装包与 win-node 全程静默的坑)。调用方接 logger.info。
530
+ */
531
+ onStage?: (message: string) => void;
517
532
  }
518
533
  interface LaunchResult {
519
534
  /** 子进程句柄(自建宿主 node 进程)。 */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/host/ipc/ipc-types.ts","../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":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/host/ipc/ipc-types.ts","../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":"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"}
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import process$1 from "node:process";
5
- import { execFile, execFileSync, spawn } from "node:child_process";
5
+ import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { chmod, copyFile, mkdir, readdir, rm } from "node:fs/promises";
8
8
  import { createHash } from "node:crypto";
@@ -416,6 +416,16 @@ async function downloadFile(options) {
416
416
  if (!fileFailed) await rm(options.dest, { force: true });
417
417
  throw err instanceof Error ? err : new DownloadError(String(err));
418
418
  }
419
+ let size;
420
+ try {
421
+ size = statSync(options.dest).size;
422
+ } catch {
423
+ throw new DownloadError(`下载完成但文件缺失: ${options.dest}`);
424
+ }
425
+ if (size <= 0) {
426
+ await rm(options.dest, { force: true });
427
+ throw new DownloadError(`下载完成但文件为空: ${options.dest}`);
428
+ }
419
429
  const sha256 = hash.digest("hex");
420
430
  const expected = options.expectedSha256;
421
431
  const verified = expected === void 0 || expected === "" ? true : sha256.toLowerCase() === expected.toLowerCase();
@@ -652,6 +662,22 @@ function unixPathToWinePath(unixPath) {
652
662
  function wineBinary() {
653
663
  return process.env["NAPUTO_WINE"] ?? "wine";
654
664
  }
665
+ /** wine 安装指引(缺失时提示用户如何准备,纯字符串可单测)。 */
666
+ function wineInstallHint() {
667
+ return "Linux/WSL 自建宿主需要 wine 运行 Windows 版 node.exe(wrapper.node 是Windows PE 原生模块,只能被 Windows node.exe dlopen)。\n 安装:sudo apt-get install wine64(或其他发行版对应 wine 包)\n 覆盖:设置环境变量 NAPUTO_WINE 指向 wine 可执行文件路径";
668
+ }
669
+ /**
670
+ * wine 可执行性探测结果 → 可读错误(null = 可用)。纯函数,可单测。
671
+ * 由 launcher 在 spawn 前用 spawnSync 探测 wine(缺失时 throw 可读错误,
672
+ * 避免 spawn 异步 emit 'error' 无监听者导致宿主进程崩溃,2026-08-23 WSL 实测)。
673
+ */
674
+ function wineCheckError(probe) {
675
+ if (probe.status === 0) return null;
676
+ const err = probe.error;
677
+ if (err !== void 0 && err.code === "ENOENT") return `未检测到 wine 可执行文件。${wineInstallHint()}`;
678
+ if (err !== void 0) return `wine 探测失败: ${err.message ?? String(err)}。${wineInstallHint()}`;
679
+ return `wine --version 退出码非 0(status=${String(probe.status)}),wine 可能异常。${wineInstallHint()}`;
680
+ }
655
681
  /** 组装 spawn 命令(win32 vs linux 分支)。 */
656
682
  function buildSpawnCommand(options) {
657
683
  if ((options.platform ?? process.platform) === "win32") return {
@@ -694,6 +720,7 @@ async function launchSelfHost(options) {
694
720
  if (!existsSync(selfHostPath)) throw new Error(`self-host.cjs 缺失: ${selfHostPath}(先运行 pnpm --filter @napuketto/loader build)`);
695
721
  const stub = options.stubDir ?? defaultStubDir();
696
722
  if (!existsSync(join(stub, "QQNT.dll"))) throw new Error(`stub QQNT.dll 未找到: ${stub}(自建宿主必需:stub 转发 QQNT.dll 符号到 node.exe;请用 --stub-dir 或环境变量 NAPUTO_STUB_DIR 指定 stub 目录)`);
723
+ options.onStage?.(`stub QQNT.dll 就绪:${stub}`);
697
724
  const { useWine, winNodePath } = await resolveNodeExecutable(options);
698
725
  const env = buildLaunchEnv(options, useWine);
699
726
  applyPathEnv(env, {
@@ -705,15 +732,21 @@ async function launchSelfHost(options) {
705
732
  const { command, args } = buildSpawnCommand({
706
733
  ...useWine && winNodePath !== void 0 ? { winNodePath } : { winNodePath: process$1.execPath },
707
734
  selfHostPath: useWine ? toWinePath(selfHostPath) : selfHostPath,
708
- wine: wineBinary$1()
735
+ wine: wineBinary()
736
+ });
737
+ options.onStage?.(`启动自建宿主子进程:${command} ${args.join(" ")}`);
738
+ const child = spawn(command, args, {
739
+ ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
740
+ env,
741
+ stdio: options.stdio ?? "inherit",
742
+ windowsHide: false
743
+ });
744
+ child.once("error", (err) => {
745
+ const message = err.code === "ENOENT" ? `无法启动子进程(命令不存在): ${command}\n${isLinux() ? wineInstallHint() : ""}` : `子进程启动失败: ${err.message}`;
746
+ process$1.stderr.write(`[napuketto-loader] ${message}\n`);
709
747
  });
710
748
  return {
711
- child: spawn(command, args, {
712
- ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
713
- env,
714
- stdio: options.stdio ?? "inherit",
715
- windowsHide: false
716
- }),
749
+ child,
717
750
  bootJsPath: selfHostPath
718
751
  };
719
752
  }
@@ -723,12 +756,18 @@ async function resolveNodeExecutable(options) {
723
756
  useWine: false,
724
757
  winNodePath: void 0
725
758
  };
759
+ const wine = wineBinary();
760
+ const hint = wineCheckError(spawnSync(wine, ["--version"], { stdio: "ignore" }));
761
+ if (hint !== null) throw new Error(hint);
762
+ options.onStage?.("wine 就绪,获取 Windows 版 node.exe(Linux/wine 场景)…");
763
+ const winNode = await ensureWinNode({
764
+ ...options.cwd !== void 0 ? { dataRoot: options.cwd } : {},
765
+ ...options.winNodePath !== void 0 ? { exePath: options.winNodePath } : {}
766
+ });
767
+ options.onStage?.(`Windows 版 node.exe 就绪:${winNode.exePath}(${winNode.version})`);
726
768
  return {
727
769
  useWine: true,
728
- winNodePath: (await ensureWinNode({
729
- ...options.cwd !== void 0 ? { dataRoot: options.cwd } : {},
730
- ...options.winNodePath !== void 0 ? { exePath: options.winNodePath } : {}
731
- })).exePath
770
+ winNodePath: winNode.exePath
732
771
  };
733
772
  }
734
773
  /** 注入 PATH(stub 目录 + wrapper.node 目录前置)与 STUB_DIR(wine 场景转 Z:\)。 */
@@ -776,10 +815,6 @@ function buildLaunchEnv(options, useWine) {
776
815
  ...optional
777
816
  };
778
817
  }
779
- /** wine 可执行文件(linux 场景;NAPUTO_WINE 可覆盖)。 */
780
- function wineBinary$1() {
781
- return process$1.env["NAPUTO_WINE"] ?? "wine";
782
- }
783
818
  /** 环境变量名(self-host.cjs 与 kernel 引导读取)。 */
784
819
  const ENV = {
785
820
  QQ_PATH: "NAPUTO_QQ_PATH",
@@ -973,7 +1008,8 @@ async function resolveQqFiles(options = {}) {
973
1008
  if (cached !== null) return cached;
974
1009
  if (options.autoDownload !== false) return ensureQqFiles({
975
1010
  dataRoot,
976
- ...options.sevenZipPath !== void 0 ? { sevenZipPath: options.sevenZipPath } : {}
1011
+ ...options.sevenZipPath !== void 0 ? { sevenZipPath: options.sevenZipPath } : {},
1012
+ ...options.onStage !== void 0 ? { onStage: options.onStage } : {}
977
1013
  });
978
1014
  throw new Error("未找到 QQ 原生文件(wrapper.node):请安装 QQ、设置 NAPUTO_QQ_PATH/NAPUTO_QQ_FILES,或使用下载流程(ensureQqFiles)自动获取");
979
1015
  }
@@ -1051,30 +1087,43 @@ async function ensureLinuxSevenZip(options = {}) {
1051
1087
  /**
1052
1088
  * 确保 QQ 原生文件就绪(P1):幂等缓存检查 → 下载官方安装包 → sha256 校验
1053
1089
  * → 7z 解包 → 提取 wrapper.node/QQNT.dll → 缓存。返回缓存版本 QqInstallInfo。
1090
+ *
1091
+ * onStage:阶段回调(下载/解包等提示,2026-08-23 起——下载 313MB 全程静默
1092
+ * 的坑)。调用方接 logger.info。
1054
1093
  */
1055
1094
  async function ensureQqFiles(options = {}) {
1056
1095
  const dataRoot = resolveDataRoot(options.dataRoot);
1057
1096
  const cacheRoot = join(dataRoot, QQ_FILES_DIR_NAME);
1058
1097
  mkdirSync(cacheRoot, { recursive: true });
1059
1098
  const existing = tryResolveCached(dataRoot);
1060
- if (existing !== null) return existing;
1099
+ if (existing !== null) {
1100
+ options.onStage?.(`QQ 原生文件缓存命中:版本 ${existing.version}`);
1101
+ return existing;
1102
+ }
1061
1103
  const release = latestRelease(loadQqReleases());
1062
1104
  const url = resolveDownloadUrl(release);
1063
1105
  const version = release.version;
1064
1106
  const tmpDir = join(dataRoot, "tmp");
1065
1107
  mkdirSync(tmpDir, { recursive: true });
1066
- const installerPath = join(tmpDir, `qq-installer-${version}.exe`);
1108
+ const installerPath = join(tmpDir, `qq-installer-${version}-${process$1.pid}-${Date.now().toString(36)}.exe`);
1109
+ options.onStage?.(`下载 QQ 官方安装包 ${version}(首次使用需下载,约 300MB)…`);
1067
1110
  await downloadFile({
1068
1111
  dest: installerPath,
1069
1112
  url,
1070
1113
  expectedSha256: release.sha256
1071
1114
  });
1115
+ options.onStage?.(`QQ 安装包下载完成,sha256 校验通过(${release.sha256.slice(0, 12)}…)`);
1072
1116
  let sevenZipPath = options.sevenZipPath;
1073
1117
  if (sevenZipPath === void 0 && process$1.platform === "linux") sevenZipPath = (await ensureLinuxSevenZip({ dataRoot })).exe;
1074
- const extractedDir = join(tmpDir, `qq-extracted-${version}`);
1118
+ const extractedDir = join(tmpDir, `qq-extracted-${version}-${process$1.pid}`);
1075
1119
  try {
1120
+ if (!existsSync(installerPath)) throw new Error(`下载产物在解包前丢失: ${installerPath}(疑为并发实例清理 tmp,已用唯一文件名规避,请重试)`);
1121
+ options.onStage?.(`7z 解包安装包(${version},需数分钟)…`);
1076
1122
  await extractInstaller(installerPath, extractedDir, sevenZipPath);
1077
- return await extractWrapperFiles(extractedDir, version, cacheRoot);
1123
+ options.onStage?.("提取 wrapper.node 及原生依赖 DLL…");
1124
+ const info = await extractWrapperFiles(extractedDir, version, cacheRoot);
1125
+ options.onStage?.(`QQ 原生文件就绪:缓存版本 ${info.version}`);
1126
+ return info;
1078
1127
  } catch (err) {
1079
1128
  await clearCacheVersion(cacheRoot, version);
1080
1129
  throw err;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["process","process","packageRoot","process","process","process","process","wineBinary","process","process"],"sources":["../src/host/ipc/ipc-types.ts","../src/instance-lock.ts","../src/data-root.ts","../src/package-info.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 * ipc-types.ts:IPC 协议消息(koishi 插件 ↔ self-host 子进程,单一来源)。\n *\n * 协议契约(koishi 插件 design.md §5.6):`{ v, type, id?, payload }` JSON 行。\n * 用 zod v4 定义单一 schema,两侧(loader host + koishi 插件)import 同一份:\n * 类型经 z.infer 派生、运行时经 safeParse 校验,消除「两份手写 types.ts」漂移。\n *\n * 领域字段(LoginState / SelfInfo / QrCodeData)与 kernel 同名类型结构等价,\n * 在协议边界内联为 zod schema(kernel 不依赖 zod,故不在 kernel 定义)。\n */\nimport { z } from \"zod\";\n\n/** IPC 协议版本(解码校验)。 */\nexport const IPC_VERSION = 1;\n\n/** 引导阶段(status 消息)。 */\nexport const IpcBootPhaseSchema = z.enum([\n \"booting\", // 子进程已启动(spawn 成功)\n \"dlopening\", // dlopen wrapper.node\n \"logging\", // 登录流程进行中\n \"sessioning\", // session 初始化中\n \"ready\", // session READY + 业务 service 装配完成\n \"failed\", // 引导失败(携带错误)\n]);\n\n/** 结构化日志级别(pino level)。 */\nexport const IpcLogLevelSchema = z.enum([\"trace\", \"debug\", \"info\", \"warn\", \"error\", \"fatal\"]);\n\n/** 登录状态(与 kernel LoginState 结构等价)。 */\nconst LoginStateSchema = z.enum([\"idle\", \"waiting_scan\", \"scanned\", \"logged_in\", \"failed\"]);\n\n/** selfInfo(与 kernel SelfInfo 结构等价)。 */\nconst SelfInfoSchema = z.object({\n uin: z.string(),\n uid: z.string(),\n nick: z.string(),\n});\n\n/** status 消息 payload。 */\nexport const IpcStatusPayloadSchema = z.object({\n phase: IpcBootPhaseSchema,\n message: z.string().optional(),\n error: z.object({ code: z.string(), message: z.string() }).optional(),\n});\n\n/** login 消息 payload。 */\nexport const IpcLoginPayloadSchema = z.object({\n state: LoginStateSchema,\n selfInfo: SelfInfoSchema.optional(),\n /** 失败原因(state=failed 时;如「登录超时,请刷新页面重试」)。 */\n message: z.string().optional(),\n});\n\n/** event 消息 payload(kernel 事件通道形状,翻译层按 service/name 断言具体类型)。 */\nexport const IpcEventPayloadSchema = z.object({\n service: z.string(),\n name: z.string(),\n args: z.array(z.unknown()),\n});\n\n/** log 消息 payload。 */\nexport const IpcLogPayloadSchema = z.object({\n level: IpcLogLevelSchema,\n message: z.string(),\n});\n\n/** 动作响应 payload(result 消息)。 */\nexport const IpcResultPayloadSchema = z.union([\n z.object({ ok: z.literal(true), value: z.unknown().optional() }),\n z.object({ ok: z.literal(false), error: z.object({ code: z.string(), message: z.string() }) }),\n]);\n\n/** 动作请求 payload(action 消息)。 */\nexport const IpcActionPayloadSchema = z.object({\n action: z.string(),\n params: z.record(z.string(), z.unknown()).optional(),\n});\n\n/** 控制指令 payload(control 消息,父→子)。 */\nexport const IpcControlPayloadSchema = z.union([\n z.object({ command: z.literal(\"stop\") }), // 优雅退出\n z.object({ command: z.literal(\"restart\") }), // 退出并期望驱动层重启\n z.object({\n command: z.literal(\"login\"),\n uin: z.string().optional(),\n qr: z.boolean().optional(),\n }), // 触发登录(预留)\n]);\n\n// ── 消息联合 ──\n\nconst IpcStatusMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"status\"),\n payload: IpcStatusPayloadSchema,\n});\n\nconst IpcLoginMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"login\"),\n payload: IpcLoginPayloadSchema,\n});\n\nconst IpcQrMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"qr\"),\n payload: z.object({ pngBase64: z.string(), qrcodeUrl: z.string() }),\n});\n\nconst IpcEventMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"event\"),\n payload: IpcEventPayloadSchema,\n});\n\nconst IpcResultMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"result\"),\n id: z.number(),\n payload: IpcResultPayloadSchema,\n});\n\nconst IpcLogMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"log\"),\n payload: IpcLogPayloadSchema,\n});\n\nconst IpcPingMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"ping\"),\n});\n\nconst IpcPongMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"pong\"),\n});\n\nconst IpcActionMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"action\"),\n id: z.number(),\n payload: IpcActionPayloadSchema,\n});\n\nconst IpcControlMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"control\"),\n payload: IpcControlPayloadSchema,\n});\n\n/** IPC 消息判别联合(协议边界,运行时经 safeParse 校验)。 */\nexport const IpcMessageSchema = z.discriminatedUnion(\"type\", [\n IpcStatusMessageSchema,\n IpcLoginMessageSchema,\n IpcQrMessageSchema,\n IpcEventMessageSchema,\n IpcResultMessageSchema,\n IpcLogMessageSchema,\n IpcPingMessageSchema,\n IpcPongMessageSchema,\n IpcActionMessageSchema,\n IpcControlMessageSchema,\n]);\n\n// ── 派生类型(z.infer,两侧消费同名类型,无镜像漂移) ──\n\nexport type IpcBootPhase = z.infer<typeof IpcBootPhaseSchema>;\nexport type IpcLogLevel = z.infer<typeof IpcLogLevelSchema>;\nexport type IpcStatusPayload = z.infer<typeof IpcStatusPayloadSchema>;\nexport type IpcLoginPayload = z.infer<typeof IpcLoginPayloadSchema>;\nexport type IpcEventPayload = z.infer<typeof IpcEventPayloadSchema>;\nexport type IpcLogPayload = z.infer<typeof IpcLogPayloadSchema>;\nexport type IpcResultPayload = z.infer<typeof IpcResultPayloadSchema>;\nexport type IpcActionPayload = z.infer<typeof IpcActionPayloadSchema>;\nexport type IpcControlPayload = z.infer<typeof IpcControlPayloadSchema>;\nexport type IpcStatusMessage = z.infer<typeof IpcStatusMessageSchema>;\nexport type IpcLoginMessage = z.infer<typeof IpcLoginMessageSchema>;\nexport type IpcQrMessage = z.infer<typeof IpcQrMessageSchema>;\nexport type IpcEventMessage = z.infer<typeof IpcEventMessageSchema>;\nexport type IpcResultMessage = z.infer<typeof IpcResultMessageSchema>;\nexport type IpcLogMessage = z.infer<typeof IpcLogMessageSchema>;\nexport type IpcPingMessage = z.infer<typeof IpcPingMessageSchema>;\nexport type IpcPongMessage = z.infer<typeof IpcPongMessageSchema>;\nexport type IpcActionMessage = z.infer<typeof IpcActionMessageSchema>;\nexport type IpcControlMessage = z.infer<typeof IpcControlMessageSchema>;\nexport type IpcMessage = z.infer<typeof IpcMessageSchema>;\n","/**\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 * data-root.ts:轻量数据根解析(loader 侧,避免新增 kernel 依赖)。\n *\n * 从 locate-qq.ts / win-node.ts 抽出的同构逻辑(2026-08 消重:两处\n * 22 行重复 resolveDataRoot + findProjectRoot)。\n *\n * 优先级:显式参数 > NAPKETTO_DATA > 项目根/.napuketto > cwd 兜底。\n * P2 若需完整语义(同 kernel resolveDataRoot)再切换。\n */\nimport { existsSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport process from \"node:process\";\n\n/** 解析数据根(NAPKETTO_DATA 显式 > 项目根/.napuketto > cwd 兜底)。 */\nexport function 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 * package-info.ts:运行时读取本包 package.json 元数据。\n *\n * ESM 下用 import.meta.url 定位包根(dist/ 与包根同层:dist/index.mjs → ../package.json);\n * CJS 输出由 rolldown 将 import.meta.url 转为 pathToFileURL(__filename) 等价形式,\n * 定位行为不变(设计文档 §发布形态)。\n */\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** 包根路径(dist 与包根同层:dist/index.mjs → ../package.json)。 */\nfunction packageRoot(): string {\n return dirname(dirname(fileURLToPath(import.meta.url)));\n}\n\n/**\n * 读取本包版本号;失败(发布产物缺 package.json / 解析异常)兜底 \"0.0.0\"。\n * 仅用于下载 User-Agent 等非关键元数据,失败不阻塞主流程。\n */\nexport function loaderVersion(): string {\n try {\n const raw = readFileSync(join(packageRoot(), \"package.json\"), \"utf8\");\n const parsed = JSON.parse(raw) as { version?: unknown };\n if (typeof parsed.version === \"string\" && parsed.version !== \"\") {\n return parsed.version;\n }\n } catch {\n // 读取/解析失败——仅影响 UA 与日志标识,兜底即可\n }\n return \"0.0.0\";\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\";\nimport { loaderVersion } from \"./package-info.js\";\n\n/** 下载请求 User-Agent(运行时读本包版本,失败兜底 0.0.0)。 */\nconst USER_AGENT = `napuketto-loader/${loaderVersion()}`;\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\": USER_AGENT },\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 环境变量 > 内置资产(Windows assets/7zip/7z.exe、Linux\n * assets/7zip/7zz——2026-08-14 治本,静态二进制已打进发布包)> 系统 PATH 7z。\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\" || process.platform === \"linux\") {\n const exeName = process.platform === \"win32\" ? \"7z.exe\" : \"7zz\";\n const bundled = join(\n dirname(fileURLToPath(import.meta.url)),\n \"..\",\n \"assets\",\n \"7zip\",\n exeName,\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 { join, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { resolveDataRoot } from \"./data-root.js\";\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/**\n * 默认 Windows node 版本(latest v24,LTS 线;可被环境变量/参数覆盖)。\n * ⚠️ nodejs.org/dist 保留历史版本不会 404,但 pin 死旧 patch 收不到安全更新——\n * 升级 Windows node 时记得同步 bump 此常量(或经 NAPUTO_WIN_NODE_VERSION 覆盖)。\n */\nconst DEFAULT_WIN_NODE_VERSION = \"v24.16.0\";\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)。\n *\n * 维护机制(真实存在,非「声称」):清单由 CI 自动维护——\n * .github/workflows/update-qq-releases.yml(每日 cron + 手动触发)调用\n * scripts/qq-releases/update-qq-releases.ts(纯 Node,与本地共用),抓取 QQ 官方下载配置\n * 解析最新版 + 下载安装包计算 sha256 + 解析安装包内部版本目录后更新本清单\n * (保留历史条目、known 按版本递增)。社区 PR 仍可手动补充。NAPUTO_QQ_URL\n * 可运行时覆盖下载地址(用户拿到新链接时用)。\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 解析;0 = 尚未解析/待运行时补)。 */\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 { fileURLToPath } from \"node:url\";\nimport { resolveDataRoot } from \"./data-root.js\";\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/**\n * 7-Zip 官方 Linux 版版本号(tar.xz 文件名组成部分;兜底下载用,内置资产优先)。\n * ⚠️ 此值为最后兜底下载用,7-Zip 官网只留最新版,旧版号会 404;内置资产失败才触发。\n */\nconst SEVEN_ZIP_LINUX_VERSION = \"2501\";\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 ];\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 = resolveDataRoot(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 就绪(治本:内置资产优先,下载兜底)。\n *\n * 优先级:内置资产 assets/7zip/7zz(2026-08-14 治本——与 Windows 7z.exe 同模式,\n * 7zz 静态二进制已打进发布包,LGPL 合规可分发,零运行时下载)> 数据根缓存\n * runtime/7zip/7zz(历史下载产物复用)> 官网下载(兜底,NAPUTO_7Z_URL 可覆盖)。\n *\n * 背景:7-Zip 官网只保留最新版 tar.xz,硬编码旧版本号(如 2409)会被删档 404,\n * 运行时下载既脆弱又依赖外网。内置资产彻底消灭该依赖。\n */\nexport async function ensureLinuxSevenZip(\n options: { dataRoot?: string } = {},\n): Promise<{ exe: string; source: string }> {\n // 1. 内置资产(治本):dist 与 assets 同层(tsdown 构建后 dist/,资产在包根 assets/)\n const bundled = join(dirname(fileURLToPath(import.meta.url)), \"..\", \"assets\", \"7zip\", \"7zz\");\n if (existsSync(bundled)) {\n // npm 安装可能丢失执行位,保险设置(失败不阻塞:execFile 报 EACCES 时由调用方提示)\n await chmod(bundled, 0o755).catch(() => {\n // 忽略:无执行位场景后续解包会明确报错,此处不静默吞掉主流程\n });\n return { exe: bundled, source: \"内置资产 assets/7zip\" };\n }\n\n // 2. 数据根缓存(历史下载产物复用)\n const dataRoot = resolveDataRoot(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 // 3. 官网下载兜底(NAPUTO_7Z_URL 覆盖;仅当发布包缺失旧形态时触发)\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 = resolveDataRoot(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"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["process","process","packageRoot","process","process","process","process","process","process"],"sources":["../src/host/ipc/ipc-types.ts","../src/instance-lock.ts","../src/data-root.ts","../src/package-info.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 * ipc-types.ts:IPC 协议消息(koishi 插件 ↔ self-host 子进程,单一来源)。\n *\n * 协议契约(koishi 插件 design.md §5.6):`{ v, type, id?, payload }` JSON 行。\n * 用 zod v4 定义单一 schema,两侧(loader host + koishi 插件)import 同一份:\n * 类型经 z.infer 派生、运行时经 safeParse 校验,消除「两份手写 types.ts」漂移。\n *\n * 领域字段(LoginState / SelfInfo / QrCodeData)与 kernel 同名类型结构等价,\n * 在协议边界内联为 zod schema(kernel 不依赖 zod,故不在 kernel 定义)。\n */\nimport { z } from \"zod\";\n\n/** IPC 协议版本(解码校验)。 */\nexport const IPC_VERSION = 1;\n\n/** 引导阶段(status 消息)。 */\nexport const IpcBootPhaseSchema = z.enum([\n \"booting\", // 子进程已启动(spawn 成功)\n \"dlopening\", // dlopen wrapper.node\n \"logging\", // 登录流程进行中\n \"sessioning\", // session 初始化中\n \"ready\", // session READY + 业务 service 装配完成\n \"failed\", // 引导失败(携带错误)\n]);\n\n/** 结构化日志级别(pino level)。 */\nexport const IpcLogLevelSchema = z.enum([\"trace\", \"debug\", \"info\", \"warn\", \"error\", \"fatal\"]);\n\n/** 登录状态(与 kernel LoginState 结构等价)。 */\nconst LoginStateSchema = z.enum([\"idle\", \"waiting_scan\", \"scanned\", \"logged_in\", \"failed\"]);\n\n/** selfInfo(与 kernel SelfInfo 结构等价)。 */\nconst SelfInfoSchema = z.object({\n uin: z.string(),\n uid: z.string(),\n nick: z.string(),\n});\n\n/** status 消息 payload。 */\nexport const IpcStatusPayloadSchema = z.object({\n phase: IpcBootPhaseSchema,\n message: z.string().optional(),\n error: z.object({ code: z.string(), message: z.string() }).optional(),\n});\n\n/** login 消息 payload。 */\nexport const IpcLoginPayloadSchema = z.object({\n state: LoginStateSchema,\n selfInfo: SelfInfoSchema.optional(),\n /** 失败原因(state=failed 时;如「登录超时,请刷新页面重试」)。 */\n message: z.string().optional(),\n});\n\n/** event 消息 payload(kernel 事件通道形状,翻译层按 service/name 断言具体类型)。 */\nexport const IpcEventPayloadSchema = z.object({\n service: z.string(),\n name: z.string(),\n args: z.array(z.unknown()),\n});\n\n/** log 消息 payload。 */\nexport const IpcLogPayloadSchema = z.object({\n level: IpcLogLevelSchema,\n message: z.string(),\n});\n\n/** 动作响应 payload(result 消息)。 */\nexport const IpcResultPayloadSchema = z.union([\n z.object({ ok: z.literal(true), value: z.unknown().optional() }),\n z.object({ ok: z.literal(false), error: z.object({ code: z.string(), message: z.string() }) }),\n]);\n\n/** 动作请求 payload(action 消息)。 */\nexport const IpcActionPayloadSchema = z.object({\n action: z.string(),\n params: z.record(z.string(), z.unknown()).optional(),\n});\n\n/** 控制指令 payload(control 消息,父→子)。 */\nexport const IpcControlPayloadSchema = z.union([\n z.object({ command: z.literal(\"stop\") }), // 优雅退出\n z.object({ command: z.literal(\"restart\") }), // 退出并期望驱动层重启\n z.object({\n command: z.literal(\"login\"),\n uin: z.string().optional(),\n qr: z.boolean().optional(),\n }), // 触发登录(预留)\n]);\n\n// ── 消息联合 ──\n\nconst IpcStatusMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"status\"),\n payload: IpcStatusPayloadSchema,\n});\n\nconst IpcLoginMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"login\"),\n payload: IpcLoginPayloadSchema,\n});\n\nconst IpcQrMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"qr\"),\n payload: z.object({ pngBase64: z.string(), qrcodeUrl: z.string() }),\n});\n\nconst IpcEventMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"event\"),\n payload: IpcEventPayloadSchema,\n});\n\nconst IpcResultMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"result\"),\n id: z.number(),\n payload: IpcResultPayloadSchema,\n});\n\nconst IpcLogMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"log\"),\n payload: IpcLogPayloadSchema,\n});\n\nconst IpcPingMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"ping\"),\n});\n\nconst IpcPongMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"pong\"),\n});\n\nconst IpcActionMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"action\"),\n id: z.number(),\n payload: IpcActionPayloadSchema,\n});\n\nconst IpcControlMessageSchema = z.object({\n v: z.literal(IPC_VERSION),\n type: z.literal(\"control\"),\n payload: IpcControlPayloadSchema,\n});\n\n/** IPC 消息判别联合(协议边界,运行时经 safeParse 校验)。 */\nexport const IpcMessageSchema = z.discriminatedUnion(\"type\", [\n IpcStatusMessageSchema,\n IpcLoginMessageSchema,\n IpcQrMessageSchema,\n IpcEventMessageSchema,\n IpcResultMessageSchema,\n IpcLogMessageSchema,\n IpcPingMessageSchema,\n IpcPongMessageSchema,\n IpcActionMessageSchema,\n IpcControlMessageSchema,\n]);\n\n// ── 派生类型(z.infer,两侧消费同名类型,无镜像漂移) ──\n\nexport type IpcBootPhase = z.infer<typeof IpcBootPhaseSchema>;\nexport type IpcLogLevel = z.infer<typeof IpcLogLevelSchema>;\nexport type IpcStatusPayload = z.infer<typeof IpcStatusPayloadSchema>;\nexport type IpcLoginPayload = z.infer<typeof IpcLoginPayloadSchema>;\nexport type IpcEventPayload = z.infer<typeof IpcEventPayloadSchema>;\nexport type IpcLogPayload = z.infer<typeof IpcLogPayloadSchema>;\nexport type IpcResultPayload = z.infer<typeof IpcResultPayloadSchema>;\nexport type IpcActionPayload = z.infer<typeof IpcActionPayloadSchema>;\nexport type IpcControlPayload = z.infer<typeof IpcControlPayloadSchema>;\nexport type IpcStatusMessage = z.infer<typeof IpcStatusMessageSchema>;\nexport type IpcLoginMessage = z.infer<typeof IpcLoginMessageSchema>;\nexport type IpcQrMessage = z.infer<typeof IpcQrMessageSchema>;\nexport type IpcEventMessage = z.infer<typeof IpcEventMessageSchema>;\nexport type IpcResultMessage = z.infer<typeof IpcResultMessageSchema>;\nexport type IpcLogMessage = z.infer<typeof IpcLogMessageSchema>;\nexport type IpcPingMessage = z.infer<typeof IpcPingMessageSchema>;\nexport type IpcPongMessage = z.infer<typeof IpcPongMessageSchema>;\nexport type IpcActionMessage = z.infer<typeof IpcActionMessageSchema>;\nexport type IpcControlMessage = z.infer<typeof IpcControlMessageSchema>;\nexport type IpcMessage = z.infer<typeof IpcMessageSchema>;\n","/**\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 * data-root.ts:轻量数据根解析(loader 侧,避免新增 kernel 依赖)。\n *\n * 从 locate-qq.ts / win-node.ts 抽出的同构逻辑(2026-08 消重:两处\n * 22 行重复 resolveDataRoot + findProjectRoot)。\n *\n * 优先级:显式参数 > NAPKETTO_DATA > 项目根/.napuketto > cwd 兜底。\n * P2 若需完整语义(同 kernel resolveDataRoot)再切换。\n */\nimport { existsSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport process from \"node:process\";\n\n/** 解析数据根(NAPKETTO_DATA 显式 > 项目根/.napuketto > cwd 兜底)。 */\nexport function 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 * package-info.ts:运行时读取本包 package.json 元数据。\n *\n * ESM 下用 import.meta.url 定位包根(dist/ 与包根同层:dist/index.mjs → ../package.json);\n * CJS 输出由 rolldown 将 import.meta.url 转为 pathToFileURL(__filename) 等价形式,\n * 定位行为不变(设计文档 §发布形态)。\n */\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** 包根路径(dist 与包根同层:dist/index.mjs → ../package.json)。 */\nfunction packageRoot(): string {\n return dirname(dirname(fileURLToPath(import.meta.url)));\n}\n\n/**\n * 读取本包版本号;失败(发布产物缺 package.json / 解析异常)兜底 \"0.0.0\"。\n * 仅用于下载 User-Agent 等非关键元数据,失败不阻塞主流程。\n */\nexport function loaderVersion(): string {\n try {\n const raw = readFileSync(join(packageRoot(), \"package.json\"), \"utf8\");\n const parsed = JSON.parse(raw) as { version?: unknown };\n if (typeof parsed.version === \"string\" && parsed.version !== \"\") {\n return parsed.version;\n }\n } catch {\n // 读取/解析失败——仅影响 UA 与日志标识,兜底即可\n }\n return \"0.0.0\";\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, statSync } 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\";\nimport { loaderVersion } from \"./package-info.js\";\n\n/** 下载请求 User-Agent(运行时读本包版本,失败兜底 0.0.0)。 */\nconst USER_AGENT = `napuketto-loader/${loaderVersion()}`;\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\": USER_AGENT },\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 // ⚠️ 完成态校验(2026-08-23 WSL 生产事故):Promise 正常 resolve 但文件\n // 缺失/为空是真实发生过的(koishi 场景 7z 解包报「No such file or\n // directory」,疑为并发实例清理 tmp 或写入中断)。resolve 不代表落盘完整,\n // 这里 stat 兜底,缺失/空文件立即报错而不是把坏文件交给下游解包。\n let size: number;\n try {\n size = statSync(options.dest).size;\n } catch {\n throw new DownloadError(`下载完成但文件缺失: ${options.dest}`);\n }\n if (size <= 0) {\n await rm(options.dest, { force: true });\n throw new DownloadError(`下载完成但文件为空: ${options.dest}`);\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 环境变量 > 内置资产(Windows assets/7zip/7z.exe、Linux\n * assets/7zip/7zz——2026-08-14 治本,静态二进制已打进发布包)> 系统 PATH 7z。\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\" || process.platform === \"linux\") {\n const exeName = process.platform === \"win32\" ? \"7z.exe\" : \"7zz\";\n const bundled = join(\n dirname(fileURLToPath(import.meta.url)),\n \"..\",\n \"assets\",\n \"7zip\",\n exeName,\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 { join, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { resolveDataRoot } from \"./data-root.js\";\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/**\n * 默认 Windows node 版本(latest v24,LTS 线;可被环境变量/参数覆盖)。\n * ⚠️ nodejs.org/dist 保留历史版本不会 404,但 pin 死旧 patch 收不到安全更新——\n * 升级 Windows node 时记得同步 bump 此常量(或经 NAPUTO_WIN_NODE_VERSION 覆盖)。\n */\nconst DEFAULT_WIN_NODE_VERSION = \"v24.16.0\";\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/** wine 安装指引(缺失时提示用户如何准备,纯字符串可单测)。 */\nexport function wineInstallHint(): string {\n return (\n \"Linux/WSL 自建宿主需要 wine 运行 Windows 版 node.exe(wrapper.node 是\" +\n \"Windows PE 原生模块,只能被 Windows node.exe dlopen)。\\n\" +\n \" 安装:sudo apt-get install wine64(或其他发行版对应 wine 包)\\n\" +\n \" 覆盖:设置环境变量 NAPUTO_WINE 指向 wine 可执行文件路径\"\n );\n}\n\n/**\n * wine 可执行性探测结果 → 可读错误(null = 可用)。纯函数,可单测。\n * 由 launcher 在 spawn 前用 spawnSync 探测 wine(缺失时 throw 可读错误,\n * 避免 spawn 异步 emit 'error' 无监听者导致宿主进程崩溃,2026-08-23 WSL 实测)。\n */\nexport function wineCheckError(probe: {\n /** spawnSync 退出码(null = spawn 本身失败,如命令不存在)。 */\n status: number | null;\n /** spawn 错误(ENOENT 等;可选)。 */\n error?: unknown;\n}): string | null {\n if (probe.status === 0) {\n return null;\n }\n const err = probe.error as NodeJS.ErrnoException | undefined;\n if (err !== undefined && err.code === \"ENOENT\") {\n return `未检测到 wine 可执行文件。${wineInstallHint()}`;\n }\n if (err !== undefined) {\n return `wine 探测失败: ${err.message ?? String(err)}。${wineInstallHint()}`;\n }\n return `wine --version 退出码非 0(status=${String(probe.status)}),wine 可能异常。${wineInstallHint()}`;\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, spawnSync } 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 {\n buildSpawnCommand,\n isLinux,\n toWinePath,\n unixPathToWinePath,\n wineBinary,\n wineCheckError,\n wineInstallHint,\n} 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 * 阶段回调(stub 校验/win-node 下载/spawn 提示,2026-08-23 起——下载\n * 313MB 安装包与 win-node 全程静默的坑)。调用方接 logger.info。\n */\n onStage?: (message: string) => void;\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 options.onStage?.(`stub QQNT.dll 就绪:${stub}`);\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 options.onStage?.(`启动自建宿主子进程:${command} ${args.join(\" \")}`);\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 // ⚠️ spawn error 兜底(2026-08-23 WSL 生产事故):wine 未安装时 Node 异步\n // emit 'error'(ENOENT),无监听者 = EventEmitter 默认 throw →\n // uncaughtException → 整个宿主进程(koishi)崩溃。预检(assertWineAvailable)\n // 已覆盖常见场景,这里防漏网并输出可读信息,绝不让宿主进程崩掉。\n child.once(\"error\", (err) => {\n const code = (err as NodeJS.ErrnoException).code;\n const message =\n code === \"ENOENT\"\n ? `无法启动子进程(命令不存在): ${command}\\n${isLinux() ? wineInstallHint() : \"\"}`\n : `子进程启动失败: ${err.message}`;\n // 防崩溃兜底输出(driver 侧另有 error 监听转 onError 友好流程)\n process.stderr.write(`[napuketto-loader] ${message}\\n`);\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 // ⚠️ wine 预检(2026-08-23 WSL 生产事故):spawn 前确认 wine 可执行——\n // 干净 WSL 环境默认没有 wine,缺失时给出安装指引(throw 可读错误,由\n // 调用方 driver 捕获转 [W] 驱动错误),而不是 spawn 后异步 'error' 崩进程。\n const wine = wineBinary();\n const probe = spawnSync(wine, [\"--version\"], { stdio: \"ignore\" });\n const hint = wineCheckError(probe);\n if (hint !== null) {\n throw new Error(hint);\n }\n options.onStage?.(\"wine 就绪,获取 Windows 版 node.exe(Linux/wine 场景)…\");\n const winNode = await ensureWinNode({\n ...(options.cwd !== undefined ? { dataRoot: options.cwd } : {}),\n ...(options.winNodePath !== undefined ? { exePath: options.winNodePath } : {}),\n });\n options.onStage?.(`Windows 版 node.exe 就绪:${winNode.exePath}(${winNode.version})`);\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/** 环境变量名(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)。\n *\n * 维护机制(真实存在,非「声称」):清单由 CI 自动维护——\n * .github/workflows/update-qq-releases.yml(每日 cron + 手动触发)调用\n * scripts/qq-releases/update-qq-releases.ts(纯 Node,与本地共用),抓取 QQ 官方下载配置\n * 解析最新版 + 下载安装包计算 sha256 + 解析安装包内部版本目录后更新本清单\n * (保留历史条目、known 按版本递增)。社区 PR 仍可手动补充。NAPUTO_QQ_URL\n * 可运行时覆盖下载地址(用户拿到新链接时用)。\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 解析;0 = 尚未解析/待运行时补)。 */\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 { fileURLToPath } from \"node:url\";\nimport { resolveDataRoot } from \"./data-root.js\";\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/**\n * 7-Zip 官方 Linux 版版本号(tar.xz 文件名组成部分;兜底下载用,内置资产优先)。\n * ⚠️ 此值为最后兜底下载用,7-Zip 官网只留最新版,旧版号会 404;内置资产失败才触发。\n */\nconst SEVEN_ZIP_LINUX_VERSION = \"2501\";\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 ];\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 * 阶段回调(下载/校验/解包/提取各阶段提示,2026-08-23 WSL 事故后加:\n * 下载 313MB 全程静默,用户以为流程没生效)。调用方接 logger.info。\n */\n onStage?: (message: string) => void;\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 = resolveDataRoot(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({\n dataRoot,\n ...sevenZipPath,\n ...(options.onStage !== undefined ? { onStage: options.onStage } : {}),\n });\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 就绪(治本:内置资产优先,下载兜底)。\n *\n * 优先级:内置资产 assets/7zip/7zz(2026-08-14 治本——与 Windows 7z.exe 同模式,\n * 7zz 静态二进制已打进发布包,LGPL 合规可分发,零运行时下载)> 数据根缓存\n * runtime/7zip/7zz(历史下载产物复用)> 官网下载(兜底,NAPUTO_7Z_URL 可覆盖)。\n *\n * 背景:7-Zip 官网只保留最新版 tar.xz,硬编码旧版本号(如 2409)会被删档 404,\n * 运行时下载既脆弱又依赖外网。内置资产彻底消灭该依赖。\n */\nexport async function ensureLinuxSevenZip(\n options: { dataRoot?: string } = {},\n): Promise<{ exe: string; source: string }> {\n // 1. 内置资产(治本):dist 与 assets 同层(tsdown 构建后 dist/,资产在包根 assets/)\n const bundled = join(dirname(fileURLToPath(import.meta.url)), \"..\", \"assets\", \"7zip\", \"7zz\");\n if (existsSync(bundled)) {\n // npm 安装可能丢失执行位,保险设置(失败不阻塞:execFile 报 EACCES 时由调用方提示)\n await chmod(bundled, 0o755).catch(() => {\n // 忽略:无执行位场景后续解包会明确报错,此处不静默吞掉主流程\n });\n return { exe: bundled, source: \"内置资产 assets/7zip\" };\n }\n\n // 2. 数据根缓存(历史下载产物复用)\n const dataRoot = resolveDataRoot(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 // 3. 官网下载兜底(NAPUTO_7Z_URL 覆盖;仅当发布包缺失旧形态时触发)\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 *\n * onStage:阶段回调(下载/解包等提示,2026-08-23 起——下载 313MB 全程静默\n * 的坑)。调用方接 logger.info。\n */\nexport async function ensureQqFiles(\n options: {\n /** 数据根(缓存 <数据根>/qq-files/)。 */\n dataRoot?: string;\n /** 7z 路径(缺省自动探测)。 */\n sevenZipPath?: string;\n /** 阶段回调(下载/校验/解包/提取,接 logger.info)。 */\n onStage?: (message: string) => void;\n } = {},\n): Promise<QqInstallInfo> {\n const dataRoot = resolveDataRoot(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 options.onStage?.(`QQ 原生文件缓存命中:版本 ${existing.version}`);\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 // ⚠️ tmp 文件名唯一化(2026-08-23 WSL 事故):固定名 qq-installer-<v>.exe 在\n // 多实例/重试并发时互相覆盖,且一个实例 finally 清理会删掉另一个实例刚下载\n // 的文件——表现就是「下载成功但 7z 解包报 No such file or directory」。\n // 加 pid + 时间戳后缀,实例间天然隔离。\n const installerPath = join(\n tmpDir,\n `qq-installer-${version}-${process.pid}-${Date.now().toString(36)}.exe`,\n );\n options.onStage?.(`下载 QQ 官方安装包 ${version}(首次使用需下载,约 300MB)…`);\n await downloadFile({ dest: installerPath, url, expectedSha256: release.sha256 });\n options.onStage?.(`QQ 安装包下载完成,sha256 校验通过(${release.sha256.slice(0, 12)}…)`);\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}-${process.pid}`);\n try {\n // 解包前再次确认安装包真实存在(downloadFile 已 stat 兜底;防御并发清理竞态)\n if (!existsSync(installerPath)) {\n throw new Error(\n `下载产物在解包前丢失: ${installerPath}(疑为并发实例清理 tmp,` +\n \"已用唯一文件名规避,请重试)\",\n );\n }\n options.onStage?.(`7z 解包安装包(${version},需数分钟)…`);\n await extractInstaller(installerPath, extractedDir, sevenZipPath);\n options.onStage?.(\"提取 wrapper.node 及原生依赖 DLL…\");\n const info = await extractWrapperFiles(extractedDir, version, cacheRoot);\n options.onStage?.(`QQ 原生文件就绪:缓存版本 ${info.version}`);\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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@napuketto/loader",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "NapukettoQQ 引导组件:自建宿主(标准 Node + stub QQNT.dll)dlopen wrapper.node 并启动 kernel,不拉起 QQ / 不注入",
5
5
  "type": "module",
6
6
  "files": [