@napuketto/loader 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
2
3
  import { dirname, join, resolve } from "node:path";
3
4
  import process$1 from "node:process";
@@ -6,6 +7,171 @@ import { fileURLToPath } from "node:url";
6
7
  import { chmod, copyFile, mkdir, readdir, rm } from "node:fs/promises";
7
8
  import { createHash } from "node:crypto";
8
9
  import { get } from "node:https";
10
+ //#region src/host/ipc/ipc-types.ts
11
+ /**
12
+ * ipc-types.ts:IPC 协议消息(koishi 插件 ↔ self-host 子进程,单一来源)。
13
+ *
14
+ * 协议契约(koishi 插件 design.md §5.6):`{ v, type, id?, payload }` JSON 行。
15
+ * 用 zod v4 定义单一 schema,两侧(loader host + koishi 插件)import 同一份:
16
+ * 类型经 z.infer 派生、运行时经 safeParse 校验,消除「两份手写 types.ts」漂移。
17
+ *
18
+ * 领域字段(LoginState / SelfInfo / QrCodeData)与 kernel 同名类型结构等价,
19
+ * 在协议边界内联为 zod schema(kernel 不依赖 zod,故不在 kernel 定义)。
20
+ */
21
+ /** IPC 协议版本(解码校验)。 */
22
+ const IPC_VERSION = 1;
23
+ /** 引导阶段(status 消息)。 */
24
+ const IpcBootPhaseSchema = z.enum([
25
+ "booting",
26
+ "dlopening",
27
+ "logging",
28
+ "sessioning",
29
+ "ready",
30
+ "failed"
31
+ ]);
32
+ /** 结构化日志级别(pino level)。 */
33
+ const IpcLogLevelSchema = z.enum([
34
+ "trace",
35
+ "debug",
36
+ "info",
37
+ "warn",
38
+ "error",
39
+ "fatal"
40
+ ]);
41
+ /** 登录状态(与 kernel LoginState 结构等价)。 */
42
+ const LoginStateSchema = z.enum([
43
+ "idle",
44
+ "waiting_scan",
45
+ "scanned",
46
+ "logged_in",
47
+ "failed"
48
+ ]);
49
+ /** selfInfo(与 kernel SelfInfo 结构等价)。 */
50
+ const SelfInfoSchema = z.object({
51
+ uin: z.string(),
52
+ uid: z.string(),
53
+ nick: z.string()
54
+ });
55
+ /** status 消息 payload。 */
56
+ const IpcStatusPayloadSchema = z.object({
57
+ phase: IpcBootPhaseSchema,
58
+ message: z.string().optional(),
59
+ error: z.object({
60
+ code: z.string(),
61
+ message: z.string()
62
+ }).optional()
63
+ });
64
+ /** login 消息 payload。 */
65
+ const IpcLoginPayloadSchema = z.object({
66
+ state: LoginStateSchema,
67
+ selfInfo: SelfInfoSchema.optional(),
68
+ /** 失败原因(state=failed 时;如「登录超时,请刷新页面重试」)。 */
69
+ message: z.string().optional()
70
+ });
71
+ /** event 消息 payload(kernel 事件通道形状,翻译层按 service/name 断言具体类型)。 */
72
+ const IpcEventPayloadSchema = z.object({
73
+ service: z.string(),
74
+ name: z.string(),
75
+ args: z.array(z.unknown())
76
+ });
77
+ /** log 消息 payload。 */
78
+ const IpcLogPayloadSchema = z.object({
79
+ level: IpcLogLevelSchema,
80
+ message: z.string()
81
+ });
82
+ /** 动作响应 payload(result 消息)。 */
83
+ const IpcResultPayloadSchema = z.union([z.object({
84
+ ok: z.literal(true),
85
+ value: z.unknown().optional()
86
+ }), z.object({
87
+ ok: z.literal(false),
88
+ error: z.object({
89
+ code: z.string(),
90
+ message: z.string()
91
+ })
92
+ })]);
93
+ /** 动作请求 payload(action 消息)。 */
94
+ const IpcActionPayloadSchema = z.object({
95
+ action: z.string(),
96
+ params: z.record(z.string(), z.unknown()).optional()
97
+ });
98
+ /** 控制指令 payload(control 消息,父→子)。 */
99
+ const IpcControlPayloadSchema = z.union([
100
+ z.object({ command: z.literal("stop") }),
101
+ z.object({ command: z.literal("restart") }),
102
+ z.object({
103
+ command: z.literal("login"),
104
+ uin: z.string().optional(),
105
+ qr: z.boolean().optional()
106
+ })
107
+ ]);
108
+ const IpcStatusMessageSchema = z.object({
109
+ v: z.literal(1),
110
+ type: z.literal("status"),
111
+ payload: IpcStatusPayloadSchema
112
+ });
113
+ const IpcLoginMessageSchema = z.object({
114
+ v: z.literal(1),
115
+ type: z.literal("login"),
116
+ payload: IpcLoginPayloadSchema
117
+ });
118
+ const IpcQrMessageSchema = z.object({
119
+ v: z.literal(1),
120
+ type: z.literal("qr"),
121
+ payload: z.object({
122
+ pngBase64: z.string(),
123
+ qrcodeUrl: z.string()
124
+ })
125
+ });
126
+ const IpcEventMessageSchema = z.object({
127
+ v: z.literal(1),
128
+ type: z.literal("event"),
129
+ payload: IpcEventPayloadSchema
130
+ });
131
+ const IpcResultMessageSchema = z.object({
132
+ v: z.literal(1),
133
+ type: z.literal("result"),
134
+ id: z.number(),
135
+ payload: IpcResultPayloadSchema
136
+ });
137
+ const IpcLogMessageSchema = z.object({
138
+ v: z.literal(1),
139
+ type: z.literal("log"),
140
+ payload: IpcLogPayloadSchema
141
+ });
142
+ const IpcPingMessageSchema = z.object({
143
+ v: z.literal(1),
144
+ type: z.literal("ping")
145
+ });
146
+ const IpcPongMessageSchema = z.object({
147
+ v: z.literal(1),
148
+ type: z.literal("pong")
149
+ });
150
+ const IpcActionMessageSchema = z.object({
151
+ v: z.literal(1),
152
+ type: z.literal("action"),
153
+ id: z.number(),
154
+ payload: IpcActionPayloadSchema
155
+ });
156
+ const IpcControlMessageSchema = z.object({
157
+ v: z.literal(1),
158
+ type: z.literal("control"),
159
+ payload: IpcControlPayloadSchema
160
+ });
161
+ /** IPC 消息判别联合(协议边界,运行时经 safeParse 校验)。 */
162
+ const IpcMessageSchema = z.discriminatedUnion("type", [
163
+ IpcStatusMessageSchema,
164
+ IpcLoginMessageSchema,
165
+ IpcQrMessageSchema,
166
+ IpcEventMessageSchema,
167
+ IpcResultMessageSchema,
168
+ IpcLogMessageSchema,
169
+ IpcPingMessageSchema,
170
+ IpcPongMessageSchema,
171
+ IpcActionMessageSchema,
172
+ IpcControlMessageSchema
173
+ ]);
174
+ //#endregion
9
175
  //#region src/instance-lock.ts
10
176
  /**
11
177
  * instance-lock.ts:单实例锁(2026-08-07 根治「多实例抢数据目录锁挂起」)
@@ -110,6 +276,34 @@ function registerLockCleanup(dataDir) {
110
276
  });
111
277
  }
112
278
  //#endregion
279
+ //#region src/data-root.ts
280
+ /**
281
+ * data-root.ts:轻量数据根解析(loader 侧,避免新增 kernel 依赖)。
282
+ *
283
+ * 从 locate-qq.ts / win-node.ts 抽出的同构逻辑(2026-08 消重:两处
284
+ * 22 行重复 resolveDataRoot + findProjectRoot)。
285
+ *
286
+ * 优先级:显式参数 > NAPKETTO_DATA > 项目根/.napuketto > cwd 兜底。
287
+ * P2 若需完整语义(同 kernel resolveDataRoot)再切换。
288
+ */
289
+ /** 解析数据根(NAPKETTO_DATA 显式 > 项目根/.napuketto > cwd 兜底)。 */
290
+ function resolveDataRoot(dataRoot) {
291
+ const explicit = dataRoot ?? process$1.env["NAPKETTO_DATA"];
292
+ if (explicit !== void 0 && explicit !== "") return resolve(explicit);
293
+ const projectRoot = findProjectRoot(process$1.cwd()) ?? process$1.cwd();
294
+ return join(projectRoot, ".napuketto");
295
+ }
296
+ /** 向上探测项目根(含 pnpm-workspace.yaml 或 package.json 的目录)。 */
297
+ function findProjectRoot(start) {
298
+ let dir = resolve(start);
299
+ for (;;) {
300
+ if (existsSync(join(dir, "pnpm-workspace.yaml")) || existsSync(join(dir, "package.json"))) return dir;
301
+ const parent = dirname(dir);
302
+ if (parent === dir) return null;
303
+ dir = parent;
304
+ }
305
+ }
306
+ //#endregion
113
307
  //#region src/qq-download.ts
114
308
  /**
115
309
  * qq-download.ts:QQ 官方安装包下载器(P1)。
@@ -233,8 +427,8 @@ async function downloadFile(options) {
233
427
  const EXTRACT_EXTENSIONS = /* @__PURE__ */ new Set([".node", ".dll"]);
234
428
  /**
235
429
  * 探测 7z 可执行文件:
236
- * NAPUTO_7Z_PATH 环境变量 > 内置资产(assets/7zip/7z.exe,Windows)> 系统 PATH 7z。
237
- * Linux 上用 p7zip 提供的 7z 命令(系统 PATH)。
430
+ * NAPUTO_7Z_PATH 环境变量 > 内置资产(Windows assets/7zip/7z.exe、Linux
431
+ * assets/7zip/7zz——2026-08-14 治本,静态二进制已打进发布包)> 系统 PATH 7z。
238
432
  */
239
433
  function findSevenZip() {
240
434
  const envPath = process$1.env["NAPUTO_7Z_PATH"];
@@ -242,8 +436,9 @@ function findSevenZip() {
242
436
  exe: envPath,
243
437
  source: "环境变量 NAPUTO_7Z_PATH"
244
438
  };
245
- if (process$1.platform === "win32") {
246
- const bundled = join(dirname(fileURLToPath(import.meta.url)), "..", "assets", "7zip", "7z.exe");
439
+ if (process$1.platform === "win32" || process$1.platform === "linux") {
440
+ const exeName = process$1.platform === "win32" ? "7z.exe" : "7zz";
441
+ const bundled = join(dirname(fileURLToPath(import.meta.url)), "..", "assets", "7zip", exeName);
247
442
  if (existsSync(bundled)) return {
248
443
  exe: bundled,
249
444
  source: "内置资产 assets/7zip"
@@ -386,23 +581,6 @@ async function unzipNode(zipPath, cacheDir) {
386
581
  }
387
582
  /** 默认 Windows node 版本(latest v24,LTS 线;可被环境变量/参数覆盖)。 */
388
583
  const DEFAULT_WIN_NODE_VERSION = "v24.16.0";
389
- /** 轻量数据根解析(与 locate-qq 内同构,避免循环依赖)。 */
390
- function resolveDataRoot(dataRoot) {
391
- const explicit = dataRoot ?? process$1.env["NAPKETTO_DATA"];
392
- if (explicit !== void 0 && explicit !== "") return resolve(explicit);
393
- const projectRoot = findProjectRoot$1(process$1.cwd()) ?? process$1.cwd();
394
- return join(projectRoot, ".napuketto");
395
- }
396
- /** 向上探测项目根(含 pnpm-workspace.yaml 或 package.json 的目录)。 */
397
- function findProjectRoot$1(start) {
398
- let dir = resolve(start);
399
- for (;;) {
400
- if (existsSync(join(dir, "pnpm-workspace.yaml")) || existsSync(join(dir, "package.json"))) return dir;
401
- const parent = dirname(dir);
402
- if (parent === dir) return null;
403
- dir = parent;
404
- }
405
- }
406
584
  //#endregion
407
585
  //#region src/wine.ts
408
586
  /**
@@ -656,8 +834,8 @@ function resolveDownloadUrl(release) {
656
834
  */
657
835
  /** 数据根下 QQ 文件缓存目录名(<数据根>/qq-files/<版本>/,P1 下载解包产物)。 */
658
836
  const QQ_FILES_DIR_NAME = "qq-files";
659
- /** 7-Zip 官方 Linux 版版本号(tar.xz 文件名组成部分,2026-08-14 起自动下载)。 */
660
- const SEVEN_ZIP_LINUX_VERSION = "2409";
837
+ /** 7-Zip 官方 Linux 版版本号(tar.xz 文件名组成部分;兜底下载用,内置资产优先)。 */
838
+ const SEVEN_ZIP_LINUX_VERSION = "2501";
661
839
  /** 注册表 UninstallString 查询(QQ 官方安装路径)。 */
662
840
  const REG_QUERY_RE = /"([^"]+)"/;
663
841
  /** Windows 盘符路径正则(`C:/...` / `C:\...`,wslMappedPath 映射用,模块级常量)。 */
@@ -751,7 +929,7 @@ async function resolveQqFiles(options = {}) {
751
929
  try {
752
930
  return resolveQqInstall();
753
931
  } catch {}
754
- const dataRoot = resolveDataRootLight(options.dataRoot);
932
+ const dataRoot = resolveDataRoot(options.dataRoot);
755
933
  const cached = tryResolveCached(dataRoot);
756
934
  if (cached !== null) return cached;
757
935
  if (options.autoDownload !== false) return ensureQqFiles({
@@ -778,17 +956,25 @@ function linuxSevenZipUrl() {
778
956
  return process$1.env["NAPUTO_7Z_URL"] ?? `https://www.7-zip.org/a/7z${SEVEN_ZIP_LINUX_VERSION}-linux-x64.tar.xz`;
779
957
  }
780
958
  /**
781
- * 确保 Linux 7zz 就绪(2026-08-14 生产修复):自动下载 7-Zip 官方 Linux 版。
959
+ * 确保 Linux 7zz 就绪(治本:内置资产优先,下载兜底)。
782
960
  *
783
- * 背景:内置 assets/7zip 的 7z.exe 是 Windows PE(Linux 不可用),此前 Linux 依赖
784
- * 系统 p7zip-full——生产环境未安装 → `7z 解包失败: No such file or directory`。
785
- * 7zz 是 7-Zip 完整版(支持 NSIS 解包,同 7z.exe 能力),官方静态二进制,
786
- * 免 root 免系统包,下载到 <数据根>/runtime/7zip/7zz(与 win-node 同模式)。
961
+ * 优先级:内置资产 assets/7zip/7zz(2026-08-14 治本——与 Windows 7z.exe 同模式,
962
+ * 7zz 静态二进制已打进发布包,LGPL 合规可分发,零运行时下载)> 数据根缓存
963
+ * runtime/7zip/7zz(历史下载产物复用)> 官网下载(兜底,NAPUTO_7Z_URL 可覆盖)。
787
964
  *
788
- * 幂等:缓存已存在直接返回;tar -xJf 解压(Linux 自带 tar;需 xz-utils)。
965
+ * 背景:7-Zip 官网只保留最新版 tar.xz,硬编码旧版本号(如 2409)会被删档 404,
966
+ * 运行时下载既脆弱又依赖外网。内置资产彻底消灭该依赖。
789
967
  */
790
968
  async function ensureLinuxSevenZip(options = {}) {
791
- const dataRoot = resolveDataRootLight(options.dataRoot);
969
+ const bundled = join(dirname(fileURLToPath(import.meta.url)), "..", "assets", "7zip", "7zz");
970
+ if (existsSync(bundled)) {
971
+ await chmod(bundled, 493).catch(() => {});
972
+ return {
973
+ exe: bundled,
974
+ source: "内置资产 assets/7zip"
975
+ };
976
+ }
977
+ const dataRoot = resolveDataRoot(options.dataRoot);
792
978
  const cacheDir = join(dataRoot, "runtime", "7zip");
793
979
  const exe = join(cacheDir, "7zz");
794
980
  if (existsSync(exe)) return {
@@ -828,7 +1014,7 @@ async function ensureLinuxSevenZip(options = {}) {
828
1014
  * → 7z 解包 → 提取 wrapper.node/QQNT.dll → 缓存。返回缓存版本 QqInstallInfo。
829
1015
  */
830
1016
  async function ensureQqFiles(options = {}) {
831
- const dataRoot = resolveDataRootLight(options.dataRoot);
1017
+ const dataRoot = resolveDataRoot(options.dataRoot);
832
1018
  const cacheRoot = join(dataRoot, QQ_FILES_DIR_NAME);
833
1019
  mkdirSync(cacheRoot, { recursive: true });
834
1020
  const existing = tryResolveCached(dataRoot);
@@ -861,27 +1047,7 @@ async function ensureQqFiles(options = {}) {
861
1047
  });
862
1048
  }
863
1049
  }
864
- /**
865
- * 轻量数据根解析(loader 侧,避免新增 kernel 依赖;P2 若需完整语义再切换)。
866
- * 优先级:显式参数 > NAPKETTO_DATA > 项目根/.napuketto > cwd 兜底。
867
- */
868
- function resolveDataRootLight(dataRoot) {
869
- const explicit = dataRoot ?? process$1.env["NAPKETTO_DATA"];
870
- if (explicit !== void 0 && explicit !== "") return resolve(explicit);
871
- const projectRoot = findProjectRoot(process$1.cwd()) ?? process$1.cwd();
872
- return join(projectRoot, ".napuketto");
873
- }
874
- /** 向上探测项目根(含 pnpm-workspace.yaml 或 package.json 的目录)。 */
875
- function findProjectRoot(start) {
876
- let dir = resolve(start);
877
- for (;;) {
878
- if (existsSync(join(dir, "pnpm-workspace.yaml")) || existsSync(join(dir, "package.json"))) return dir;
879
- const parent = dirname(dir);
880
- if (parent === dir) return null;
881
- dir = parent;
882
- }
883
- }
884
1050
  //#endregion
885
- export { DownloadError, ENV, INSTANCE_LOCK_FILE, QQ_FILES_DIR_NAME, QqReleasesError, WIN_NODE_DIR_NAME, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureLinuxSevenZip, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, latestRelease, launchSelfHost, linuxSevenZipUrl, loadQqReleases, locateQqPath, nodeZipUrl, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
1051
+ export { DownloadError, ENV, INSTANCE_LOCK_FILE, IPC_VERSION, IpcActionPayloadSchema, IpcBootPhaseSchema, IpcControlPayloadSchema, IpcEventPayloadSchema, IpcLogLevelSchema, IpcLogPayloadSchema, IpcLoginPayloadSchema, IpcMessageSchema, IpcResultPayloadSchema, IpcStatusPayloadSchema, QQ_FILES_DIR_NAME, QqReleasesError, WIN_NODE_DIR_NAME, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureLinuxSevenZip, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, latestRelease, launchSelfHost, linuxSevenZipUrl, loadQqReleases, locateQqPath, nodeZipUrl, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
886
1052
 
887
1053
  //# sourceMappingURL=index.mjs.map