@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.
- package/LICENSE +663 -0
- package/dist/bds-manager.d.ts +16 -6
- package/dist/bds-manager.d.ts.map +1 -1
- package/dist/bds-manager.js +40 -65
- package/dist/bds-manager.js.map +2 -2
- package/dist/check-update.d.ts.map +1 -1
- package/dist/check-update.js +15 -17
- package/dist/check-update.js.map +2 -2
- package/dist/cli-pack-manager.js +7 -3
- package/dist/cli-pack-manager.js.map +2 -2
- package/dist/fsx.d.ts +6 -1
- package/dist/fsx.d.ts.map +1 -1
- package/dist/fsx.js +9 -1
- package/dist/fsx.js.map +2 -2
- package/dist/host-platform.d.ts +25 -0
- package/dist/host-platform.d.ts.map +1 -0
- package/dist/host-platform.js +50 -0
- package/dist/host-platform.js.map +7 -0
- package/dist/http.d.ts +49 -11
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js.map +2 -2
- package/dist/log.d.ts +2 -3
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +0 -1
- package/dist/log.js.map +2 -2
- package/dist/pack-manager-lifecycle.test.js +252 -0
- package/dist/pack-manager-lifecycle.test.js.map +7 -0
- package/dist/pack-manager.d.ts +24 -29
- package/dist/pack-manager.d.ts.map +1 -1
- package/dist/pack-manager.js +11 -4
- package/dist/pack-manager.js.map +2 -2
- package/dist/pack-update/bindings.d.ts +16 -0
- package/dist/pack-update/bindings.d.ts.map +1 -0
- package/dist/pack-update/bindings.js +69 -0
- package/dist/pack-update/bindings.js.map +7 -0
- package/dist/pack-update/config.d.ts +16 -0
- package/dist/pack-update/config.d.ts.map +1 -0
- package/dist/pack-update/config.js +146 -0
- package/dist/pack-update/config.js.map +7 -0
- package/dist/pack-update/config.test.js +75 -0
- package/dist/pack-update/config.test.js.map +7 -0
- package/dist/pack-update/deps.d.ts +28 -0
- package/dist/pack-update/deps.d.ts.map +1 -0
- package/dist/pack-update/deps.js +20 -0
- package/dist/pack-update/deps.js.map +7 -0
- package/dist/pack-update/index.d.ts +55 -0
- package/dist/pack-update/index.d.ts.map +1 -0
- package/dist/pack-update/index.js +72 -0
- package/dist/pack-update/index.js.map +7 -0
- package/dist/pack-update/providers/curseforge.d.ts +23 -0
- package/dist/pack-update/providers/curseforge.d.ts.map +1 -0
- package/dist/pack-update/providers/curseforge.js +229 -0
- package/dist/pack-update/providers/curseforge.js.map +7 -0
- package/dist/pack-update/providers/index.d.ts +16 -0
- package/dist/pack-update/providers/index.d.ts.map +1 -0
- package/dist/pack-update/providers/index.js +37 -0
- package/dist/pack-update/providers/index.js.map +7 -0
- package/dist/pack-update/service.d.ts +28 -0
- package/dist/pack-update/service.d.ts.map +1 -0
- package/dist/pack-update/service.js +570 -0
- package/dist/pack-update/service.js.map +7 -0
- package/dist/pack-update/types.d.ts +126 -0
- package/dist/pack-update/types.d.ts.map +1 -0
- package/dist/pack-update/types.js +8 -0
- package/dist/pack-update/types.js.map +7 -0
- package/dist/pack-update/version-policy.d.ts +81 -0
- package/dist/pack-update/version-policy.d.ts.map +1 -0
- package/dist/pack-update/version-policy.js +210 -0
- package/dist/pack-update/version-policy.js.map +7 -0
- package/dist/pack-update/version-policy.test.js +137 -0
- package/dist/pack-update/version-policy.test.js.map +7 -0
- package/dist/paths.d.ts +1 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +3 -5
- package/dist/paths.js.map +2 -2
- package/dist/process-probe.d.ts +34 -0
- package/dist/process-probe.d.ts.map +1 -0
- package/dist/process-probe.js +152 -0
- package/dist/process-probe.js.map +7 -0
- package/dist/process-probe.test.js +76 -0
- package/dist/process-probe.test.js.map +7 -0
- package/dist/qq-events-notify.d.ts +16 -0
- package/dist/qq-events-notify.d.ts.map +1 -0
- package/dist/qq-events-notify.js +70 -0
- package/dist/qq-events-notify.js.map +7 -0
- package/dist/qqutil.d.ts +10 -5
- package/dist/qqutil.d.ts.map +1 -1
- package/dist/qqutil.js +49 -8
- package/dist/qqutil.js.map +2 -2
- package/dist/update-result.d.ts +8 -2
- package/dist/update-result.d.ts.map +1 -1
- package/dist/update-result.js +7 -5
- package/dist/update-result.js.map +2 -2
- package/dist/upstream.d.ts +18 -9
- package/dist/upstream.d.ts.map +1 -1
- package/dist/upstream.js +8 -7
- package/dist/upstream.js.map +2 -2
- package/dist/version.js +2 -2
- package/dist/version.js.map +2 -2
- package/dist/world-packs.d.ts +158 -5
- package/dist/world-packs.d.ts.map +1 -1
- package/dist/world-packs.js +294 -48
- package/dist/world-packs.js.map +2 -2
- package/dist/world-packs.test.js +676 -0
- package/dist/world-packs.test.js.map +7 -0
- package/dist/zipx.test.js +36 -0
- package/dist/zipx.test.js.map +7 -0
- package/package.json +43 -39
package/dist/world-packs.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
44
|
-
export declare function
|
|
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
|
-
*
|
|
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":"
|
|
1
|
+
{"version":3,"file":"world-packs.d.ts","sourceRoot":"","sources":["../src/world-packs.ts"],"names":[],"mappings":"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"}
|
package/dist/world-packs.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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:
|
|
457
|
+
existing: plan.existing,
|
|
234
458
|
incoming: info
|
|
235
459
|
}
|
|
236
460
|
};
|
|
237
461
|
}
|
|
238
|
-
const destDir = path.join(opts.destParent, folderName);
|
|
239
|
-
if (
|
|
240
|
-
|
|
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
|
-
|
|
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
|
};
|