@sfmc-bds/bds-tools 0.2.0-beta.0 → 0.2.0-beta.10

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.
Files changed (108) hide show
  1. package/LICENSE +663 -0
  2. package/dist/bds-manager.d.ts +16 -6
  3. package/dist/bds-manager.d.ts.map +1 -1
  4. package/dist/bds-manager.js +40 -65
  5. package/dist/bds-manager.js.map +2 -2
  6. package/dist/check-update.d.ts.map +1 -1
  7. package/dist/check-update.js +15 -17
  8. package/dist/check-update.js.map +2 -2
  9. package/dist/cli-pack-manager.js +7 -3
  10. package/dist/cli-pack-manager.js.map +2 -2
  11. package/dist/fsx.d.ts +6 -1
  12. package/dist/fsx.d.ts.map +1 -1
  13. package/dist/fsx.js +9 -1
  14. package/dist/fsx.js.map +2 -2
  15. package/dist/host-platform.d.ts +25 -0
  16. package/dist/host-platform.d.ts.map +1 -0
  17. package/dist/host-platform.js +50 -0
  18. package/dist/host-platform.js.map +7 -0
  19. package/dist/http.d.ts +49 -11
  20. package/dist/http.d.ts.map +1 -1
  21. package/dist/http.js.map +2 -2
  22. package/dist/log.d.ts +2 -3
  23. package/dist/log.d.ts.map +1 -1
  24. package/dist/log.js +0 -1
  25. package/dist/log.js.map +2 -2
  26. package/dist/pack-manager-lifecycle.test.js +252 -0
  27. package/dist/pack-manager-lifecycle.test.js.map +7 -0
  28. package/dist/pack-manager.d.ts +24 -29
  29. package/dist/pack-manager.d.ts.map +1 -1
  30. package/dist/pack-manager.js +11 -4
  31. package/dist/pack-manager.js.map +2 -2
  32. package/dist/pack-update/bindings.d.ts +16 -0
  33. package/dist/pack-update/bindings.d.ts.map +1 -0
  34. package/dist/pack-update/bindings.js +69 -0
  35. package/dist/pack-update/bindings.js.map +7 -0
  36. package/dist/pack-update/config.d.ts +16 -0
  37. package/dist/pack-update/config.d.ts.map +1 -0
  38. package/dist/pack-update/config.js +146 -0
  39. package/dist/pack-update/config.js.map +7 -0
  40. package/dist/pack-update/config.test.js +75 -0
  41. package/dist/pack-update/config.test.js.map +7 -0
  42. package/dist/pack-update/deps.d.ts +28 -0
  43. package/dist/pack-update/deps.d.ts.map +1 -0
  44. package/dist/pack-update/deps.js +20 -0
  45. package/dist/pack-update/deps.js.map +7 -0
  46. package/dist/pack-update/index.d.ts +55 -0
  47. package/dist/pack-update/index.d.ts.map +1 -0
  48. package/dist/pack-update/index.js +72 -0
  49. package/dist/pack-update/index.js.map +7 -0
  50. package/dist/pack-update/providers/curseforge.d.ts +23 -0
  51. package/dist/pack-update/providers/curseforge.d.ts.map +1 -0
  52. package/dist/pack-update/providers/curseforge.js +229 -0
  53. package/dist/pack-update/providers/curseforge.js.map +7 -0
  54. package/dist/pack-update/providers/index.d.ts +16 -0
  55. package/dist/pack-update/providers/index.d.ts.map +1 -0
  56. package/dist/pack-update/providers/index.js +37 -0
  57. package/dist/pack-update/providers/index.js.map +7 -0
  58. package/dist/pack-update/service.d.ts +28 -0
  59. package/dist/pack-update/service.d.ts.map +1 -0
  60. package/dist/pack-update/service.js +570 -0
  61. package/dist/pack-update/service.js.map +7 -0
  62. package/dist/pack-update/types.d.ts +126 -0
  63. package/dist/pack-update/types.d.ts.map +1 -0
  64. package/dist/pack-update/types.js +8 -0
  65. package/dist/pack-update/types.js.map +7 -0
  66. package/dist/pack-update/version-policy.d.ts +81 -0
  67. package/dist/pack-update/version-policy.d.ts.map +1 -0
  68. package/dist/pack-update/version-policy.js +210 -0
  69. package/dist/pack-update/version-policy.js.map +7 -0
  70. package/dist/pack-update/version-policy.test.js +137 -0
  71. package/dist/pack-update/version-policy.test.js.map +7 -0
  72. package/dist/paths.d.ts +1 -1
  73. package/dist/paths.d.ts.map +1 -1
  74. package/dist/paths.js +3 -5
  75. package/dist/paths.js.map +2 -2
  76. package/dist/process-probe.d.ts +34 -0
  77. package/dist/process-probe.d.ts.map +1 -0
  78. package/dist/process-probe.js +152 -0
  79. package/dist/process-probe.js.map +7 -0
  80. package/dist/process-probe.test.js +76 -0
  81. package/dist/process-probe.test.js.map +7 -0
  82. package/dist/qq-events-notify.d.ts +16 -0
  83. package/dist/qq-events-notify.d.ts.map +1 -0
  84. package/dist/qq-events-notify.js +70 -0
  85. package/dist/qq-events-notify.js.map +7 -0
  86. package/dist/qqutil.d.ts +10 -5
  87. package/dist/qqutil.d.ts.map +1 -1
  88. package/dist/qqutil.js +49 -8
  89. package/dist/qqutil.js.map +2 -2
  90. package/dist/update-result.d.ts +8 -2
  91. package/dist/update-result.d.ts.map +1 -1
  92. package/dist/update-result.js +7 -5
  93. package/dist/update-result.js.map +2 -2
  94. package/dist/upstream.d.ts +18 -9
  95. package/dist/upstream.d.ts.map +1 -1
  96. package/dist/upstream.js +8 -7
  97. package/dist/upstream.js.map +2 -2
  98. package/dist/version.js +2 -2
  99. package/dist/version.js.map +2 -2
  100. package/dist/world-packs.d.ts +158 -5
  101. package/dist/world-packs.d.ts.map +1 -1
  102. package/dist/world-packs.js +294 -48
  103. package/dist/world-packs.js.map +2 -2
  104. package/dist/world-packs.test.js +676 -0
  105. package/dist/world-packs.test.js.map +7 -0
  106. package/dist/zipx.test.js +36 -0
  107. package/dist/zipx.test.js.map +7 -0
  108. package/package.json +43 -39
@@ -1,3 +1,12 @@
1
+ /**
2
+ * world-packs.ts — 通用 Minecraft 基岩版世界行为包(BP)与资源包(RP)管理工具
3
+ *
4
+ * 核心能力:
5
+ * - 格式与命名规范化:过滤 Minecraft 颜色格式码,自动补充 `[BP]` / `[RP]` 前缀及冲突避让
6
+ * - 压缩包递归解构:支持 `.zip`、`.mcpack`、`.mcaddon` 嵌套包解压并智能识别 BP 与 RP 根目录
7
+ * - 版本递增与同步:同步升级 manifest 中的版本元数据并对齐世界启用清单版本
8
+ * - 幂等安装与回滚:按 UUID 精确匹配升级与安装计划,支持删除备份至回收站机制
9
+ */
1
10
  import { type WorldPackListReadResult } from "./pack-manager.js";
2
11
  export type WorldPackKind = "behavior" | "resource";
3
12
  export interface PackManifestInfo {
@@ -18,6 +27,34 @@ export interface InstalledWorldPack {
18
27
  }
19
28
  /** 滤掉 §x(Minecraft 格式码)、残余压缩后缀,加 [BP]/[RP] 前缀 */
20
29
  export declare function formatWorldPackFolderName(rawName: string, kind: WorldPackKind): string;
30
+ /** 去掉已有 [BP]/[RP] 前缀,得到文件夹名主干 */
31
+ export declare function stripWorldPackFolderPrefix(rawName: string): string;
32
+ /**
33
+ * 归档内常见占位目录名(B / R / BP / RP)过短或过泛,不能作为世界目录名,
34
+ * 否则不同包会撞到同一路径并被误判为「覆盖」。
35
+ */
36
+ export declare function isGenericPackFolderStem(rawName: string): boolean;
37
+ /**
38
+ * 选定安装文件夹名:hint 过泛时改用 manifest.name;再与已占用名避让(不同 uuid 不互相覆盖)。
39
+ * 纯函数:只依赖 taken 集合,便于表驱动单测(DRY:decide / choose 共用)。
40
+ */
41
+ export declare function allocatePackFolderName(opts: {
42
+ hint?: string;
43
+ info: PackManifestInfo;
44
+ takenFolderNames: Iterable<string>;
45
+ /** 同 uuid 更新时允许占用的已有目录名(不触发避让) */
46
+ allowFolderName?: string;
47
+ }): string;
48
+ /**
49
+ * 选定安装文件夹名(读 destParent 占用)。
50
+ * 决策权威见 allocatePackFolderName / decidePackInstallPlan。
51
+ */
52
+ export declare function choosePackInstallFolderName(opts: {
53
+ hint?: string;
54
+ info: PackManifestInfo;
55
+ destParent: string;
56
+ allowFolderName?: string;
57
+ }): string;
21
58
  /** 根据 manifest modules[].type 判定 BP / RP */
22
59
  export declare function detectPackKindFromManifest(raw: {
23
60
  modules?: Array<{
@@ -30,18 +67,72 @@ export declare function discoverPackRoots(root: string, opts?: {
30
67
  maxDepth?: number;
31
68
  }): string[];
32
69
  export declare function isPackArchive(filePath: string): boolean;
70
+ /**
71
+ * 列出目录树内的包归档文件。
72
+ * 已是包根(含 manifest.json)的目录不再下钻,避免误解压包内资源。
73
+ */
74
+ export declare function listPackArchiveFiles(root: string, opts?: {
75
+ maxDepth?: number;
76
+ }): string[];
77
+ /**
78
+ * 在临时树内原地展开嵌套归档:解压到同级目录并删除归档文件,多轮直至没有新归档。
79
+ * 仅应对我们自己的 staging,禁止用于用户 inbox 原目录。
80
+ */
81
+ export declare function expandNestedArchivesInPlace(workDir: string, opts?: {
82
+ maxRounds?: number;
83
+ maxScanDepth?: number;
84
+ }): Promise<number>;
85
+ export type ResolvePackRootsResult = {
86
+ roots: string[];
87
+ /** 清理本轮创建的全部临时目录(可安全多次调用) */
88
+ dispose: () => void;
89
+ };
90
+ /**
91
+ * 统一包根解析:目录 / 顶层归档 / 嵌套 .mcpack|.zip|.mcaddon。
92
+ * 规则:先展开归档为目录树,再只认含 manifest.json 的目录为包根。
93
+ * 用户目录内的嵌套归档解到独立 temp,不修改原目录。
94
+ */
95
+ export declare function resolvePackRoots(src: string, opts?: {
96
+ maxDirDepth?: number;
97
+ maxArchiveRounds?: number;
98
+ }): Promise<ResolvePackRootsResult>;
33
99
  /** 解压 zip/mcpack/mcaddon 到临时目录,返回该目录(经 zipx 防 zip-slip) */
34
100
  export declare function extractArchiveToTemp(filePath: string): Promise<string>;
35
101
  /** 扫描世界内已安装 BP/RP 目录 */
36
102
  export declare function listInstalledWorldPacks(bdsRoot: string, levelName: string): InstalledWorldPack[];
37
- /** RP/BP 第三位版本 +1,写回 manifest */
103
+ /**
104
+ * 将目标包 manifest.json 中的 Patch 版本号递增(+1)并持久化写回。
105
+ *
106
+ * @param packDir 包根目录路径。
107
+ * @returns 递增后的版本三元组 `[major, minor, patch]`。
108
+ */
38
109
  export declare function bumpPackPatchVersion(packDir: string): [number, number, number];
39
- /** 写入 header.version(并同步 modules[].version 的 patch) */
110
+ /**
111
+ * 向目标包的 manifest.json 写入新的版本号,并同步更新内部 modules 的 patch 版本。
112
+ *
113
+ * @param packDir 包根目录路径。
114
+ * @param next 待写入的版本三元组。
115
+ * @returns 写入确认的版本三元组。
116
+ */
40
117
  export declare function writePackHeaderVersion(packDir: string, next: [number, number, number]): [number, number, number];
41
118
  /** 读取 BP manifest 中依赖的资源包 uuid(非 @minecraft 模块) */
42
119
  export declare function readPackDependencyUuids(packDir: string): string[];
43
- /** 确保版本严格大于 floor(patch/minor 抬一级) */
44
- export declare function ensureVersionGreaterThan(packDir: string, floor: [number, number, number], component?: "patch" | "minor"): [number, number, number];
120
+ /** 比较 SemVer 三元组;a>b 正,a<b 负,相等 0 */
121
+ export declare function compareSemVer3(a: [number, number, number], b: [number, number, number]): number;
122
+ /** 取较大的 SemVer 三元组 */
123
+ export declare function maxSemVer3(a: [number, number, number], b: [number, number, number]): [number, number, number];
124
+ /** 按 component 抬一级 */
125
+ export declare function bumpSemVer3(v: [number, number, number], component: "patch" | "minor"): [number, number, number];
126
+ /**
127
+ * 进服启用版本:max(远程/新包, 旧启用) 再抬一级。
128
+ * 单一规则,禁止 while 追赶(作者乱写高版本如 [1,21,100] 时也能一次算完)。
129
+ */
130
+ export declare function nextEnabledVersion(remote: [number, number, number], previous: [number, number, number], component?: "patch" | "minor"): [number, number, number];
131
+ /**
132
+ * 将包目录 manifest 抬到 nextEnabledVersion(当前, previous)。
133
+ * previous 一般为更新前已安装 RP / 世界 enable 列表中的版本。
134
+ */
135
+ export declare function ensureVersionGreaterThan(packDir: string, previous: [number, number, number], component?: "patch" | "minor"): [number, number, number];
45
136
  export interface InstallPackResult {
46
137
  ok: boolean;
47
138
  skipped?: boolean;
@@ -56,9 +147,71 @@ export interface InstallPackResult {
56
147
  incoming: PackManifestInfo;
57
148
  };
58
149
  }
150
+ /** 目标父目录内已占用项(供纯决策,不含 FS 副作用) */
151
+ export type DestOccupancy = {
152
+ folderName: string;
153
+ dir: string;
154
+ uuid: string | null;
155
+ version: [number, number, number] | null;
156
+ /** 可读时的展示名;决策不依赖,仅供 conflict UI */
157
+ name?: string;
158
+ /** 可读时的包类型;决策展示优先用真实 kind,避免伪造 incoming.kind(LSP) */
159
+ kind?: WorldPackKind;
160
+ };
161
+ export type PackInstallPlan = {
162
+ kind: "fresh";
163
+ folderName: string;
164
+ incoming: PackManifestInfo;
165
+ } | {
166
+ kind: "overwriteInPlace";
167
+ folderName: string;
168
+ dir: string;
169
+ incoming: PackManifestInfo;
170
+ existing: PackManifestInfo & {
171
+ dir: string;
172
+ };
173
+ } | {
174
+ kind: "needConfirm";
175
+ folderName: string;
176
+ dir: string;
177
+ incoming: PackManifestInfo;
178
+ existing: PackManifestInfo & {
179
+ dir: string;
180
+ };
181
+ };
182
+ /**
183
+ * 读单目录占用事实(info 优先,否则 header)。
184
+ * 解析失败返回 null;不抛 BOM(readJsonFile 已剥离)。
185
+ */
186
+ export declare function readPackDirOccupancy(dir: string): {
187
+ uuid: string;
188
+ version: [number, number, number];
189
+ name?: string;
190
+ kind?: WorldPackKind;
191
+ } | null;
192
+ /** 扫描 destParent 下含 manifest 的目录占用(单一事实源:readPackDirOccupancy) */
193
+ export declare function scanDestOccupancy(destParent: string): DestOccupancy[];
194
+ /**
195
+ * 纯决策:装到哪、是否需确认、是否原地覆盖。
196
+ * 同 uuid:incoming 版本严格更大 → 静默覆盖;≤ 已有且 !force → needConfirm;force → 原地覆盖。
197
+ */
198
+ export declare function decidePackInstallPlan(opts: {
199
+ incoming: PackManifestInfo;
200
+ hint?: string;
201
+ occupancy: readonly DestOccupancy[];
202
+ force?: boolean;
203
+ }): PackInstallPlan;
204
+ /** 落盘后校验:防止旁路安装 / 拷贝失败仍报成功 */
205
+ export declare function verifyInstalledPack(destDir: string, incoming: PackManifestInfo): {
206
+ ok: true;
207
+ info: PackManifestInfo;
208
+ } | {
209
+ ok: false;
210
+ reason: string;
211
+ };
59
212
  /**
60
213
  * 将包目录安装到 destParent(behavior_packs 或 resource_packs)。
61
- * force=true 时覆盖冲突;否则返回 conflict 供上层询问。
214
+ * 编排:scan decide → apply → verify。同 uuid 版本更高时静默原地覆盖。
62
215
  */
63
216
  export declare function installPackDirectory(opts: {
64
217
  srcDir: string;
@@ -1 +1 @@
1
- {"version":3,"file":"world-packs.d.ts","sourceRoot":"","sources":["../src/world-packs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"world-packs.d.ts","sourceRoot":"","sources":["../src/world-packs.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import { copyDirAsync } from "./fsx.js";
4
+ import { copyDirAsync, readJsonFile } from "./fsx.js";
5
5
  import {
6
6
  bdsWorldLevelDir,
7
7
  bdsWorldsDir,
@@ -23,6 +23,41 @@ function formatWorldPackFolderName(rawName, kind) {
23
23
  if (s.startsWith("[RP]") || s.startsWith("[BP]")) return s;
24
24
  return `${prefix} ${s}`;
25
25
  }
26
+ function stripWorldPackFolderPrefix(rawName) {
27
+ return String(rawName ?? "").replace(/^\[BP\]\s*/i, "").replace(/^\[RP\]\s*/i, "").replace(/\.(zip|mcpack|mcaddon)$/i, "").replace(/\s+/g, " ").trim();
28
+ }
29
+ function isGenericPackFolderStem(rawName) {
30
+ const s = stripWorldPackFolderPrefix(rawName);
31
+ if (!s) return true;
32
+ if (s.length <= 2) return true;
33
+ return /^(bp|rp|b|r|pack|packs|behavior|behaviours?|resource|resources)$/i.test(s);
34
+ }
35
+ function allocatePackFolderName(opts) {
36
+ const hint = opts.hint && opts.hint.trim() || "";
37
+ const preferred = isGenericPackFolderStem(hint) ? opts.info.name || hint || "pack" : hint;
38
+ let folderName = formatWorldPackFolderName(preferred, opts.info.kind);
39
+ const allow = opts.allowFolderName ? path.basename(opts.allowFolderName) : null;
40
+ if (allow && folderName === allow) return folderName;
41
+ const taken = new Set([...opts.takenFolderNames].filter((n) => n !== allow));
42
+ if (!taken.has(folderName)) return folderName;
43
+ const withUuid = formatWorldPackFolderName(
44
+ `${stripWorldPackFolderPrefix(preferred)} ${opts.info.uuid.slice(0, 8)}`,
45
+ opts.info.kind
46
+ );
47
+ if (!taken.has(withUuid)) return withUuid;
48
+ let i = 2;
49
+ while (taken.has(`${withUuid}_${i}`)) i++;
50
+ return `${withUuid}_${i}`;
51
+ }
52
+ function choosePackInstallFolderName(opts) {
53
+ const taken = listPackDirsIn(opts.destParent).map((d) => path.basename(d));
54
+ return allocatePackFolderName({
55
+ info: opts.info,
56
+ takenFolderNames: taken,
57
+ ...opts.hint !== void 0 ? { hint: opts.hint } : {},
58
+ ...opts.allowFolderName !== void 0 ? { allowFolderName: opts.allowFolderName } : {}
59
+ });
60
+ }
26
61
  function detectPackKindFromManifest(raw) {
27
62
  const types = (raw.modules ?? []).map((m) => String(m.type ?? "").toLowerCase());
28
63
  if (types.includes("resources")) return "resource";
@@ -33,7 +68,7 @@ function readPackManifestInfo(packDir) {
33
68
  const file = path.join(packDir, "manifest.json");
34
69
  if (!fs.existsSync(file)) return null;
35
70
  try {
36
- const raw = JSON.parse(fs.readFileSync(file, "utf8"));
71
+ const raw = readJsonFile(file);
37
72
  const kind = detectPackKindFromManifest(raw);
38
73
  const uuid = raw.header?.uuid;
39
74
  const ver = raw.header?.version;
@@ -85,6 +120,118 @@ function isPackArchive(filePath) {
85
120
  const lower = filePath.toLowerCase();
86
121
  return ARCHIVE_EXTS.some((ext) => lower.endsWith(ext));
87
122
  }
123
+ function archiveStem(filePath) {
124
+ const base = path.basename(filePath);
125
+ const lower = base.toLowerCase();
126
+ for (const ext of ARCHIVE_EXTS) {
127
+ if (lower.endsWith(ext)) return base.slice(0, -ext.length);
128
+ }
129
+ return path.parse(base).name;
130
+ }
131
+ function uniqueSiblingExtractDir(archivePath) {
132
+ const parent = path.dirname(archivePath);
133
+ let dest = path.join(parent, archiveStem(archivePath) || "pack");
134
+ if (!fs.existsSync(dest)) return dest;
135
+ let i = 2;
136
+ while (fs.existsSync(`${dest}_${i}`)) i++;
137
+ return `${dest}_${i}`;
138
+ }
139
+ function listPackArchiveFiles(root, opts) {
140
+ const maxDepth = opts?.maxDepth ?? 4;
141
+ const out = [];
142
+ const abs = path.resolve(root);
143
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) return out;
144
+ function walk(dir, depth) {
145
+ if (fs.existsSync(path.join(dir, "manifest.json"))) return;
146
+ let entries;
147
+ try {
148
+ entries = fs.readdirSync(dir, { withFileTypes: true });
149
+ } catch {
150
+ return;
151
+ }
152
+ for (const e of entries) {
153
+ if (e.name.startsWith(".") || e.name === "node_modules") continue;
154
+ const full = path.join(dir, e.name);
155
+ if (e.isFile() && isPackArchive(full)) {
156
+ out.push(full);
157
+ continue;
158
+ }
159
+ if (e.isDirectory() && depth < maxDepth) walk(full, depth + 1);
160
+ }
161
+ }
162
+ walk(abs, 0);
163
+ return out;
164
+ }
165
+ async function expandNestedArchivesInPlace(workDir, opts) {
166
+ const maxRounds = opts?.maxRounds ?? 3;
167
+ const maxScanDepth = opts?.maxScanDepth ?? 4;
168
+ let expanded = 0;
169
+ for (let round = 0; round < maxRounds; round++) {
170
+ const archives = listPackArchiveFiles(workDir, { maxDepth: maxScanDepth });
171
+ if (archives.length === 0) break;
172
+ for (const arch of archives) {
173
+ const dest = uniqueSiblingExtractDir(arch);
174
+ await fs.promises.mkdir(dest, { recursive: true });
175
+ await extractZipFileToDir(arch, dest);
176
+ await fs.promises.unlink(arch);
177
+ expanded++;
178
+ }
179
+ }
180
+ return expanded;
181
+ }
182
+ async function resolvePackRoots(src, opts) {
183
+ const maxDirDepth = opts?.maxDirDepth ?? 4;
184
+ const maxArchiveRounds = opts?.maxArchiveRounds ?? 3;
185
+ const temps = [];
186
+ const dispose = () => {
187
+ for (const d of temps.splice(0, temps.length)) {
188
+ try {
189
+ fs.rmSync(d, { recursive: true, force: true });
190
+ } catch {
191
+ }
192
+ }
193
+ };
194
+ const abs = path.resolve(src);
195
+ if (!fs.existsSync(abs)) return { roots: [], dispose };
196
+ try {
197
+ const st = fs.statSync(abs);
198
+ if (st.isFile()) {
199
+ if (!isPackArchive(abs)) return { roots: [], dispose };
200
+ const tmp = await extractArchiveToTemp(abs);
201
+ temps.push(tmp);
202
+ await expandNestedArchivesInPlace(tmp, {
203
+ maxRounds: maxArchiveRounds,
204
+ maxScanDepth: maxDirDepth
205
+ });
206
+ return { roots: discoverPackRoots(tmp, { maxDepth: maxDirDepth }), dispose };
207
+ }
208
+ if (!st.isDirectory()) return { roots: [], dispose };
209
+ const nestedArchives = listPackArchiveFiles(abs, { maxDepth: maxDirDepth });
210
+ if (nestedArchives.length === 0) {
211
+ return { roots: discoverPackRoots(abs, { maxDepth: maxDirDepth }), dispose };
212
+ }
213
+ const roots = discoverPackRoots(abs, { maxDepth: maxDirDepth });
214
+ const seen = new Set(roots.map((r) => path.resolve(r)));
215
+ for (const arch of nestedArchives) {
216
+ const tmp = await extractArchiveToTemp(arch);
217
+ temps.push(tmp);
218
+ await expandNestedArchivesInPlace(tmp, {
219
+ maxRounds: maxArchiveRounds,
220
+ maxScanDepth: maxDirDepth
221
+ });
222
+ for (const r of discoverPackRoots(tmp, { maxDepth: maxDirDepth })) {
223
+ const key = path.resolve(r);
224
+ if (seen.has(key)) continue;
225
+ seen.add(key);
226
+ roots.push(r);
227
+ }
228
+ }
229
+ return { roots, dispose };
230
+ } catch (e) {
231
+ dispose();
232
+ throw e;
233
+ }
234
+ }
88
235
  async function extractArchiveToTemp(filePath) {
89
236
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "sfmc-pack-"));
90
237
  await extractZipFileToDir(filePath, tmp);
@@ -143,7 +290,7 @@ function listInstalledWorldPacks(bdsRoot, levelName) {
143
290
  function bumpPackPatchVersion(packDir) {
144
291
  const file = path.join(packDir, "manifest.json");
145
292
  if (!fs.existsSync(file)) throw new Error(`manifest.json missing: ${packDir}`);
146
- const raw = JSON.parse(fs.readFileSync(file, "utf8"));
293
+ const raw = readJsonFile(file);
147
294
  const ver = raw.header?.version;
148
295
  if (!Array.isArray(ver) || ver.length < 3) throw new Error(`invalid header.version in ${file}`);
149
296
  const next = [Number(ver[0]), Number(ver[1]), Number(ver[2]) + 1];
@@ -152,7 +299,7 @@ function bumpPackPatchVersion(packDir) {
152
299
  function writePackHeaderVersion(packDir, next) {
153
300
  const file = path.join(packDir, "manifest.json");
154
301
  if (!fs.existsSync(file)) throw new Error(`manifest.json missing: ${packDir}`);
155
- const raw = JSON.parse(fs.readFileSync(file, "utf8"));
302
+ const raw = readJsonFile(file);
156
303
  if (!raw.header) raw.header = {};
157
304
  raw.header.version = next;
158
305
  if (Array.isArray(raw.modules)) {
@@ -170,80 +317,164 @@ function readPackDependencyUuids(packDir) {
170
317
  const file = path.join(packDir, "manifest.json");
171
318
  if (!fs.existsSync(file)) return [];
172
319
  try {
173
- const raw = JSON.parse(fs.readFileSync(file, "utf8"));
320
+ const raw = readJsonFile(file);
174
321
  return (raw.dependencies ?? []).map((d) => d.uuid).filter((u) => typeof u === "string" && u.length > 0);
175
322
  } catch {
176
323
  return [];
177
324
  }
178
325
  }
179
- function ensureVersionGreaterThan(packDir, floor, component = "patch") {
326
+ function compareSemVer3(a, b) {
327
+ for (let i = 0; i < 3; i++) {
328
+ const d = a[i] - b[i];
329
+ if (d !== 0) return d;
330
+ }
331
+ return 0;
332
+ }
333
+ function maxSemVer3(a, b) {
334
+ return compareSemVer3(a, b) >= 0 ? a : b;
335
+ }
336
+ function bumpSemVer3(v, component) {
337
+ return component === "minor" ? [v[0], v[1] + 1, 0] : [v[0], v[1], v[2] + 1];
338
+ }
339
+ function nextEnabledVersion(remote, previous, component = "patch") {
340
+ return bumpSemVer3(maxSemVer3(remote, previous), component);
341
+ }
342
+ function ensureVersionGreaterThan(packDir, previous, component = "patch") {
180
343
  const info = readPackManifestInfo(packDir);
181
344
  if (!info) throw new Error(`cannot read pack version: ${packDir}`);
182
- let next = component === "minor" ? [info.version[0], info.version[1] + 1, 0] : [info.version[0], info.version[1], info.version[2] + 1];
183
- const cmp = (a, b) => {
184
- for (let i = 0; i < 3; i++) {
185
- const d = a[i] - b[i];
186
- if (d !== 0) return d;
345
+ const next = nextEnabledVersion(info.version, previous, component);
346
+ return writePackHeaderVersion(packDir, next);
347
+ }
348
+ function readPackDirOccupancy(dir) {
349
+ const info = readPackManifestInfo(dir);
350
+ if (info) {
351
+ return {
352
+ uuid: info.uuid,
353
+ version: info.version,
354
+ name: info.name,
355
+ kind: info.kind
356
+ };
357
+ }
358
+ const header = readPackManifestHeader(dir);
359
+ if (header) {
360
+ return { uuid: header.uuid, version: header.version };
361
+ }
362
+ return null;
363
+ }
364
+ function scanDestOccupancy(destParent) {
365
+ const out = [];
366
+ for (const dir of listPackDirsIn(destParent)) {
367
+ let facts = null;
368
+ try {
369
+ facts = readPackDirOccupancy(dir);
370
+ } catch {
187
371
  }
188
- return 0;
189
- };
190
- while (cmp(next, floor) <= 0) {
191
- if (component === "minor") next = [next[0], next[1] + 1, 0];
192
- else next = [next[0], next[1], next[2] + 1];
372
+ out.push({
373
+ folderName: path.basename(dir),
374
+ dir,
375
+ uuid: facts?.uuid ?? null,
376
+ version: facts?.version ?? null,
377
+ ...facts?.name ? { name: facts.name } : {},
378
+ ...facts?.kind ? { kind: facts.kind } : {}
379
+ });
193
380
  }
194
- return writePackHeaderVersion(packDir, next);
381
+ return out;
382
+ }
383
+ function decidePackInstallPlan(opts) {
384
+ const force = Boolean(opts.force);
385
+ const incomingUuid = opts.incoming.uuid.toLowerCase();
386
+ const same = opts.occupancy.find((o) => o.uuid != null && o.uuid.toLowerCase() === incomingUuid);
387
+ if (same) {
388
+ const existingVersion = same.version ?? [0, 0, 0];
389
+ const existing = {
390
+ name: same.name ?? same.folderName,
391
+ uuid: same.uuid,
392
+ version: existingVersion,
393
+ kind: same.kind ?? opts.incoming.kind,
394
+ dir: same.dir
395
+ };
396
+ const newer = compareSemVer3(opts.incoming.version, existing.version) > 0;
397
+ if (force || newer) {
398
+ return {
399
+ kind: "overwriteInPlace",
400
+ folderName: same.folderName,
401
+ dir: same.dir,
402
+ incoming: opts.incoming,
403
+ existing
404
+ };
405
+ }
406
+ return {
407
+ kind: "needConfirm",
408
+ folderName: same.folderName,
409
+ dir: same.dir,
410
+ incoming: opts.incoming,
411
+ existing
412
+ };
413
+ }
414
+ const folderName = allocatePackFolderName({
415
+ info: opts.incoming,
416
+ takenFolderNames: opts.occupancy.map((o) => o.folderName),
417
+ ...opts.hint !== void 0 ? { hint: opts.hint } : {}
418
+ });
419
+ return { kind: "fresh", folderName, incoming: opts.incoming };
420
+ }
421
+ function verifyInstalledPack(destDir, incoming) {
422
+ const info = readPackManifestInfo(destDir);
423
+ if (!info) return { ok: false, reason: "verify: dest has no readable manifest" };
424
+ if (info.uuid.toLowerCase() !== incoming.uuid.toLowerCase()) {
425
+ return { ok: false, reason: `verify: uuid mismatch got=${info.uuid} want=${incoming.uuid}` };
426
+ }
427
+ if (info.kind !== incoming.kind) {
428
+ return { ok: false, reason: `verify: kind mismatch got=${info.kind} want=${incoming.kind}` };
429
+ }
430
+ if (compareSemVer3(info.version, incoming.version) !== 0) {
431
+ return {
432
+ ok: false,
433
+ reason: `verify: version mismatch got=${info.version.join(".")} want=${incoming.version.join(".")}`
434
+ };
435
+ }
436
+ return { ok: true, info };
195
437
  }
196
438
  async function installPackDirectory(opts) {
197
439
  const info = readPackManifestInfo(opts.srcDir);
198
440
  if (!info) {
199
441
  return { ok: false, reason: "invalid or unrecognized manifest.json" };
200
442
  }
201
- const folderName = formatWorldPackFolderName(
202
- opts.folderName ?? path.basename(opts.srcDir),
203
- info.kind
204
- );
205
443
  await fs.promises.mkdir(opts.destParent, { recursive: true });
206
- let conflictDir = null;
207
- let conflictExisting = null;
208
- for (const dir of listPackDirsIn(opts.destParent)) {
209
- const existing = readPackManifestInfo(dir);
210
- const sameFolder = path.basename(dir) === folderName;
211
- const sameUuid = !!existing && existing.uuid === info.uuid;
212
- if (!sameFolder && !sameUuid) continue;
213
- conflictDir = dir;
214
- if (existing) {
215
- conflictExisting = existing;
216
- } else {
217
- const header = readPackManifestHeader(dir);
218
- conflictExisting = {
219
- name: path.basename(dir),
220
- uuid: header?.uuid ?? "(unknown)",
221
- version: header?.version ?? [0, 0, 0],
222
- kind: info.kind
223
- };
224
- }
225
- break;
226
- }
227
- if (conflictDir && !opts.force) {
444
+ const occupancy = scanDestOccupancy(opts.destParent);
445
+ const plan = decidePackInstallPlan({
446
+ incoming: info,
447
+ hint: opts.folderName ?? path.basename(opts.srcDir),
448
+ occupancy,
449
+ ...opts.force !== void 0 ? { force: opts.force } : {}
450
+ });
451
+ if (plan.kind === "needConfirm") {
228
452
  return {
229
453
  ok: false,
230
454
  skipped: true,
231
455
  reason: "conflict",
232
456
  conflict: {
233
- existing: { ...conflictExisting ?? info, dir: conflictDir },
457
+ existing: plan.existing,
234
458
  incoming: info
235
459
  }
236
460
  };
237
461
  }
238
- const destDir = path.join(opts.destParent, folderName);
239
- if (conflictDir && path.resolve(conflictDir) !== path.resolve(destDir)) {
240
- await fs.promises.rm(conflictDir, { recursive: true, force: true });
462
+ const destDir = path.join(opts.destParent, plan.folderName);
463
+ if (plan.kind === "overwriteInPlace" && path.resolve(plan.dir) !== path.resolve(destDir)) {
464
+ return {
465
+ ok: false,
466
+ reason: `invariant: overwrite path drift plan.dir=${plan.dir} destDir=${destDir}`
467
+ };
241
468
  }
242
469
  if (fs.existsSync(destDir)) {
243
470
  await fs.promises.rm(destDir, { recursive: true, force: true });
244
471
  }
245
472
  await copyDirAsync(opts.srcDir, destDir);
246
- return { ok: true, destDir, folderName, info };
473
+ const verified = verifyInstalledPack(destDir, info);
474
+ if (!verified.ok) {
475
+ return { ok: false, reason: verified.reason, folderName: plan.folderName, info };
476
+ }
477
+ return { ok: true, destDir, folderName: plan.folderName, info: verified.info };
247
478
  }
248
479
  async function enableInstalledPack(opts) {
249
480
  await enablePackInWorld({
@@ -312,23 +543,38 @@ function findInstalledPackById(packs, id) {
312
543
  ) ?? null;
313
544
  }
314
545
  export {
546
+ allocatePackFolderName,
315
547
  bumpPackPatchVersion,
548
+ bumpSemVer3,
549
+ choosePackInstallFolderName,
550
+ compareSemVer3,
551
+ decidePackInstallPlan,
316
552
  detectPackKindFromManifest,
317
553
  disableInstalledPack,
318
554
  discoverPackRoots,
319
555
  enableInstalledPack,
320
556
  ensureVersionGreaterThan,
557
+ expandNestedArchivesInPlace,
321
558
  extractArchiveToTemp,
322
559
  findInstalledPackById,
323
560
  formatWorldPackFolderName,
324
561
  installPackDirectory,
562
+ isGenericPackFolderStem,
325
563
  isPackArchive,
326
564
  listInstalledWorldPacks,
565
+ listPackArchiveFiles,
327
566
  listWorldEnableEntries,
328
567
  listWorldEnableListResult,
568
+ maxSemVer3,
569
+ nextEnabledVersion,
329
570
  readPackDependencyUuids,
571
+ readPackDirOccupancy,
330
572
  readPackManifestInfo,
573
+ resolvePackRoots,
574
+ scanDestOccupancy,
575
+ stripWorldPackFolderPrefix,
331
576
  uninstallInstalledPack,
577
+ verifyInstalledPack,
332
578
  worldPackParentDir,
333
579
  writePackHeaderVersion
334
580
  };