@sfmc-bds/bds-tools 0.2.0-beta.1 → 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 +4 -6
- package/dist/check-update.js.map +2 -2
- package/dist/cli-pack-manager.js +0 -0
- package/dist/fsx.d.ts +2 -6
- package/dist/fsx.d.ts.map +1 -1
- package/dist/fsx.js +3 -14
- 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 -5
- 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/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 +98 -7
- package/dist/world-packs.d.ts.map +1 -1
- package/dist/world-packs.js +129 -57
- 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/fsx.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/fsx.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * fsx.ts \u2014 \u5F02\u6B65/\u6D41\u5F0F\u6587\u4EF6\u64CD\u4F5C (\u907F\u514D\u5927\u6587\u4EF6\u52A0\u8F7D\u8FDB\u5185\u5B58)\n */\n\nimport
|
|
5
|
-
"mappings": "AAIA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,
|
|
4
|
+
"sourcesContent": ["/**\n * fsx.ts \u2014 \u5F02\u6B65/\u6D41\u5F0F\u6587\u4EF6\u64CD\u4F5C (\u907F\u514D\u5927\u6587\u4EF6\u52A0\u8F7D\u8FDB\u5185\u5B58)\n */\n\nimport crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { pipeline } from \"node:stream/promises\";\n\n/**\n * \u6D41\u5F0F\u8BA1\u7B97\u6587\u4EF6\u54C8\u5E0C (sha1 / sha256)\uFF0C\n * \u7528\u4E8E\u5927\u6587\u4EF6 (\u4F8B\u5982 BDS \u53EF\u6267\u884C\u6587\u4EF6 ~80MB)\uFF0C\u4E0D\u4F1A OOM\u3002\n */\nexport async function hashFileAsync(filePath: string, algo: \"sha1\" | \"sha256\" = \"sha256\"): Promise<string> {\n return new Promise((resolve, reject) => {\n const h = crypto.createHash(algo);\n const stream = fs.createReadStream(filePath);\n stream.on(\"data\", (chunk: Buffer | string) => {\n h.update(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n });\n stream.on(\"end\", () => resolve(h.digest(\"hex\").toLowerCase()));\n stream.on(\"error\", reject);\n });\n}\n\n/** \u540C\u6B65\u7248\u672C\uFF08\u7528\u4E8E\u5C0F\u6587\u4EF6\uFF09 */\nexport function hashFileSync(filePath: string, algo: \"sha1\" | \"sha256\" = \"sha256\"): string {\n try {\n const h = crypto.createHash(algo);\n h.update(fs.readFileSync(filePath));\n return h.digest(\"hex\").toLowerCase();\n } catch {\n return \"\";\n }\n}\n\n/** \u540C\u6B65\u6D41\u5F0F\u62F7\u8D1D\u5355\u6587\u4EF6 (\u590D\u5236\u4E2D) */\nexport async function copyFileAsync(src: string, dest: string): Promise<void> {\n await pipeline(fs.createReadStream(src), fs.createWriteStream(dest));\n}\n\n/** \u540C\u6B65\u76EE\u5F55\u590D\u5236 (\u9012\u5F52) */\nexport function copyDirSync(src: string, dest: string): void {\n fs.mkdirSync(dest, { recursive: true });\n for (const entry of fs.readdirSync(src)) {\n const srcPath = path.join(src, entry);\n const destPath = path.join(dest, entry);\n if (fs.statSync(srcPath).isDirectory()) {\n copyDirSync(srcPath, destPath);\n } else {\n fs.copyFileSync(srcPath, destPath);\n }\n }\n}\n\n/** \u5F02\u6B65\u76EE\u5F55\u590D\u5236 (\u57FA\u4E8E\u6D41) */\nexport async function copyDirAsync(src: string, dest: string): Promise<void> {\n fs.mkdirSync(dest, { recursive: true });\n await Promise.all(\n fs.readdirSync(src).map(async (entry) => {\n const srcPath = path.join(src, entry);\n const destPath = path.join(dest, entry);\n const stat = fs.statSync(srcPath);\n if (stat.isDirectory()) {\n await copyDirAsync(srcPath, destPath);\n } else {\n await copyFileAsync(srcPath, destPath);\n }\n })\n );\n}\n\n/** \u8BA1\u7B97\u76EE\u5F55\u5927\u5C0F (\u5B57\u8282) */\nexport function getDirSize(dir: string): number {\n let total = 0;\n try {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) total += getDirSize(full);\n else if (entry.isFile()) total += fs.statSync(full).size;\n }\n } catch {}\n return total;\n}\n\n/** \u5F3A\u5236\u6E05\u7A7A\u76EE\u5F55\u5185\u5BB9 (\u4F46\u4FDD\u7559\u76EE\u5F55\u672C\u8EAB) */\nexport function emptyDirSync(dir: string): void {\n if (!fs.existsSync(dir)) return;\n for (const entry of fs.readdirSync(dir)) {\n const full = path.join(dir, entry);\n try {\n const stat = fs.statSync(full);\n if (stat.isDirectory()) fs.rmSync(full, { recursive: true, force: true });\n else fs.unlinkSync(full);\n } catch (e) {\n // \u5FFD\u7565: \u9876\u5C42\u4ECD\u4FDD\u7559\u76EE\u5F55\uFF0C\u5373\u4F7F\u90E8\u5206\u5B50\u9879\u65E0\u6CD5\u5220\u9664\n }\n }\n}\n\n/** \u5B89\u5168\u5220\u9664\u6574\u4E2A\u76EE\u5F55 */\nexport function rmSafe(path: string): void {\n try {\n fs.rmSync(path, { recursive: true, force: true });\n } catch {\n /* ignore */\n }\n}\n\n/** \u5B89\u5168\u5199\u6587\u4EF6 (\u539F\u5B50: \u5199\u4E34\u65F6\u6587\u4EF6 \u2192 rename) */\nexport function writeFileSafe(filePath: string, data: string | Buffer): void {\n const dir = path.dirname(filePath);\n fs.mkdirSync(dir, { recursive: true });\n const tmp = `${filePath}.${process.pid}.tmp`;\n fs.writeFileSync(tmp, data);\n fs.renameSync(tmp, filePath);\n}\n\n/**\n * \u8BFB JSON \u6587\u4EF6\uFF08utf8\uFF09\u3002\n * \u82E5\u6587\u4EF6\u4EE5 UTF-8 BOM\uFF08U+FEFF\uFF09\u5F00\u5934\u5219\u5265\u79BB\u540E\u518D\u89E3\u6790\uFF08\u8BB0\u4E8B\u672C\u300CUTF-8 with BOM\u300D\uFF09\u3002\n */\nexport function readJsonFile<T = unknown>(filePath: string): T {\n let text = fs.readFileSync(filePath, \"utf8\");\n if (text.length > 0 && text.charCodeAt(0) === 0xfeff) {\n text = text.slice(1);\n }\n return JSON.parse(text) as T;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["path"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* host-platform.ts — BDS 宿主平台契约(单一权威来源)
|
|
3
|
+
*
|
|
4
|
+
* Windows / Linux 官方 BDS 的可执行文件名、CDN 路径、启动环境不同。
|
|
5
|
+
* 启动器与更新器只依赖本模块,禁止在业务里再写 bedrock_server.exe / bin-win。
|
|
6
|
+
* macOS 无官方 BDS,按 linux 处理(调用方应避免在 darwin 上当生产宿主)。
|
|
7
|
+
*/
|
|
8
|
+
export type BdsHostOs = "windows" | "linux";
|
|
9
|
+
export declare function bdsHostOs(platform?: NodeJS.Platform): BdsHostOs;
|
|
10
|
+
export declare function bdsExeName(os?: BdsHostOs): string;
|
|
11
|
+
export declare function bdsExePath(bdsPath: string, os?: BdsHostOs): string;
|
|
12
|
+
/** BDS-Versions 详情 JSON 的 platform 段:windows / windows_preview / linux / linux_preview */
|
|
13
|
+
export declare function bdsDetailsPlatformKey(channel: string, os?: BdsHostOs): string;
|
|
14
|
+
/** minecraft.net / azureedge CDN 上的 zip 相对路径 */
|
|
15
|
+
export declare function bdsCdnZipSuffix(channel: string, version: string, os?: BdsHostOs): string;
|
|
16
|
+
/** 解压后完整性检查的必备相对路径 */
|
|
17
|
+
export declare function bdsInstallRequiredFiles(os?: BdsHostOs): string[];
|
|
18
|
+
/**
|
|
19
|
+
* 仅返回需覆盖进 spawn env 的键。
|
|
20
|
+
* Linux 官方 BDS 依赖同目录 lib*.so,须把安装目录放进 LD_LIBRARY_PATH。
|
|
21
|
+
*/
|
|
22
|
+
export declare function bdsSpawnEnvExtra(bdsPath: string, os?: BdsHostOs): Record<string, string>;
|
|
23
|
+
/** Linux zip 解压后往往没有 +x,启动前补上 */
|
|
24
|
+
export declare function ensureBdsExecutable(exePath: string, os?: BdsHostOs): void;
|
|
25
|
+
//# sourceMappingURL=host-platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-platform.d.ts","sourceRoot":"","sources":["../src/host-platform.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAE5C,wBAAgB,SAAS,CAAC,QAAQ,kBAAmB,GAAG,SAAS,CAEhE;AAED,wBAAgB,UAAU,CAAC,EAAE,GAAE,SAAuB,GAAG,MAAM,CAE9D;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,GAAE,SAAuB,GAAG,MAAM,CAE/E;AAED,0FAA0F;AAC1F,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,GAAE,SAAuB,GAAG,MAAM,CAI1F;AAED,gDAAgD;AAChD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,GAAE,SAAuB,GAAG,MAAM,CAUrG;AAED,sBAAsB;AACtB,wBAAgB,uBAAuB,CAAC,EAAE,GAAE,SAAuB,GAAG,MAAM,EAAE,CAE7E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,GAAE,SAAuB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAKrG;AAED,iCAAiC;AACjC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,GAAE,SAAuB,GAAG,IAAI,CAOtF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function bdsHostOs(platform = process.platform) {
|
|
4
|
+
return platform === "win32" ? "windows" : "linux";
|
|
5
|
+
}
|
|
6
|
+
function bdsExeName(os = bdsHostOs()) {
|
|
7
|
+
return os === "windows" ? "bedrock_server.exe" : "bedrock_server";
|
|
8
|
+
}
|
|
9
|
+
function bdsExePath(bdsPath, os = bdsHostOs()) {
|
|
10
|
+
return path.join(bdsPath, bdsExeName(os));
|
|
11
|
+
}
|
|
12
|
+
function bdsDetailsPlatformKey(channel, os = bdsHostOs()) {
|
|
13
|
+
const preview = channel === "preview";
|
|
14
|
+
if (os === "windows") return preview ? "windows_preview" : "windows";
|
|
15
|
+
return preview ? "linux_preview" : "linux";
|
|
16
|
+
}
|
|
17
|
+
function bdsCdnZipSuffix(channel, version, os = bdsHostOs()) {
|
|
18
|
+
const preview = channel === "preview";
|
|
19
|
+
if (os === "windows") {
|
|
20
|
+
return preview ? `/bin-win-preview/bedrock-server-${version}-preview.zip` : `/bin-win/bedrock-server-${version}.zip`;
|
|
21
|
+
}
|
|
22
|
+
return preview ? `/bin-linux-preview/bedrock-server-${version}-preview.zip` : `/bin-linux/bedrock-server-${version}.zip`;
|
|
23
|
+
}
|
|
24
|
+
function bdsInstallRequiredFiles(os = bdsHostOs()) {
|
|
25
|
+
return [bdsExeName(os), "permissions.json"];
|
|
26
|
+
}
|
|
27
|
+
function bdsSpawnEnvExtra(bdsPath, os = bdsHostOs()) {
|
|
28
|
+
if (os === "windows") return {};
|
|
29
|
+
const prev = process.env.LD_LIBRARY_PATH ?? "";
|
|
30
|
+
const ld = prev ? `${bdsPath}${path.delimiter}${prev}` : bdsPath;
|
|
31
|
+
return { LD_LIBRARY_PATH: ld };
|
|
32
|
+
}
|
|
33
|
+
function ensureBdsExecutable(exePath, os = bdsHostOs()) {
|
|
34
|
+
if (os === "windows") return;
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(exePath)) fs.chmodSync(exePath, 493);
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
bdsCdnZipSuffix,
|
|
42
|
+
bdsDetailsPlatformKey,
|
|
43
|
+
bdsExeName,
|
|
44
|
+
bdsExePath,
|
|
45
|
+
bdsHostOs,
|
|
46
|
+
bdsInstallRequiredFiles,
|
|
47
|
+
bdsSpawnEnvExtra,
|
|
48
|
+
ensureBdsExecutable
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=host-platform.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/host-platform.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * host-platform.ts \u2014 BDS \u5BBF\u4E3B\u5E73\u53F0\u5951\u7EA6\uFF08\u5355\u4E00\u6743\u5A01\u6765\u6E90\uFF09\n *\n * Windows / Linux \u5B98\u65B9 BDS \u7684\u53EF\u6267\u884C\u6587\u4EF6\u540D\u3001CDN \u8DEF\u5F84\u3001\u542F\u52A8\u73AF\u5883\u4E0D\u540C\u3002\n * \u542F\u52A8\u5668\u4E0E\u66F4\u65B0\u5668\u53EA\u4F9D\u8D56\u672C\u6A21\u5757\uFF0C\u7981\u6B62\u5728\u4E1A\u52A1\u91CC\u518D\u5199 bedrock_server.exe / bin-win\u3002\n * macOS \u65E0\u5B98\u65B9 BDS\uFF0C\u6309 linux \u5904\u7406\uFF08\u8C03\u7528\u65B9\u5E94\u907F\u514D\u5728 darwin \u4E0A\u5F53\u751F\u4EA7\u5BBF\u4E3B\uFF09\u3002\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport type BdsHostOs = \"windows\" | \"linux\";\n\nexport function bdsHostOs(platform = process.platform): BdsHostOs {\n return platform === \"win32\" ? \"windows\" : \"linux\";\n}\n\nexport function bdsExeName(os: BdsHostOs = bdsHostOs()): string {\n return os === \"windows\" ? \"bedrock_server.exe\" : \"bedrock_server\";\n}\n\nexport function bdsExePath(bdsPath: string, os: BdsHostOs = bdsHostOs()): string {\n return path.join(bdsPath, bdsExeName(os));\n}\n\n/** BDS-Versions \u8BE6\u60C5 JSON \u7684 platform \u6BB5\uFF1Awindows / windows_preview / linux / linux_preview */\nexport function bdsDetailsPlatformKey(channel: string, os: BdsHostOs = bdsHostOs()): string {\n const preview = channel === \"preview\";\n if (os === \"windows\") return preview ? \"windows_preview\" : \"windows\";\n return preview ? \"linux_preview\" : \"linux\";\n}\n\n/** minecraft.net / azureedge CDN \u4E0A\u7684 zip \u76F8\u5BF9\u8DEF\u5F84 */\nexport function bdsCdnZipSuffix(channel: string, version: string, os: BdsHostOs = bdsHostOs()): string {\n const preview = channel === \"preview\";\n if (os === \"windows\") {\n return preview\n ? `/bin-win-preview/bedrock-server-${version}-preview.zip`\n : `/bin-win/bedrock-server-${version}.zip`;\n }\n return preview\n ? `/bin-linux-preview/bedrock-server-${version}-preview.zip`\n : `/bin-linux/bedrock-server-${version}.zip`;\n}\n\n/** \u89E3\u538B\u540E\u5B8C\u6574\u6027\u68C0\u67E5\u7684\u5FC5\u5907\u76F8\u5BF9\u8DEF\u5F84 */\nexport function bdsInstallRequiredFiles(os: BdsHostOs = bdsHostOs()): string[] {\n return [bdsExeName(os), \"permissions.json\"];\n}\n\n/**\n * \u4EC5\u8FD4\u56DE\u9700\u8986\u76D6\u8FDB spawn env \u7684\u952E\u3002\n * Linux \u5B98\u65B9 BDS \u4F9D\u8D56\u540C\u76EE\u5F55 lib*.so\uFF0C\u987B\u628A\u5B89\u88C5\u76EE\u5F55\u653E\u8FDB LD_LIBRARY_PATH\u3002\n */\nexport function bdsSpawnEnvExtra(bdsPath: string, os: BdsHostOs = bdsHostOs()): Record<string, string> {\n if (os === \"windows\") return {};\n const prev = process.env.LD_LIBRARY_PATH ?? \"\";\n const ld = prev ? `${bdsPath}${path.delimiter}${prev}` : bdsPath;\n return { LD_LIBRARY_PATH: ld };\n}\n\n/** Linux zip \u89E3\u538B\u540E\u5F80\u5F80\u6CA1\u6709 +x\uFF0C\u542F\u52A8\u524D\u8865\u4E0A */\nexport function ensureBdsExecutable(exePath: string, os: BdsHostOs = bdsHostOs()): void {\n if (os === \"windows\") return;\n try {\n if (fs.existsSync(exePath)) fs.chmodSync(exePath, 0o755);\n } catch {\n /* \u53EA\u8BFB\u6587\u4EF6\u7CFB\u7EDF\u65F6\u5FFD\u7565\uFF0Cspawn \u4F1A\u518D\u62A5\u9519 */\n }\n}\n"],
|
|
5
|
+
"mappings": "AAQA,OAAO,QAAQ;AACf,OAAO,UAAU;AAIV,SAAS,UAAU,WAAW,QAAQ,UAAqB;AAChE,SAAO,aAAa,UAAU,YAAY;AAC5C;AAEO,SAAS,WAAW,KAAgB,UAAU,GAAW;AAC9D,SAAO,OAAO,YAAY,uBAAuB;AACnD;AAEO,SAAS,WAAW,SAAiB,KAAgB,UAAU,GAAW;AAC/E,SAAO,KAAK,KAAK,SAAS,WAAW,EAAE,CAAC;AAC1C;AAGO,SAAS,sBAAsB,SAAiB,KAAgB,UAAU,GAAW;AAC1F,QAAM,UAAU,YAAY;AAC5B,MAAI,OAAO,UAAW,QAAO,UAAU,oBAAoB;AAC3D,SAAO,UAAU,kBAAkB;AACrC;AAGO,SAAS,gBAAgB,SAAiB,SAAiB,KAAgB,UAAU,GAAW;AACrG,QAAM,UAAU,YAAY;AAC5B,MAAI,OAAO,WAAW;AACpB,WAAO,UACH,mCAAmC,OAAO,iBAC1C,2BAA2B,OAAO;AAAA,EACxC;AACA,SAAO,UACH,qCAAqC,OAAO,iBAC5C,6BAA6B,OAAO;AAC1C;AAGO,SAAS,wBAAwB,KAAgB,UAAU,GAAa;AAC7E,SAAO,CAAC,WAAW,EAAE,GAAG,kBAAkB;AAC5C;AAMO,SAAS,iBAAiB,SAAiB,KAAgB,UAAU,GAA2B;AACrG,MAAI,OAAO,UAAW,QAAO,CAAC;AAC9B,QAAM,OAAO,QAAQ,IAAI,mBAAmB;AAC5C,QAAM,KAAK,OAAO,GAAG,OAAO,GAAG,KAAK,SAAS,GAAG,IAAI,KAAK;AACzD,SAAO,EAAE,iBAAiB,GAAG;AAC/B;AAGO,SAAS,oBAAoB,SAAiB,KAAgB,UAAU,GAAS;AACtF,MAAI,OAAO,UAAW;AACtB,MAAI;AACF,QAAI,GAAG,WAAW,OAAO,EAAG,IAAG,UAAU,SAAS,GAAK;AAAA,EACzD,QAAQ;AAAA,EAER;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/http.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* http.ts —
|
|
2
|
+
* http.ts — 针对大文件与外部上游设计的 HTTP 网络请求工具
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* 核心机制:
|
|
5
|
+
* - 分阶段超时控制:区分连接建立超时(connectTimeout)与全量传输超时(totalTimeout)
|
|
6
|
+
* - 自动重定向处理:安全跟踪 3xx 跳转(最多 5 次)
|
|
7
|
+
* - 流式文件下载:通过 pipeline 管道直接落盘,避免大文件一次性加载至内存
|
|
8
|
+
* - 异常容错清理:下载失败时自动清理残留的临时文件碎片
|
|
8
9
|
*/
|
|
9
10
|
interface HttpOptions {
|
|
10
11
|
method?: "GET" | "HEAD" | "POST";
|
|
@@ -14,24 +15,61 @@ interface HttpOptions {
|
|
|
14
15
|
totalTimeoutMs?: number;
|
|
15
16
|
redirects?: number;
|
|
16
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* 发起底层 HTTP/HTTPS 请求。
|
|
20
|
+
*
|
|
21
|
+
* @param url 目标请求地址。
|
|
22
|
+
* @param opts 请求参数(包含请求方法、请求头、主体、阶段超时时间及重定向次数等)。
|
|
23
|
+
* @returns 包含状态码、响应体 Buffer 及响应头字典的结果对象。
|
|
24
|
+
*/
|
|
17
25
|
export declare function httpRequest(url: string, opts?: HttpOptions): Promise<{
|
|
18
26
|
statusCode: number;
|
|
19
27
|
body: Buffer;
|
|
20
28
|
headers: Record<string, string | string[] | undefined>;
|
|
21
29
|
}>;
|
|
30
|
+
/**
|
|
31
|
+
* 发起 GET 请求并自动将响应体解析为 JSON 数据对象。
|
|
32
|
+
*
|
|
33
|
+
* @template T 预期的 JSON 数据类型。
|
|
34
|
+
* @param url 请求目标 URL。
|
|
35
|
+
* @param opts 请求配置选项。
|
|
36
|
+
* @returns 解析后的 JSON 对象。
|
|
37
|
+
*/
|
|
22
38
|
export declare function httpGetJson<T = unknown>(url: string, opts?: HttpOptions): Promise<T>;
|
|
39
|
+
/**
|
|
40
|
+
* 发起 GET 请求并获取 UTF-8 纯文本响应内容。
|
|
41
|
+
*
|
|
42
|
+
* @param url 请求目标 URL。
|
|
43
|
+
* @param opts 请求配置选项。
|
|
44
|
+
* @returns 响应文本字符串。
|
|
45
|
+
*/
|
|
23
46
|
export declare function httpGetText(url: string, opts?: HttpOptions): Promise<string>;
|
|
24
|
-
/** 多个源并发请求,谁先成功返回(每个源都用 JSON 解析,解析失败也失败) */
|
|
25
|
-
export declare function fetchJsonWithFallback<T = unknown>(sources: string[], timeoutMs?: number): Promise<T>;
|
|
26
47
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
48
|
+
* 针对多个备选源并发发起请求,由最先成功返回且合法解析 JSON 的源提供数据(Happy Eyeballs 机制)。
|
|
49
|
+
*
|
|
50
|
+
* @template T 返回的 JSON 数据类型。
|
|
51
|
+
* @param sources 候选 URL 列表。
|
|
52
|
+
* @param timeoutMs 超时时间(毫秒,默认为 15000)。
|
|
53
|
+
* @returns 最先成功响应的数据。
|
|
54
|
+
* @throws 当所有备选源均失败时抛出聚合错误。
|
|
31
55
|
*/
|
|
56
|
+
export declare function fetchJsonWithFallback<T = unknown>(sources: string[], timeoutMs?: number): Promise<T>;
|
|
57
|
+
/** 下载配置选项。 */
|
|
32
58
|
export interface DownloadOptions extends HttpOptions {
|
|
59
|
+
/** 下载进度变化回调函数。 */
|
|
33
60
|
onProgress?: (downloaded: number, total: number) => void;
|
|
34
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* 流式下载网络资源至本地指定路径。
|
|
64
|
+
* - 传输失败时自动删除残留的临时文件碎片
|
|
65
|
+
* - 支持节流的进度通知回调
|
|
66
|
+
* - 支持连接建立与全量传输双阶段独立超时
|
|
67
|
+
*
|
|
68
|
+
* @param url 下载目标地址。
|
|
69
|
+
* @param destPath 本地落盘目标绝对路径。
|
|
70
|
+
* @param opts 下载参数选项。
|
|
71
|
+
* @returns 成功下载的总字节数。
|
|
72
|
+
*/
|
|
35
73
|
export declare function httpDownload(url: string, destPath: string, opts?: DownloadOptions): Promise<number>;
|
|
36
74
|
export {};
|
|
37
75
|
//# sourceMappingURL=http.d.ts.map
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH,UAAU,WAAW;IACnB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAE/B,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;CAAE,CAAC,CA2DvG;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,CAAC,GAAG,OAAO,EAC3C,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAGtF;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,GAAG,OAAO,EACrD,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,SAAS,GACjB,OAAO,CAAC,CAAC,CAAC,CAcZ;AAED,cAAc;AACd,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,kBAAkB;IAClB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1D;AAED;;;;;;;;;;GAUG;AAGH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,MAAM,CAAC,CA4GjB"}
|
package/dist/http.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/http.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * http.ts \u2014 \u7A33\u5B9A HTTP \u5DE5\u5177 (\u5206\u9636\u6BB5\u8D85\u65F6 + \u81EA\u52A8\u91CD\u5B9A\u5411 + \u6D41\u5F0F\u4E0B\u8F7D)\n *\n * \u6539\u8FDB:\n * - connect timeout vs total timeout \u5206\u5F00\n * - \u4E0B\u8F7D\u901A\u8FC7\u6D41\u7BA1\u9053\uFF0C\u907F\u514D\u5927\u6587\u4EF6\u52A0\u8F7D\u5230\u5185\u5B58\n * - \u5931\u8D25\u540E\u5DF2\u5199\u5165\u7684\u4E34\u65F6\u6587\u4EF6\u53EF\u88AB\u6E05\u7406\n */\n\nimport http from \"node:http\";\nimport https from \"node:https\";\nimport type { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { createWriteStream, statSync } from \"node:fs\";\nimport { log } from \"./log.js\";\n\ninterface HttpOptions {\n method?: \"GET\" | \"HEAD\" | \"POST\";\n headers?: Record<string, string>;\n body?: string;\n connectTimeoutMs?: number;\n totalTimeoutMs?: number;\n redirects?: number;\n}\n\nconst MAX_REDIRECTS = 5;\nconst DEFAULT_CONNECT_TIMEOUT = 15_000;\nconst DEFAULT_TOTAL_TIMEOUT = 600_000;\n\nexport async function httpRequest(\n url: string,\n opts: HttpOptions = {}\n): Promise<{ statusCode: number; body: Buffer; headers: Record<string, string | string[] | undefined> }> {\n const redirects = opts.redirects ?? 0;\n const totalTimeoutMs = opts.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT;\n const connectTimeoutMs = opts.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT;\n\n const u = new URL(url);\n const isHttps = u.protocol === \"https:\";\n const mod = isHttps ? https : http;\n\n return new Promise((resolve, reject) => {\n const req = mod.request(\n {\n hostname: u.hostname,\n port: u.port || (isHttps ? 443 : 80),\n path: u.pathname + u.search,\n method: opts.method ?? \"GET\",\n headers: { \"User-Agent\": \"BDSUpdater/2.0\", ...(opts.headers ?? {}) },\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (c: Buffer) => chunks.push(c));\n res.on(\"end\", () => {\n const body = Buffer.concat(chunks);\n const status = res.statusCode ?? 0;\n // 3xx \u2192 \u8DDF\u968F Location\n if (status >= 300 && status < 400 && redirects < MAX_REDIRECTS) {\n const loc = res.headers.location;\n if (!loc) return reject(new Error(`HTTP ${status} \u4F46\u7F3A\u5C11 Location`));\n const next = loc.startsWith(\"http\") ? loc : new URL(loc, url).href;\n httpRequest(next, { ...opts, redirects: redirects + 1 })\n .then(resolve)\n .catch(reject);\n return;\n }\n if (status >= 400) {\n return reject(new Error(`HTTP ${status} for ${url}`));\n }\n resolve({ statusCode: status, body, headers: res.headers });\n });\n }\n );\n\n const totalTimer = setTimeout(() => {\n req.destroy(new Error(`HTTP \u603B\u8D85\u65F6 ${totalTimeoutMs}ms: ${url}`));\n }, totalTimeoutMs);\n const connectTimer = setTimeout(() => {\n req.destroy(new Error(`HTTP \u8FDE\u63A5\u8D85\u65F6 ${connectTimeoutMs}ms: ${url}`));\n }, connectTimeoutMs);\n\n req.on(\"socket\", () => clearTimeout(connectTimer));\n req.on(\"error\", (err) => {\n clearTimeout(totalTimer);\n clearTimeout(connectTimer);\n reject(err);\n });\n\n if (opts.body) req.write(opts.body);\n req.end();\n });\n}\n\nexport async function httpGetJson<T = unknown>(\n url: string,\n opts: HttpOptions = {}\n): Promise<T> {\n const res = await httpRequest(url, opts);\n try {\n return JSON.parse(res.body.toString(\"utf-8\")) as T;\n } catch (e) {\n throw new Error(`JSON \u89E3\u6790\u5931\u8D25 ${url}: ${(e as Error).message}`);\n }\n}\n\nexport async function httpGetText(url: string, opts: HttpOptions = {}): Promise<string> {\n const res = await httpRequest(url, opts);\n return res.body.toString(\"utf-8\");\n}\n\n/** \u591A\u4E2A\u6E90\u5E76\u53D1\u8BF7\u6C42\uFF0C\u8C01\u5148\u6210\u529F\u8FD4\u56DE\uFF08\u6BCF\u4E2A\u6E90\u90FD\u7528 JSON \u89E3\u6790\uFF0C\u89E3\u6790\u5931\u8D25\u4E5F\u5931\u8D25\uFF09 */\nexport async function fetchJsonWithFallback<T = unknown>(\n sources: string[],\n timeoutMs = 15_000\n): Promise<T> {\n const tasks = sources.map(async (url) => {\n const res = await httpGetJson<T>(url, { totalTimeoutMs: timeoutMs });\n return { url, value: res };\n });\n const settled = await Promise.allSettled(tasks);\n for (const r of settled) {\n if (r.status === \"fulfilled\") return r.value.value;\n }\n const errors = settled\n .filter((r): r is PromiseRejectedResult => r.status === \"rejected\")\n .map((r) => r.reason?.message ?? \"unknown\")\n .join(\"; \");\n throw new Error(`\u6240\u6709\u6E90\u5747\u4E0D\u53EF\u7528: ${errors}`);\n}\n\n/**\n * \u6D41\u5F0F\u4E0B\u8F7D\u5230\u6307\u5B9A\u8DEF\u5F84\uFF0C\u8FD4\u56DE\u5B57\u8282\u6570\u3002\n * - \u5931\u8D25\u65F6\u81EA\u52A8\u6E05\u7406 partial \u6587\u4EF6\n * - \u652F\u6301 progress \u56DE\u8C03\n * - \u652F\u6301 connect/total \u9636\u6BB5\u8D85\u65F6\n */\nexport interface DownloadOptions extends HttpOptions {\n onProgress?: (downloaded: number, total: number) => void;\n}\n\nexport async function httpDownload(\n url: string,\n destPath: string,\n opts: DownloadOptions = {}\n): Promise<number> {\n const totalTimeoutMs = opts.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT;\n const connectTimeoutMs = opts.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT;\n\n const u = new URL(url);\n const isHttps = u.protocol === \"https:\";\n const mod = isHttps ? https : http;\n let redirects = opts.redirects ?? 0;\n\n return new Promise((resolve, reject) => {\n const req = mod.request(\n {\n hostname: u.hostname,\n port: u.port || (isHttps ? 443 : 80),\n path: u.pathname + u.search,\n method: \"GET\",\n headers: { \"User-Agent\": \"BDSUpdater/2.0\", ...(opts.headers ?? {}) },\n },\n (res) => {\n const status = res.statusCode ?? 0;\n if (status >= 300 && status < 400 && redirects < MAX_REDIRECTS) {\n const loc = res.headers.location;\n if (!loc) return reject(new Error(`HTTP ${status} \u7F3A\u5C11 Location`));\n redirects++;\n const next = loc.startsWith(\"http\") ? loc : new URL(loc, url).href;\n res.resume();\n httpDownload(next, destPath, { ...opts, redirects })\n .then(resolve)\n .catch(reject);\n return;\n }\n if (status >= 400) return reject(new Error(`HTTP ${status} for ${url}`));\n\n const total = parseInt(String(res.headers[\"content-length\"] ?? 0), 10) || 0;\n const file = createWriteStream(destPath);\n let downloaded = 0;\n let failed = false;\n // \u8282\u6D41\u8FDB\u5EA6\u56DE\u8C03:\u6BCF\u4E2A tick \u6700\u591A 10 \u6B21/\u79D2(100ms \u95F4\u9694),\n // \u907F\u514D cli-progress \u9891\u7E41\u91CD\u7ED8\u62D6\u6162\u4E0B\u8F7D\n let lastProgressAt = 0;\n const PROGRESS_INTERVAL_MS = 100;\n\n const handleError = (e: Error): void => {\n if (failed) return;\n failed = true;\n file.close();\n // \u6E05\u7406 partial \u6587\u4EF6\n import(\"node:fs\").then((fs) => {\n try { fs.unlinkSync(destPath); } catch {}\n });\n reject(e);\n };\n\n const stream: Readable = res;\n stream.on(\"data\", (chunk: Buffer) => {\n downloaded += chunk.length;\n if (opts.onProgress) {\n const now = Date.now();\n if (now - lastProgressAt >= PROGRESS_INTERVAL_MS) {\n lastProgressAt = now;\n /* total=0\uFF08\u65E0 Content-Length\uFF09\u65F6\u4ECD\u56DE\u8C03\uFF0C\u8C03\u7528\u65B9\u53EF\u7528\u5DF2\u4E0B\u8F7D\u5B57\u8282\u66F4\u65B0 UI */\n opts.onProgress(downloaded, total);\n }\n }\n });\n stream.on(\"error\", (e) => handleError(e as Error));\n\n file.on(\"error\", (e) => handleError(e as Error));\n\n pipeline(stream, file).catch((e) => {\n if (!failed) handleError(e as Error);\n });\n\n // \u4E0D\u80FD\u76F4\u63A5\u76D1\u542C file 'finish' \u56E0\u4E3A\u6211\u4EEC\u5728\u9519\u8BEF\u65F6\u624B\u52A8 handleError\n file.on(\"finish\", () => {\n if (failed) return;\n try {\n const finalBytes = statSync(destPath).size;\n // \u6536\u5C3E:\u786E\u4FDD\u6700\u540E\u4E00\u6B21\u56DE\u8C03\u8BA9\u8FDB\u5EA6\u6761\u8D70\u5230 100%(\u5373\u4FBF\u6700\u8FD1\n // \u4E00\u4E2A tick \u7684\u8282\u6D41\u6CA1\u89E6\u53D1,\u4E5F\u8865\u4E00\u6B21)\u3002\n // \u65E0 Content-Length\uFF08total=0\uFF09\u65F6\u7528 finalBytes \u4F5C\u603B\u91CF\uFF0C\u4E0E\u4E2D\u9014\u300C\u4ECD\u56DE\u8C03\u300D\u5951\u7EA6\u4E00\u81F4\uFF08LSP\uFF09\u3002\n if (opts.onProgress) {\n opts.onProgress(finalBytes, total > 0 ? total : finalBytes);\n }\n resolve(finalBytes);\n } catch (e) {\n reject(e as Error);\n }\n });\n }\n );\n\n const totalTimer = setTimeout(() => {\n req.destroy(new Error(`\u4E0B\u8F7D\u603B\u8D85\u65F6 ${totalTimeoutMs}ms: ${url}`));\n }, totalTimeoutMs);\n const connectTimer = setTimeout(() => {\n req.destroy(new Error(`\u4E0B\u8F7D\u8FDE\u63A5\u8D85\u65F6 ${connectTimeoutMs}ms: ${url}`));\n }, connectTimeoutMs);\n\n req.on(\"socket\", () => clearTimeout(connectTimer));\n req.on(\"error\", (e) => {\n clearTimeout(totalTimer);\n clearTimeout(connectTimer);\n log.warn(`HTTP \u9519\u8BEF ${url}: ${(e as Error).message}`);\n reject(e);\n });\n req.end();\n });\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * http.ts \u2014 \u9488\u5BF9\u5927\u6587\u4EF6\u4E0E\u5916\u90E8\u4E0A\u6E38\u8BBE\u8BA1\u7684 HTTP \u7F51\u7EDC\u8BF7\u6C42\u5DE5\u5177\n *\n * \u6838\u5FC3\u673A\u5236\uFF1A\n * - \u5206\u9636\u6BB5\u8D85\u65F6\u63A7\u5236\uFF1A\u533A\u5206\u8FDE\u63A5\u5EFA\u7ACB\u8D85\u65F6\uFF08connectTimeout\uFF09\u4E0E\u5168\u91CF\u4F20\u8F93\u8D85\u65F6\uFF08totalTimeout\uFF09\n * - \u81EA\u52A8\u91CD\u5B9A\u5411\u5904\u7406\uFF1A\u5B89\u5168\u8DDF\u8E2A 3xx \u8DF3\u8F6C\uFF08\u6700\u591A 5 \u6B21\uFF09\n * - \u6D41\u5F0F\u6587\u4EF6\u4E0B\u8F7D\uFF1A\u901A\u8FC7 pipeline \u7BA1\u9053\u76F4\u63A5\u843D\u76D8\uFF0C\u907F\u514D\u5927\u6587\u4EF6\u4E00\u6B21\u6027\u52A0\u8F7D\u81F3\u5185\u5B58\n * - \u5F02\u5E38\u5BB9\u9519\u6E05\u7406\uFF1A\u4E0B\u8F7D\u5931\u8D25\u65F6\u81EA\u52A8\u6E05\u7406\u6B8B\u7559\u7684\u4E34\u65F6\u6587\u4EF6\u788E\u7247\n */\n\nimport http from \"node:http\";\nimport https from \"node:https\";\nimport type { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { createWriteStream, statSync } from \"node:fs\";\nimport { log } from \"./log.js\";\n\ninterface HttpOptions {\n method?: \"GET\" | \"HEAD\" | \"POST\";\n headers?: Record<string, string>;\n body?: string;\n connectTimeoutMs?: number;\n totalTimeoutMs?: number;\n redirects?: number;\n}\n\nconst MAX_REDIRECTS = 5;\nconst DEFAULT_CONNECT_TIMEOUT = 15_000;\nconst DEFAULT_TOTAL_TIMEOUT = 600_000;\n\n/**\n * \u53D1\u8D77\u5E95\u5C42 HTTP/HTTPS \u8BF7\u6C42\u3002\n *\n * @param url \u76EE\u6807\u8BF7\u6C42\u5730\u5740\u3002\n * @param opts \u8BF7\u6C42\u53C2\u6570\uFF08\u5305\u542B\u8BF7\u6C42\u65B9\u6CD5\u3001\u8BF7\u6C42\u5934\u3001\u4E3B\u4F53\u3001\u9636\u6BB5\u8D85\u65F6\u65F6\u95F4\u53CA\u91CD\u5B9A\u5411\u6B21\u6570\u7B49\uFF09\u3002\n * @returns \u5305\u542B\u72B6\u6001\u7801\u3001\u54CD\u5E94\u4F53 Buffer \u53CA\u54CD\u5E94\u5934\u5B57\u5178\u7684\u7ED3\u679C\u5BF9\u8C61\u3002\n */\nexport async function httpRequest(\n\n url: string,\n opts: HttpOptions = {}\n): Promise<{ statusCode: number; body: Buffer; headers: Record<string, string | string[] | undefined> }> {\n const redirects = opts.redirects ?? 0;\n const totalTimeoutMs = opts.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT;\n const connectTimeoutMs = opts.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT;\n\n const u = new URL(url);\n const isHttps = u.protocol === \"https:\";\n const mod = isHttps ? https : http;\n\n return new Promise((resolve, reject) => {\n const req = mod.request(\n {\n hostname: u.hostname,\n port: u.port || (isHttps ? 443 : 80),\n path: u.pathname + u.search,\n method: opts.method ?? \"GET\",\n headers: { \"User-Agent\": \"BDSUpdater/2.0\", ...(opts.headers ?? {}) },\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (c: Buffer) => chunks.push(c));\n res.on(\"end\", () => {\n const body = Buffer.concat(chunks);\n const status = res.statusCode ?? 0;\n // 3xx \u2192 \u8DDF\u968F Location\n if (status >= 300 && status < 400 && redirects < MAX_REDIRECTS) {\n const loc = res.headers.location;\n if (!loc) return reject(new Error(`HTTP ${status} \u4F46\u7F3A\u5C11 Location`));\n const next = loc.startsWith(\"http\") ? loc : new URL(loc, url).href;\n httpRequest(next, { ...opts, redirects: redirects + 1 })\n .then(resolve)\n .catch(reject);\n return;\n }\n if (status >= 400) {\n return reject(new Error(`HTTP ${status} for ${url}`));\n }\n resolve({ statusCode: status, body, headers: res.headers });\n });\n }\n );\n\n const totalTimer = setTimeout(() => {\n req.destroy(new Error(`HTTP \u603B\u8D85\u65F6 ${totalTimeoutMs}ms: ${url}`));\n }, totalTimeoutMs);\n const connectTimer = setTimeout(() => {\n req.destroy(new Error(`HTTP \u8FDE\u63A5\u8D85\u65F6 ${connectTimeoutMs}ms: ${url}`));\n }, connectTimeoutMs);\n\n req.on(\"socket\", () => clearTimeout(connectTimer));\n req.on(\"error\", (err) => {\n clearTimeout(totalTimer);\n clearTimeout(connectTimer);\n reject(err);\n });\n\n if (opts.body) req.write(opts.body);\n req.end();\n });\n}\n\n/**\n * \u53D1\u8D77 GET \u8BF7\u6C42\u5E76\u81EA\u52A8\u5C06\u54CD\u5E94\u4F53\u89E3\u6790\u4E3A JSON \u6570\u636E\u5BF9\u8C61\u3002\n *\n * @template T \u9884\u671F\u7684 JSON \u6570\u636E\u7C7B\u578B\u3002\n * @param url \u8BF7\u6C42\u76EE\u6807 URL\u3002\n * @param opts \u8BF7\u6C42\u914D\u7F6E\u9009\u9879\u3002\n * @returns \u89E3\u6790\u540E\u7684 JSON \u5BF9\u8C61\u3002\n */\nexport async function httpGetJson<T = unknown>(\n url: string,\n opts: HttpOptions = {}\n): Promise<T> {\n const res = await httpRequest(url, opts);\n try {\n return JSON.parse(res.body.toString(\"utf-8\")) as T;\n } catch (e) {\n throw new Error(`JSON \u89E3\u6790\u5931\u8D25 ${url}: ${(e as Error).message}`);\n }\n}\n\n/**\n * \u53D1\u8D77 GET \u8BF7\u6C42\u5E76\u83B7\u53D6 UTF-8 \u7EAF\u6587\u672C\u54CD\u5E94\u5185\u5BB9\u3002\n *\n * @param url \u8BF7\u6C42\u76EE\u6807 URL\u3002\n * @param opts \u8BF7\u6C42\u914D\u7F6E\u9009\u9879\u3002\n * @returns \u54CD\u5E94\u6587\u672C\u5B57\u7B26\u4E32\u3002\n */\nexport async function httpGetText(url: string, opts: HttpOptions = {}): Promise<string> {\n const res = await httpRequest(url, opts);\n return res.body.toString(\"utf-8\");\n}\n\n/**\n * \u9488\u5BF9\u591A\u4E2A\u5907\u9009\u6E90\u5E76\u53D1\u53D1\u8D77\u8BF7\u6C42\uFF0C\u7531\u6700\u5148\u6210\u529F\u8FD4\u56DE\u4E14\u5408\u6CD5\u89E3\u6790 JSON \u7684\u6E90\u63D0\u4F9B\u6570\u636E\uFF08Happy Eyeballs \u673A\u5236\uFF09\u3002\n *\n * @template T \u8FD4\u56DE\u7684 JSON \u6570\u636E\u7C7B\u578B\u3002\n * @param sources \u5019\u9009 URL \u5217\u8868\u3002\n * @param timeoutMs \u8D85\u65F6\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF0C\u9ED8\u8BA4\u4E3A 15000\uFF09\u3002\n * @returns \u6700\u5148\u6210\u529F\u54CD\u5E94\u7684\u6570\u636E\u3002\n * @throws \u5F53\u6240\u6709\u5907\u9009\u6E90\u5747\u5931\u8D25\u65F6\u629B\u51FA\u805A\u5408\u9519\u8BEF\u3002\n */\nexport async function fetchJsonWithFallback<T = unknown>(\n sources: string[],\n timeoutMs = 15_000\n): Promise<T> {\n const tasks = sources.map(async (url) => {\n const res = await httpGetJson<T>(url, { totalTimeoutMs: timeoutMs });\n return { url, value: res };\n });\n const settled = await Promise.allSettled(tasks);\n for (const r of settled) {\n if (r.status === \"fulfilled\") return r.value.value;\n }\n const errors = settled\n .filter((r): r is PromiseRejectedResult => r.status === \"rejected\")\n .map((r) => r.reason?.message ?? \"unknown\")\n .join(\"; \");\n throw new Error(`\u6240\u6709\u6E90\u5747\u4E0D\u53EF\u7528: ${errors}`);\n}\n\n/** \u4E0B\u8F7D\u914D\u7F6E\u9009\u9879\u3002 */\nexport interface DownloadOptions extends HttpOptions {\n /** \u4E0B\u8F7D\u8FDB\u5EA6\u53D8\u5316\u56DE\u8C03\u51FD\u6570\u3002 */\n onProgress?: (downloaded: number, total: number) => void;\n}\n\n/**\n * \u6D41\u5F0F\u4E0B\u8F7D\u7F51\u7EDC\u8D44\u6E90\u81F3\u672C\u5730\u6307\u5B9A\u8DEF\u5F84\u3002\n * - \u4F20\u8F93\u5931\u8D25\u65F6\u81EA\u52A8\u5220\u9664\u6B8B\u7559\u7684\u4E34\u65F6\u6587\u4EF6\u788E\u7247\n * - \u652F\u6301\u8282\u6D41\u7684\u8FDB\u5EA6\u901A\u77E5\u56DE\u8C03\n * - \u652F\u6301\u8FDE\u63A5\u5EFA\u7ACB\u4E0E\u5168\u91CF\u4F20\u8F93\u53CC\u9636\u6BB5\u72EC\u7ACB\u8D85\u65F6\n *\n * @param url \u4E0B\u8F7D\u76EE\u6807\u5730\u5740\u3002\n * @param destPath \u672C\u5730\u843D\u76D8\u76EE\u6807\u7EDD\u5BF9\u8DEF\u5F84\u3002\n * @param opts \u4E0B\u8F7D\u53C2\u6570\u9009\u9879\u3002\n * @returns \u6210\u529F\u4E0B\u8F7D\u7684\u603B\u5B57\u8282\u6570\u3002\n */\n\n\nexport async function httpDownload(\n url: string,\n destPath: string,\n opts: DownloadOptions = {}\n): Promise<number> {\n const totalTimeoutMs = opts.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT;\n const connectTimeoutMs = opts.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT;\n\n const u = new URL(url);\n const isHttps = u.protocol === \"https:\";\n const mod = isHttps ? https : http;\n let redirects = opts.redirects ?? 0;\n\n return new Promise((resolve, reject) => {\n const req = mod.request(\n {\n hostname: u.hostname,\n port: u.port || (isHttps ? 443 : 80),\n path: u.pathname + u.search,\n method: \"GET\",\n headers: { \"User-Agent\": \"BDSUpdater/2.0\", ...(opts.headers ?? {}) },\n },\n (res) => {\n const status = res.statusCode ?? 0;\n if (status >= 300 && status < 400 && redirects < MAX_REDIRECTS) {\n const loc = res.headers.location;\n if (!loc) return reject(new Error(`HTTP ${status} \u7F3A\u5C11 Location`));\n redirects++;\n const next = loc.startsWith(\"http\") ? loc : new URL(loc, url).href;\n res.resume();\n httpDownload(next, destPath, { ...opts, redirects })\n .then(resolve)\n .catch(reject);\n return;\n }\n if (status >= 400) return reject(new Error(`HTTP ${status} for ${url}`));\n\n const total = parseInt(String(res.headers[\"content-length\"] ?? 0), 10) || 0;\n const file = createWriteStream(destPath);\n let downloaded = 0;\n let failed = false;\n // \u8282\u6D41\u8FDB\u5EA6\u56DE\u8C03:\u6BCF\u4E2A tick \u6700\u591A 10 \u6B21/\u79D2(100ms \u95F4\u9694),\n // \u907F\u514D cli-progress \u9891\u7E41\u91CD\u7ED8\u62D6\u6162\u4E0B\u8F7D\n let lastProgressAt = 0;\n const PROGRESS_INTERVAL_MS = 100;\n\n const handleError = (e: Error): void => {\n if (failed) return;\n failed = true;\n file.close();\n // \u6E05\u7406 partial \u6587\u4EF6\n import(\"node:fs\").then((fs) => {\n try { fs.unlinkSync(destPath); } catch {}\n });\n reject(e);\n };\n\n const stream: Readable = res;\n stream.on(\"data\", (chunk: Buffer) => {\n downloaded += chunk.length;\n if (opts.onProgress) {\n const now = Date.now();\n if (now - lastProgressAt >= PROGRESS_INTERVAL_MS) {\n lastProgressAt = now;\n /* total=0\uFF08\u65E0 Content-Length\uFF09\u65F6\u4ECD\u56DE\u8C03\uFF0C\u8C03\u7528\u65B9\u53EF\u7528\u5DF2\u4E0B\u8F7D\u5B57\u8282\u66F4\u65B0 UI */\n opts.onProgress(downloaded, total);\n }\n }\n });\n stream.on(\"error\", (e) => handleError(e as Error));\n\n file.on(\"error\", (e) => handleError(e as Error));\n\n pipeline(stream, file).catch((e) => {\n if (!failed) handleError(e as Error);\n });\n\n // \u4E0D\u80FD\u76F4\u63A5\u76D1\u542C file 'finish' \u56E0\u4E3A\u6211\u4EEC\u5728\u9519\u8BEF\u65F6\u624B\u52A8 handleError\n file.on(\"finish\", () => {\n if (failed) return;\n try {\n const finalBytes = statSync(destPath).size;\n // \u6536\u5C3E:\u786E\u4FDD\u6700\u540E\u4E00\u6B21\u56DE\u8C03\u8BA9\u8FDB\u5EA6\u6761\u8D70\u5230 100%(\u5373\u4FBF\u6700\u8FD1\n // \u4E00\u4E2A tick \u7684\u8282\u6D41\u6CA1\u89E6\u53D1,\u4E5F\u8865\u4E00\u6B21)\u3002\n // \u65E0 Content-Length\uFF08total=0\uFF09\u65F6\u7528 finalBytes \u4F5C\u603B\u91CF\uFF0C\u4E0E\u4E2D\u9014\u300C\u4ECD\u56DE\u8C03\u300D\u5951\u7EA6\u4E00\u81F4\uFF08LSP\uFF09\u3002\n if (opts.onProgress) {\n opts.onProgress(finalBytes, total > 0 ? total : finalBytes);\n }\n resolve(finalBytes);\n } catch (e) {\n reject(e as Error);\n }\n });\n }\n );\n\n const totalTimer = setTimeout(() => {\n req.destroy(new Error(`\u4E0B\u8F7D\u603B\u8D85\u65F6 ${totalTimeoutMs}ms: ${url}`));\n }, totalTimeoutMs);\n const connectTimer = setTimeout(() => {\n req.destroy(new Error(`\u4E0B\u8F7D\u8FDE\u63A5\u8D85\u65F6 ${connectTimeoutMs}ms: ${url}`));\n }, connectTimeoutMs);\n\n req.on(\"socket\", () => clearTimeout(connectTimer));\n req.on(\"error\", (e) => {\n clearTimeout(totalTimer);\n clearTimeout(connectTimer);\n log.warn(`HTTP \u9519\u8BEF ${url}: ${(e as Error).message}`);\n reject(e);\n });\n req.end();\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/log.d.ts
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* log.ts — bds-tools 统一日志实例
|
|
3
3
|
*
|
|
4
4
|
* stdout bare + 落盘 LOG_PATH (<ROOT>/.sfmc/logs/bds-update.log)。
|
|
5
|
-
* check-update.ts 是独立入口,用 source = "updater" 单独创建 logger
|
|
6
|
-
* 见该文件内的 createUpdaterLogger()。
|
|
5
|
+
* check-update.ts 是独立入口,用 source = "updater" 单独创建 logger。
|
|
7
6
|
*/
|
|
8
7
|
export declare const log: import("@sfmc-bds/sdk/logs").NodeServiceLogger;
|
|
9
|
-
/**
|
|
8
|
+
/** 兼容钩子:FileSink 为 sync append,close 为空操作 */
|
|
10
9
|
export declare function closeLog(): void;
|
|
11
10
|
//# sourceMappingURL=log.d.ts.map
|
package/dist/log.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,eAAO,MAAM,GAAG,gDAGd,CAAC;AAEH,6CAA6C;AAC7C,wBAAgB,QAAQ,IAAI,IAAI,CAE/B"}
|
package/dist/log.js
CHANGED
package/dist/log.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/log.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * log.ts \u2014 bds-tools \u7EDF\u4E00\u65E5\u5FD7\u5B9E\u4F8B\n *\n * stdout bare + \u843D\u76D8 LOG_PATH (<ROOT>/.sfmc/logs/bds-update.log)\u3002\n * check-update.ts \u662F\u72EC\u7ACB\u5165\u53E3,\u7528 source = \"updater\" \u5355\u72EC\u521B\u5EFA logger
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * log.ts \u2014 bds-tools \u7EDF\u4E00\u65E5\u5FD7\u5B9E\u4F8B\n *\n * stdout bare + \u843D\u76D8 LOG_PATH (<ROOT>/.sfmc/logs/bds-update.log)\u3002\n * check-update.ts \u662F\u72EC\u7ACB\u5165\u53E3,\u7528 source = \"updater\" \u5355\u72EC\u521B\u5EFA logger\u3002\n */\n\nimport { createNodeServiceLogger } from \"@sfmc-bds/sdk/logs\";\nimport { LOG_PATH } from \"./paths.js\";\n\nexport const log = createNodeServiceLogger({\n source: \"bds-tools\",\n logPath: LOG_PATH,\n});\n\n/** \u517C\u5BB9\u94A9\u5B50\uFF1AFileSink \u4E3A sync append\uFF0Cclose \u4E3A\u7A7A\u64CD\u4F5C */\nexport function closeLog(): void {\n log.close();\n}\n"],
|
|
5
|
+
"mappings": "AAOA,SAAS,+BAA+B;AACxC,SAAS,gBAAgB;AAElB,MAAM,MAAM,wBAAwB;AAAA,EACzC,QAAQ;AAAA,EACR,SAAS;AACX,CAAC;AAGM,SAAS,WAAiB;AAC/B,MAAI,MAAM;AACZ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { describe, it, before, after } from "node:test";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
const CLI = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "dist", "cli-pack-manager.js");
|
|
9
|
+
function run(args) {
|
|
10
|
+
const r = spawnSync(process.execPath, [CLI, ...args], { encoding: "utf8" });
|
|
11
|
+
return { status: r.status, out: r.stdout ?? "", err: r.stderr ?? "" };
|
|
12
|
+
}
|
|
13
|
+
describe("pack-manager CLI extensions", () => {
|
|
14
|
+
let tmp;
|
|
15
|
+
before(() => {
|
|
16
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), "sfmc-pm-"));
|
|
17
|
+
});
|
|
18
|
+
after(() => {
|
|
19
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
it("assemble-rp --modules-json \u53EA\u7528\u663E\u5F0F map(\u4E0D\u626B\u6574\u6811)", async () => {
|
|
22
|
+
const { loadModuleResourcePackMap, assembleResourcePack } = await import("./pack-manager.js");
|
|
23
|
+
const modA = path.join(tmp, "mod-a", "resource_pack");
|
|
24
|
+
const modB = path.join(tmp, "mod-b", "resource_pack");
|
|
25
|
+
fs.mkdirSync(modA, { recursive: true });
|
|
26
|
+
fs.mkdirSync(modB, { recursive: true });
|
|
27
|
+
fs.writeFileSync(path.join(modA, "a.txt"), "a");
|
|
28
|
+
fs.writeFileSync(path.join(modB, "b.txt"), "b");
|
|
29
|
+
const mapFile = path.join(tmp, "map.json");
|
|
30
|
+
fs.writeFileSync(mapFile, JSON.stringify({ "mod-a": modA }));
|
|
31
|
+
const map = loadModuleResourcePackMap(mapFile);
|
|
32
|
+
assert.deepEqual(Object.keys(map), ["mod-a"]);
|
|
33
|
+
const out = path.join(tmp, "rp-out");
|
|
34
|
+
await assembleResourcePack({
|
|
35
|
+
moduleResourceDirs: map,
|
|
36
|
+
outDir: out,
|
|
37
|
+
projectName: "test-rp",
|
|
38
|
+
uuid: "11111111-1111-1111-1111-111111111111",
|
|
39
|
+
version: [1, 0, 0]
|
|
40
|
+
});
|
|
41
|
+
assert.ok(fs.existsSync(path.join(out, "mod-a", "a.txt")));
|
|
42
|
+
assert.ok(!fs.existsSync(path.join(out, "mod-b")));
|
|
43
|
+
const cli = run([
|
|
44
|
+
"assemble-rp",
|
|
45
|
+
"--modules-json",
|
|
46
|
+
mapFile,
|
|
47
|
+
"--out",
|
|
48
|
+
path.join(tmp, "rp-cli"),
|
|
49
|
+
"--name",
|
|
50
|
+
"test-rp-cli",
|
|
51
|
+
"--uuid",
|
|
52
|
+
"22222222-2222-2222-2222-222222222222"
|
|
53
|
+
]);
|
|
54
|
+
assert.equal(cli.status, 0, cli.err || cli.out);
|
|
55
|
+
});
|
|
56
|
+
it("deploy --clear-rp \u5220\u9664\u4E16\u754C\u5185\u6B8B\u7559 RP \u76EE\u5F55", async () => {
|
|
57
|
+
const { deployToBDS } = await import("./pack-manager.js");
|
|
58
|
+
const bds = path.join(tmp, "bds");
|
|
59
|
+
const level = "Bedrock level";
|
|
60
|
+
const worlds = path.join(bds, "worlds", level);
|
|
61
|
+
const bpSrc = path.join(tmp, "bp-src");
|
|
62
|
+
const rpDst = path.join(worlds, "resource_packs", "sfmc-modules-rp");
|
|
63
|
+
fs.mkdirSync(bpSrc, { recursive: true });
|
|
64
|
+
fs.writeFileSync(path.join(bpSrc, "dummy.txt"), "bp");
|
|
65
|
+
fs.mkdirSync(rpDst, { recursive: true });
|
|
66
|
+
fs.writeFileSync(path.join(rpDst, "stale.txt"), "old");
|
|
67
|
+
await deployToBDS({
|
|
68
|
+
bdsRoot: bds,
|
|
69
|
+
levelName: level,
|
|
70
|
+
behaviorPackSrc: bpSrc,
|
|
71
|
+
bpName: "sfmc-modules",
|
|
72
|
+
rpName: "sfmc-modules-rp",
|
|
73
|
+
clearResourcePack: true
|
|
74
|
+
});
|
|
75
|
+
assert.ok(!fs.existsSync(rpDst));
|
|
76
|
+
assert.ok(fs.existsSync(path.join(worlds, "behavior_packs", "sfmc-modules", "dummy.txt")));
|
|
77
|
+
});
|
|
78
|
+
it("read-manifest / has-pack / list-packs CLI \u5951\u7EA6", async () => {
|
|
79
|
+
const { assembleBehaviorPack, enablePackInWorld, worldPackListHas, readWorldPackList } = await import("./pack-manager.js");
|
|
80
|
+
const bpOut = path.join(tmp, "bp-assembled");
|
|
81
|
+
const src = path.join(tmp, "bp-empty-src");
|
|
82
|
+
fs.mkdirSync(path.join(src, "scripts"), { recursive: true });
|
|
83
|
+
fs.writeFileSync(path.join(src, "scripts", "main.js"), "//\n");
|
|
84
|
+
const uuid = "33333333-3333-3333-3333-333333333333";
|
|
85
|
+
await assembleBehaviorPack({
|
|
86
|
+
srcDir: src,
|
|
87
|
+
outDir: bpOut,
|
|
88
|
+
projectName: "sfmc-modules",
|
|
89
|
+
uuid,
|
|
90
|
+
version: [1, 2, 3]
|
|
91
|
+
});
|
|
92
|
+
const rm = run(["read-manifest", "--pack-dir", bpOut]);
|
|
93
|
+
assert.equal(rm.status, 0, rm.err);
|
|
94
|
+
const header = JSON.parse(rm.out.trim());
|
|
95
|
+
assert.equal(header.uuid, uuid);
|
|
96
|
+
assert.deepEqual(header.version, [1, 2, 3]);
|
|
97
|
+
const worldsDir = path.join(tmp, "worlds-root");
|
|
98
|
+
await enablePackInWorld({
|
|
99
|
+
worldsDir,
|
|
100
|
+
levelName: "L1",
|
|
101
|
+
kind: "behavior",
|
|
102
|
+
packUuid: uuid,
|
|
103
|
+
version: [1, 2, 3]
|
|
104
|
+
});
|
|
105
|
+
assert.equal(worldPackListHas(worldsDir, "L1", "behavior", uuid), true);
|
|
106
|
+
assert.equal(worldPackListHas(worldsDir, "L1", "behavior", "00000000-0000-0000-0000-000000000000"), false);
|
|
107
|
+
const hp = run([
|
|
108
|
+
"has-pack",
|
|
109
|
+
"--worlds-dir",
|
|
110
|
+
worldsDir,
|
|
111
|
+
"--level",
|
|
112
|
+
"L1",
|
|
113
|
+
"--kind",
|
|
114
|
+
"behavior",
|
|
115
|
+
"--pack-id",
|
|
116
|
+
uuid
|
|
117
|
+
]);
|
|
118
|
+
assert.equal(hp.status, 0);
|
|
119
|
+
assert.equal(hp.out.trim(), "1");
|
|
120
|
+
const lp = run(["list-packs", "--worlds-dir", worldsDir, "--level", "L1", "--kind", "behavior"]);
|
|
121
|
+
assert.equal(lp.status, 0, lp.err);
|
|
122
|
+
const listed = JSON.parse(lp.out.trim());
|
|
123
|
+
assert.equal(listed.length, 1);
|
|
124
|
+
assert.equal(listed[0].pack_id, uuid);
|
|
125
|
+
assert.deepEqual(readWorldPackList(worldsDir, "L1", "behavior"), listed);
|
|
126
|
+
});
|
|
127
|
+
it("readLevelNameSync \u4E0E async \u540C\u5951\u7EA6\uFF08\u4F9B sfmc resolveBdsContext\uFF09", async () => {
|
|
128
|
+
const { readLevelName, readLevelNameSync } = await import("./pack-manager.js");
|
|
129
|
+
const bds = path.join(tmp, "bds-level");
|
|
130
|
+
fs.mkdirSync(bds, { recursive: true });
|
|
131
|
+
assert.equal(readLevelNameSync(bds), "Bedrock level");
|
|
132
|
+
assert.equal(await readLevelName(bds), "Bedrock level");
|
|
133
|
+
fs.writeFileSync(path.join(bds, "server.properties"), "level-name=My World\n", "utf8");
|
|
134
|
+
assert.equal(readLevelNameSync(bds), "My World");
|
|
135
|
+
assert.equal(await readLevelName(bds), "My World");
|
|
136
|
+
});
|
|
137
|
+
it("BDS \u8DEF\u5F84 helpers \u5355\u4E00\u6743\u5A01\uFF08Demeter/DRY\uFF09", async () => {
|
|
138
|
+
const {
|
|
139
|
+
bdsWorldsDir,
|
|
140
|
+
bdsWorldLevelDir,
|
|
141
|
+
worldPackListFile,
|
|
142
|
+
configPermissionPath,
|
|
143
|
+
hasConfigPermission,
|
|
144
|
+
serverPropertiesPath,
|
|
145
|
+
ensureConfigPermission
|
|
146
|
+
} = await import("./pack-manager.js");
|
|
147
|
+
const bds = path.join(tmp, "bds-paths");
|
|
148
|
+
fs.mkdirSync(bds, { recursive: true });
|
|
149
|
+
assert.equal(bdsWorldsDir(bds), path.join(bds, "worlds"));
|
|
150
|
+
assert.equal(bdsWorldLevelDir(bds, "L1"), path.join(bds, "worlds", "L1"));
|
|
151
|
+
assert.equal(
|
|
152
|
+
worldPackListFile(bdsWorldsDir(bds), "L1", "behavior"),
|
|
153
|
+
path.join(bds, "worlds", "L1", "world_behavior_packs.json")
|
|
154
|
+
);
|
|
155
|
+
assert.equal(
|
|
156
|
+
worldPackListFile(bdsWorldsDir(bds), "L1", "resource"),
|
|
157
|
+
path.join(bds, "worlds", "L1", "world_resource_packs.json")
|
|
158
|
+
);
|
|
159
|
+
assert.equal(serverPropertiesPath(bds), path.join(bds, "server.properties"));
|
|
160
|
+
const uuid = "00000000-0000-4000-8000-000000000099";
|
|
161
|
+
assert.equal(configPermissionPath(bds, uuid), path.join(bds, "config", uuid, "permission.json"));
|
|
162
|
+
assert.equal(hasConfigPermission(bds, uuid), false);
|
|
163
|
+
assert.equal(await ensureConfigPermission(bds, uuid), true);
|
|
164
|
+
assert.equal(hasConfigPermission(bds, uuid), true);
|
|
165
|
+
assert.equal(await ensureConfigPermission(bds, uuid), false);
|
|
166
|
+
});
|
|
167
|
+
it("readWorldPackListResult \u533A\u5206\u7F3A\u5931\u4E0E JSON \u635F\u574F\uFF08doctor parseFail\uFF09", async () => {
|
|
168
|
+
const { readWorldPackListResult } = await import("./pack-manager.js");
|
|
169
|
+
const worldsDir = path.join(tmp, "worlds-parse");
|
|
170
|
+
const levelDir = path.join(worldsDir, "Lbad");
|
|
171
|
+
fs.mkdirSync(levelDir, { recursive: true });
|
|
172
|
+
const missing = readWorldPackListResult(worldsDir, "Lbad", "behavior");
|
|
173
|
+
assert.deepEqual(missing.entries, []);
|
|
174
|
+
assert.equal(missing.parseFailedFile, void 0);
|
|
175
|
+
const badFile = path.join(levelDir, "world_behavior_packs.json");
|
|
176
|
+
fs.writeFileSync(badFile, "{not-json", "utf8");
|
|
177
|
+
const bad = readWorldPackListResult(worldsDir, "Lbad", "behavior");
|
|
178
|
+
assert.deepEqual(bad.entries, []);
|
|
179
|
+
assert.equal(bad.parseFailedFile, badFile);
|
|
180
|
+
fs.writeFileSync(badFile, '{"not":"array"}', "utf8");
|
|
181
|
+
const notArr = readWorldPackListResult(worldsDir, "Lbad", "behavior");
|
|
182
|
+
assert.deepEqual(notArr.entries, []);
|
|
183
|
+
assert.equal(notArr.parseFailedFile, badFile);
|
|
184
|
+
});
|
|
185
|
+
it("\u65E0 deploy-catalog \u65F6\u4ECD\u53EF\u51ED\u78C1\u76D8 RP manifest \u5378\u4E16\u754C\u6E05\u5355(BLOCKER \u56DE\u5F52)", async () => {
|
|
186
|
+
const {
|
|
187
|
+
assembleResourcePack,
|
|
188
|
+
deployToBDS,
|
|
189
|
+
enablePackInWorld,
|
|
190
|
+
disablePackInWorld,
|
|
191
|
+
readPackManifestHeader,
|
|
192
|
+
worldPackListHas
|
|
193
|
+
} = await import("./pack-manager.js");
|
|
194
|
+
const bds = path.join(tmp, "bds-stale");
|
|
195
|
+
const level = "Bedrock level";
|
|
196
|
+
const worlds = path.join(bds, "worlds", level);
|
|
197
|
+
const worldsDir = path.join(bds, "worlds");
|
|
198
|
+
const bpSrc = path.join(tmp, "bp-stale-src");
|
|
199
|
+
const rpMod = path.join(tmp, "mod-stale", "resource_pack");
|
|
200
|
+
fs.mkdirSync(path.join(bpSrc, "scripts"), { recursive: true });
|
|
201
|
+
fs.writeFileSync(path.join(bpSrc, "scripts", "main.js"), "//\n");
|
|
202
|
+
fs.mkdirSync(rpMod, { recursive: true });
|
|
203
|
+
fs.writeFileSync(path.join(rpMod, "x.txt"), "x");
|
|
204
|
+
const staleUuid = "44444444-4444-4444-4444-444444444444";
|
|
205
|
+
const rpAssembled = path.join(tmp, "rp-stale-assembled");
|
|
206
|
+
await assembleResourcePack({
|
|
207
|
+
moduleResourceDirs: { stale: rpMod },
|
|
208
|
+
outDir: rpAssembled,
|
|
209
|
+
projectName: "sfmc-modules-rp",
|
|
210
|
+
uuid: staleUuid,
|
|
211
|
+
version: [1, 0, 0]
|
|
212
|
+
});
|
|
213
|
+
await deployToBDS({
|
|
214
|
+
bdsRoot: bds,
|
|
215
|
+
levelName: level,
|
|
216
|
+
behaviorPackSrc: bpSrc,
|
|
217
|
+
resourcePackSrc: rpAssembled,
|
|
218
|
+
bpName: "sfmc-modules",
|
|
219
|
+
rpName: "sfmc-modules-rp"
|
|
220
|
+
});
|
|
221
|
+
await enablePackInWorld({
|
|
222
|
+
worldsDir,
|
|
223
|
+
levelName: level,
|
|
224
|
+
kind: "resource",
|
|
225
|
+
packUuid: staleUuid,
|
|
226
|
+
version: [1, 0, 0]
|
|
227
|
+
});
|
|
228
|
+
assert.equal(worldPackListHas(worldsDir, level, "resource", staleUuid), true);
|
|
229
|
+
const rpDst = path.join(worlds, "resource_packs", "sfmc-modules-rp");
|
|
230
|
+
const header = readPackManifestHeader(rpDst);
|
|
231
|
+
assert.equal(header?.uuid, staleUuid);
|
|
232
|
+
assert.ok(header);
|
|
233
|
+
await deployToBDS({
|
|
234
|
+
bdsRoot: bds,
|
|
235
|
+
levelName: level,
|
|
236
|
+
behaviorPackSrc: bpSrc,
|
|
237
|
+
bpName: "sfmc-modules",
|
|
238
|
+
rpName: "sfmc-modules-rp",
|
|
239
|
+
clearResourcePack: true
|
|
240
|
+
});
|
|
241
|
+
assert.ok(!fs.existsSync(rpDst));
|
|
242
|
+
await disablePackInWorld({
|
|
243
|
+
worldsDir,
|
|
244
|
+
levelName: level,
|
|
245
|
+
kind: "resource",
|
|
246
|
+
packUuid: header.uuid,
|
|
247
|
+
version: [1, 0, 0]
|
|
248
|
+
});
|
|
249
|
+
assert.equal(worldPackListHas(worldsDir, level, "resource", staleUuid), false);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
//# sourceMappingURL=pack-manager-lifecycle.test.js.map
|