@sfmc-bds/bds-tools 0.1.0 → 0.2.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bds-manager.d.ts.map +1 -1
- package/dist/bds-manager.js +236 -263
- package/dist/bds-manager.js.map +7 -1
- package/dist/changelog.js +39 -50
- package/dist/changelog.js.map +7 -1
- package/dist/check-update.d.ts +2 -2
- package/dist/check-update.d.ts.map +1 -1
- package/dist/check-update.js +361 -401
- package/dist/check-update.js.map +7 -1
- package/dist/cli-pack-manager.d.ts +10 -6
- package/dist/cli-pack-manager.d.ts.map +1 -1
- package/dist/cli-pack-manager.js +226 -135
- package/dist/cli-pack-manager.js.map +7 -1
- package/dist/fsx.js +86 -100
- package/dist/fsx.js.map +7 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +166 -185
- package/dist/http.js.map +7 -1
- package/dist/is-main.d.ts +17 -0
- package/dist/is-main.d.ts.map +1 -0
- package/dist/is-main.js +32 -0
- package/dist/is-main.js.map +7 -0
- package/dist/log.d.ts +2 -4
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +12 -19
- package/dist/log.js.map +7 -1
- package/dist/logger.js +6 -10
- package/dist/logger.js.map +7 -1
- package/dist/pack-manager.d.ts +88 -24
- package/dist/pack-manager.d.ts.map +1 -1
- package/dist/pack-manager.js +280 -245
- package/dist/pack-manager.js.map +7 -1
- package/dist/paths.d.ts +8 -3
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +53 -39
- package/dist/paths.js.map +7 -1
- package/dist/qqutil.d.ts.map +1 -1
- package/dist/qqutil.js +90 -87
- package/dist/qqutil.js.map +7 -1
- package/dist/recovery.js +26 -37
- package/dist/recovery.js.map +7 -1
- package/dist/rollback.d.ts +1 -1
- package/dist/rollback.js +55 -74
- package/dist/rollback.js.map +7 -1
- package/dist/server-properties.d.ts +12 -0
- package/dist/server-properties.d.ts.map +1 -0
- package/dist/server-properties.js +28 -0
- package/dist/server-properties.js.map +7 -0
- package/dist/taskbar.d.ts +2 -0
- package/dist/taskbar.d.ts.map +1 -1
- package/dist/taskbar.js +51 -82
- package/dist/taskbar.js.map +7 -1
- package/dist/types.js +1 -5
- package/dist/types.js.map +7 -1
- package/dist/update-result.d.ts +12 -0
- package/dist/update-result.d.ts.map +1 -0
- package/dist/update-result.js +15 -0
- package/dist/update-result.js.map +7 -0
- package/dist/upstream.js +94 -108
- package/dist/upstream.js.map +7 -1
- package/dist/version.js +63 -81
- package/dist/version.js.map +7 -1
- package/dist/world-packs.d.ts +112 -0
- package/dist/world-packs.d.ts.map +1 -0
- package/dist/world-packs.js +335 -0
- package/dist/world-packs.js.map +7 -0
- package/dist/zipx.d.ts +10 -0
- package/dist/zipx.d.ts.map +1 -0
- package/dist/zipx.js +48 -0
- package/dist/zipx.js.map +7 -0
- package/package.json +17 -6
package/dist/fsx.js
CHANGED
|
@@ -1,117 +1,103 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* fsx.ts — 异步/流式文件操作 (避免大文件加载进内存)
|
|
3
|
-
*/
|
|
4
1
|
import fs from "node:fs";
|
|
5
2
|
import path from "node:path";
|
|
6
3
|
import crypto from "node:crypto";
|
|
7
4
|
import { pipeline } from "node:stream/promises";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const h = crypto.createHash(algo);
|
|
15
|
-
const stream = fs.createReadStream(filePath);
|
|
16
|
-
stream.on("data", (chunk) => {
|
|
17
|
-
h.update(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
18
|
-
});
|
|
19
|
-
stream.on("end", () => resolve(h.digest("hex").toLowerCase()));
|
|
20
|
-
stream.on("error", reject);
|
|
5
|
+
async function hashFileAsync(filePath, algo = "sha256") {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const h = crypto.createHash(algo);
|
|
8
|
+
const stream = fs.createReadStream(filePath);
|
|
9
|
+
stream.on("data", (chunk) => {
|
|
10
|
+
h.update(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
21
11
|
});
|
|
12
|
+
stream.on("end", () => resolve(h.digest("hex").toLowerCase()));
|
|
13
|
+
stream.on("error", reject);
|
|
14
|
+
});
|
|
22
15
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return "";
|
|
32
|
-
}
|
|
16
|
+
function hashFileSync(filePath, algo = "sha256") {
|
|
17
|
+
try {
|
|
18
|
+
const h = crypto.createHash(algo);
|
|
19
|
+
h.update(fs.readFileSync(filePath));
|
|
20
|
+
return h.digest("hex").toLowerCase();
|
|
21
|
+
} catch {
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
33
24
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
await pipeline(fs.createReadStream(src), fs.createWriteStream(dest));
|
|
25
|
+
async function copyFileAsync(src, dest) {
|
|
26
|
+
await pipeline(fs.createReadStream(src), fs.createWriteStream(dest));
|
|
37
27
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
else {
|
|
48
|
-
fs.copyFileSync(srcPath, destPath);
|
|
49
|
-
}
|
|
28
|
+
function copyDirSync(src, dest) {
|
|
29
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
30
|
+
for (const entry of fs.readdirSync(src)) {
|
|
31
|
+
const srcPath = path.join(src, entry);
|
|
32
|
+
const destPath = path.join(dest, entry);
|
|
33
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
34
|
+
copyDirSync(srcPath, destPath);
|
|
35
|
+
} else {
|
|
36
|
+
fs.copyFileSync(srcPath, destPath);
|
|
50
37
|
}
|
|
38
|
+
}
|
|
51
39
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
/** 计算目录大小 (字节) */
|
|
68
|
-
export function getDirSize(dir) {
|
|
69
|
-
let total = 0;
|
|
70
|
-
try {
|
|
71
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
72
|
-
const full = path.join(dir, entry.name);
|
|
73
|
-
if (entry.isDirectory())
|
|
74
|
-
total += getDirSize(full);
|
|
75
|
-
else if (entry.isFile())
|
|
76
|
-
total += fs.statSync(full).size;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch { }
|
|
80
|
-
return total;
|
|
40
|
+
async function copyDirAsync(src, dest) {
|
|
41
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
42
|
+
await Promise.all(
|
|
43
|
+
fs.readdirSync(src).map(async (entry) => {
|
|
44
|
+
const srcPath = path.join(src, entry);
|
|
45
|
+
const destPath = path.join(dest, entry);
|
|
46
|
+
const stat = fs.statSync(srcPath);
|
|
47
|
+
if (stat.isDirectory()) {
|
|
48
|
+
await copyDirAsync(srcPath, destPath);
|
|
49
|
+
} else {
|
|
50
|
+
await copyFileAsync(srcPath, destPath);
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
);
|
|
81
54
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const stat = fs.statSync(full);
|
|
90
|
-
if (stat.isDirectory())
|
|
91
|
-
fs.rmSync(full, { recursive: true, force: true });
|
|
92
|
-
else
|
|
93
|
-
fs.unlinkSync(full);
|
|
94
|
-
}
|
|
95
|
-
catch (e) {
|
|
96
|
-
// 忽略: 顶层仍保留目录,即使部分子项无法删除
|
|
97
|
-
}
|
|
55
|
+
function getDirSize(dir) {
|
|
56
|
+
let total = 0;
|
|
57
|
+
try {
|
|
58
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
59
|
+
const full = path.join(dir, entry.name);
|
|
60
|
+
if (entry.isDirectory()) total += getDirSize(full);
|
|
61
|
+
else if (entry.isFile()) total += fs.statSync(full).size;
|
|
98
62
|
}
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
return total;
|
|
99
66
|
}
|
|
100
|
-
|
|
101
|
-
|
|
67
|
+
function emptyDirSync(dir) {
|
|
68
|
+
if (!fs.existsSync(dir)) return;
|
|
69
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
70
|
+
const full = path.join(dir, entry);
|
|
102
71
|
try {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
72
|
+
const stat = fs.statSync(full);
|
|
73
|
+
if (stat.isDirectory()) fs.rmSync(full, { recursive: true, force: true });
|
|
74
|
+
else fs.unlinkSync(full);
|
|
75
|
+
} catch (e) {
|
|
107
76
|
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function rmSafe(path2) {
|
|
80
|
+
try {
|
|
81
|
+
fs.rmSync(path2, { recursive: true, force: true });
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
108
84
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
fs.renameSync(tmp, filePath);
|
|
85
|
+
function writeFileSafe(filePath, data) {
|
|
86
|
+
const dir = path.dirname(filePath);
|
|
87
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
88
|
+
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
89
|
+
fs.writeFileSync(tmp, data);
|
|
90
|
+
fs.renameSync(tmp, filePath);
|
|
116
91
|
}
|
|
117
|
-
|
|
92
|
+
export {
|
|
93
|
+
copyDirAsync,
|
|
94
|
+
copyDirSync,
|
|
95
|
+
copyFileAsync,
|
|
96
|
+
emptyDirSync,
|
|
97
|
+
getDirSize,
|
|
98
|
+
hashFileAsync,
|
|
99
|
+
hashFileSync,
|
|
100
|
+
rmSafe,
|
|
101
|
+
writeFileSafe
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=fsx.js.map
|
package/dist/fsx.js.map
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"version": 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 fs from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\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 bedrock_server.exe ~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"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["path"]
|
|
7
|
+
}
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;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,wBAAsB,WAAW,CAC/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,wBAAsB,WAAW,CAAC,CAAC,GAAG,OAAO,EAC3C,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAGtF;AAED,4CAA4C;AAC5C,wBAAsB,qBAAqB,CAAC,CAAC,GAAG,OAAO,EACrD,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,SAAS,GACjB,OAAO,CAAC,CAAC,CAAC,CAcZ;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1D;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;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,wBAAsB,WAAW,CAC/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,wBAAsB,WAAW,CAAC,CAAC,GAAG,OAAO,EAC3C,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAGtF;AAED,4CAA4C;AAC5C,wBAAsB,qBAAqB,CAAC,CAAC,GAAG,OAAO,EACrD,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,SAAS,GACjB,OAAO,CAAC,CAAC,CAAC,CAcZ;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1D;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,MAAM,CAAC,CA4GjB"}
|
package/dist/http.js
CHANGED
|
@@ -1,203 +1,184 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* http.ts — 稳定 HTTP 工具 (分阶段超时 + 自动重定向 + 流式下载)
|
|
3
|
-
*
|
|
4
|
-
* 改进:
|
|
5
|
-
* - connect timeout vs total timeout 分开
|
|
6
|
-
* - 下载通过流管道,避免大文件加载到内存
|
|
7
|
-
* - 失败后已写入的临时文件可被清理
|
|
8
|
-
*/
|
|
9
1
|
import http from "node:http";
|
|
10
2
|
import https from "node:https";
|
|
11
3
|
import { pipeline } from "node:stream/promises";
|
|
12
4
|
import { createWriteStream, statSync } from "node:fs";
|
|
13
5
|
import { log } from "./log.js";
|
|
14
6
|
const MAX_REDIRECTS = 5;
|
|
15
|
-
const DEFAULT_CONNECT_TIMEOUT =
|
|
16
|
-
const DEFAULT_TOTAL_TIMEOUT =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
resolve({ statusCode: status, body, headers: res.headers });
|
|
52
|
-
});
|
|
7
|
+
const DEFAULT_CONNECT_TIMEOUT = 15e3;
|
|
8
|
+
const DEFAULT_TOTAL_TIMEOUT = 6e5;
|
|
9
|
+
async function httpRequest(url, opts = {}) {
|
|
10
|
+
const redirects = opts.redirects ?? 0;
|
|
11
|
+
const totalTimeoutMs = opts.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT;
|
|
12
|
+
const connectTimeoutMs = opts.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT;
|
|
13
|
+
const u = new URL(url);
|
|
14
|
+
const isHttps = u.protocol === "https:";
|
|
15
|
+
const mod = isHttps ? https : http;
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const req = mod.request(
|
|
18
|
+
{
|
|
19
|
+
hostname: u.hostname,
|
|
20
|
+
port: u.port || (isHttps ? 443 : 80),
|
|
21
|
+
path: u.pathname + u.search,
|
|
22
|
+
method: opts.method ?? "GET",
|
|
23
|
+
headers: { "User-Agent": "BDSUpdater/2.0", ...opts.headers ?? {} }
|
|
24
|
+
},
|
|
25
|
+
(res) => {
|
|
26
|
+
const chunks = [];
|
|
27
|
+
res.on("data", (c) => chunks.push(c));
|
|
28
|
+
res.on("end", () => {
|
|
29
|
+
const body = Buffer.concat(chunks);
|
|
30
|
+
const status = res.statusCode ?? 0;
|
|
31
|
+
if (status >= 300 && status < 400 && redirects < MAX_REDIRECTS) {
|
|
32
|
+
const loc = res.headers.location;
|
|
33
|
+
if (!loc) return reject(new Error(`HTTP ${status} \u4F46\u7F3A\u5C11 Location`));
|
|
34
|
+
const next = loc.startsWith("http") ? loc : new URL(loc, url).href;
|
|
35
|
+
httpRequest(next, { ...opts, redirects: redirects + 1 }).then(resolve).catch(reject);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (status >= 400) {
|
|
39
|
+
return reject(new Error(`HTTP ${status} for ${url}`));
|
|
40
|
+
}
|
|
41
|
+
resolve({ statusCode: status, body, headers: res.headers });
|
|
53
42
|
});
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
req.write(opts.body);
|
|
68
|
-
req.end();
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
const totalTimer = setTimeout(() => {
|
|
46
|
+
req.destroy(new Error(`HTTP \u603B\u8D85\u65F6 ${totalTimeoutMs}ms: ${url}`));
|
|
47
|
+
}, totalTimeoutMs);
|
|
48
|
+
const connectTimer = setTimeout(() => {
|
|
49
|
+
req.destroy(new Error(`HTTP \u8FDE\u63A5\u8D85\u65F6 ${connectTimeoutMs}ms: ${url}`));
|
|
50
|
+
}, connectTimeoutMs);
|
|
51
|
+
req.on("socket", () => clearTimeout(connectTimer));
|
|
52
|
+
req.on("error", (err) => {
|
|
53
|
+
clearTimeout(totalTimer);
|
|
54
|
+
clearTimeout(connectTimer);
|
|
55
|
+
reject(err);
|
|
69
56
|
});
|
|
57
|
+
if (opts.body) req.write(opts.body);
|
|
58
|
+
req.end();
|
|
59
|
+
});
|
|
70
60
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
61
|
+
async function httpGetJson(url, opts = {}) {
|
|
62
|
+
const res = await httpRequest(url, opts);
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(res.body.toString("utf-8"));
|
|
65
|
+
} catch (e) {
|
|
66
|
+
throw new Error(`JSON \u89E3\u6790\u5931\u8D25 ${url}: ${e.message}`);
|
|
67
|
+
}
|
|
79
68
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
async function httpGetText(url, opts = {}) {
|
|
70
|
+
const res = await httpRequest(url, opts);
|
|
71
|
+
return res.body.toString("utf-8");
|
|
83
72
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const errors = settled
|
|
96
|
-
.filter((r) => r.status === "rejected")
|
|
97
|
-
.map((r) => r.reason?.message ?? "unknown")
|
|
98
|
-
.join("; ");
|
|
99
|
-
throw new Error(`所有源均不可用: ${errors}`);
|
|
73
|
+
async function fetchJsonWithFallback(sources, timeoutMs = 15e3) {
|
|
74
|
+
const tasks = sources.map(async (url) => {
|
|
75
|
+
const res = await httpGetJson(url, { totalTimeoutMs: timeoutMs });
|
|
76
|
+
return { url, value: res };
|
|
77
|
+
});
|
|
78
|
+
const settled = await Promise.allSettled(tasks);
|
|
79
|
+
for (const r of settled) {
|
|
80
|
+
if (r.status === "fulfilled") return r.value.value;
|
|
81
|
+
}
|
|
82
|
+
const errors = settled.filter((r) => r.status === "rejected").map((r) => r.reason?.message ?? "unknown").join("; ");
|
|
83
|
+
throw new Error(`\u6240\u6709\u6E90\u5747\u4E0D\u53EF\u7528: ${errors}`);
|
|
100
84
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
85
|
+
async function httpDownload(url, destPath, opts = {}) {
|
|
86
|
+
const totalTimeoutMs = opts.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT;
|
|
87
|
+
const connectTimeoutMs = opts.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT;
|
|
88
|
+
const u = new URL(url);
|
|
89
|
+
const isHttps = u.protocol === "https:";
|
|
90
|
+
const mod = isHttps ? https : http;
|
|
91
|
+
let redirects = opts.redirects ?? 0;
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const req = mod.request(
|
|
94
|
+
{
|
|
95
|
+
hostname: u.hostname,
|
|
96
|
+
port: u.port || (isHttps ? 443 : 80),
|
|
97
|
+
path: u.pathname + u.search,
|
|
98
|
+
method: "GET",
|
|
99
|
+
headers: { "User-Agent": "BDSUpdater/2.0", ...opts.headers ?? {} }
|
|
100
|
+
},
|
|
101
|
+
(res) => {
|
|
102
|
+
const status = res.statusCode ?? 0;
|
|
103
|
+
if (status >= 300 && status < 400 && redirects < MAX_REDIRECTS) {
|
|
104
|
+
const loc = res.headers.location;
|
|
105
|
+
if (!loc) return reject(new Error(`HTTP ${status} \u7F3A\u5C11 Location`));
|
|
106
|
+
redirects++;
|
|
107
|
+
const next = loc.startsWith("http") ? loc : new URL(loc, url).href;
|
|
108
|
+
res.resume();
|
|
109
|
+
httpDownload(next, destPath, { ...opts, redirects }).then(resolve).catch(reject);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (status >= 400) return reject(new Error(`HTTP ${status} for ${url}`));
|
|
113
|
+
const total = parseInt(String(res.headers["content-length"] ?? 0), 10) || 0;
|
|
114
|
+
const file = createWriteStream(destPath);
|
|
115
|
+
let downloaded = 0;
|
|
116
|
+
let failed = false;
|
|
117
|
+
let lastProgressAt = 0;
|
|
118
|
+
const PROGRESS_INTERVAL_MS = 100;
|
|
119
|
+
const handleError = (e) => {
|
|
120
|
+
if (failed) return;
|
|
121
|
+
failed = true;
|
|
122
|
+
file.close();
|
|
123
|
+
import("node:fs").then((fs) => {
|
|
124
|
+
try {
|
|
125
|
+
fs.unlinkSync(destPath);
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
reject(e);
|
|
130
|
+
};
|
|
131
|
+
const stream = res;
|
|
132
|
+
stream.on("data", (chunk) => {
|
|
133
|
+
downloaded += chunk.length;
|
|
134
|
+
if (opts.onProgress) {
|
|
135
|
+
const now = Date.now();
|
|
136
|
+
if (now - lastProgressAt >= PROGRESS_INTERVAL_MS) {
|
|
137
|
+
lastProgressAt = now;
|
|
138
|
+
opts.onProgress(downloaded, total);
|
|
128
139
|
}
|
|
129
|
-
|
|
130
|
-
return reject(new Error(`HTTP ${status} for ${url}`));
|
|
131
|
-
const total = parseInt(String(res.headers["content-length"] ?? 0), 10) || 0;
|
|
132
|
-
const file = createWriteStream(destPath);
|
|
133
|
-
let downloaded = 0;
|
|
134
|
-
let failed = false;
|
|
135
|
-
// 节流进度回调:每个 tick 最多 10 次/秒(100ms 间隔),
|
|
136
|
-
// 避免 cli-progress 频繁重绘拖慢下载
|
|
137
|
-
let lastProgressAt = 0;
|
|
138
|
-
const PROGRESS_INTERVAL_MS = 100;
|
|
139
|
-
const handleError = (e) => {
|
|
140
|
-
if (failed)
|
|
141
|
-
return;
|
|
142
|
-
failed = true;
|
|
143
|
-
file.close();
|
|
144
|
-
// 清理 partial 文件
|
|
145
|
-
import("node:fs").then((fs) => {
|
|
146
|
-
try {
|
|
147
|
-
fs.unlinkSync(destPath);
|
|
148
|
-
}
|
|
149
|
-
catch { }
|
|
150
|
-
});
|
|
151
|
-
reject(e);
|
|
152
|
-
};
|
|
153
|
-
const stream = res;
|
|
154
|
-
stream.on("data", (chunk) => {
|
|
155
|
-
downloaded += chunk.length;
|
|
156
|
-
if (opts.onProgress && total) {
|
|
157
|
-
const now = Date.now();
|
|
158
|
-
if (now - lastProgressAt >= PROGRESS_INTERVAL_MS) {
|
|
159
|
-
lastProgressAt = now;
|
|
160
|
-
opts.onProgress(downloaded, total);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
stream.on("error", (e) => handleError(e));
|
|
165
|
-
file.on("error", (e) => handleError(e));
|
|
166
|
-
pipeline(stream, file).catch((e) => {
|
|
167
|
-
if (!failed)
|
|
168
|
-
handleError(e);
|
|
169
|
-
});
|
|
170
|
-
// 不能直接监听 file 'finish' 因为我们在错误时手动 handleError
|
|
171
|
-
file.on("finish", () => {
|
|
172
|
-
if (failed)
|
|
173
|
-
return;
|
|
174
|
-
try {
|
|
175
|
-
const finalBytes = statSync(destPath).size;
|
|
176
|
-
// 收尾:确保最后一次回调让进度条走到 100%(即便最近
|
|
177
|
-
// 一个 tick 的节流没触发,也补一次)
|
|
178
|
-
if (opts.onProgress && total)
|
|
179
|
-
opts.onProgress(finalBytes, total);
|
|
180
|
-
resolve(finalBytes);
|
|
181
|
-
}
|
|
182
|
-
catch (e) {
|
|
183
|
-
reject(e);
|
|
184
|
-
}
|
|
185
|
-
});
|
|
140
|
+
}
|
|
186
141
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
142
|
+
stream.on("error", (e) => handleError(e));
|
|
143
|
+
file.on("error", (e) => handleError(e));
|
|
144
|
+
pipeline(stream, file).catch((e) => {
|
|
145
|
+
if (!failed) handleError(e);
|
|
146
|
+
});
|
|
147
|
+
file.on("finish", () => {
|
|
148
|
+
if (failed) return;
|
|
149
|
+
try {
|
|
150
|
+
const finalBytes = statSync(destPath).size;
|
|
151
|
+
if (opts.onProgress) {
|
|
152
|
+
opts.onProgress(finalBytes, total > 0 ? total : finalBytes);
|
|
153
|
+
}
|
|
154
|
+
resolve(finalBytes);
|
|
155
|
+
} catch (e) {
|
|
198
156
|
reject(e);
|
|
157
|
+
}
|
|
199
158
|
});
|
|
200
|
-
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
const totalTimer = setTimeout(() => {
|
|
162
|
+
req.destroy(new Error(`\u4E0B\u8F7D\u603B\u8D85\u65F6 ${totalTimeoutMs}ms: ${url}`));
|
|
163
|
+
}, totalTimeoutMs);
|
|
164
|
+
const connectTimer = setTimeout(() => {
|
|
165
|
+
req.destroy(new Error(`\u4E0B\u8F7D\u8FDE\u63A5\u8D85\u65F6 ${connectTimeoutMs}ms: ${url}`));
|
|
166
|
+
}, connectTimeoutMs);
|
|
167
|
+
req.on("socket", () => clearTimeout(connectTimer));
|
|
168
|
+
req.on("error", (e) => {
|
|
169
|
+
clearTimeout(totalTimer);
|
|
170
|
+
clearTimeout(connectTimer);
|
|
171
|
+
log.warn(`HTTP \u9519\u8BEF ${url}: ${e.message}`);
|
|
172
|
+
reject(e);
|
|
201
173
|
});
|
|
174
|
+
req.end();
|
|
175
|
+
});
|
|
202
176
|
}
|
|
203
|
-
|
|
177
|
+
export {
|
|
178
|
+
fetchJsonWithFallback,
|
|
179
|
+
httpDownload,
|
|
180
|
+
httpGetJson,
|
|
181
|
+
httpGetText,
|
|
182
|
+
httpRequest
|
|
183
|
+
};
|
|
184
|
+
//# sourceMappingURL=http.js.map
|