@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.
Files changed (71) hide show
  1. package/dist/bds-manager.d.ts.map +1 -1
  2. package/dist/bds-manager.js +236 -263
  3. package/dist/bds-manager.js.map +7 -1
  4. package/dist/changelog.js +39 -50
  5. package/dist/changelog.js.map +7 -1
  6. package/dist/check-update.d.ts +2 -2
  7. package/dist/check-update.d.ts.map +1 -1
  8. package/dist/check-update.js +361 -401
  9. package/dist/check-update.js.map +7 -1
  10. package/dist/cli-pack-manager.d.ts +10 -6
  11. package/dist/cli-pack-manager.d.ts.map +1 -1
  12. package/dist/cli-pack-manager.js +226 -135
  13. package/dist/cli-pack-manager.js.map +7 -1
  14. package/dist/fsx.js +86 -100
  15. package/dist/fsx.js.map +7 -1
  16. package/dist/http.d.ts.map +1 -1
  17. package/dist/http.js +166 -185
  18. package/dist/http.js.map +7 -1
  19. package/dist/is-main.d.ts +17 -0
  20. package/dist/is-main.d.ts.map +1 -0
  21. package/dist/is-main.js +32 -0
  22. package/dist/is-main.js.map +7 -0
  23. package/dist/log.d.ts +2 -4
  24. package/dist/log.d.ts.map +1 -1
  25. package/dist/log.js +12 -19
  26. package/dist/log.js.map +7 -1
  27. package/dist/logger.js +6 -10
  28. package/dist/logger.js.map +7 -1
  29. package/dist/pack-manager.d.ts +88 -24
  30. package/dist/pack-manager.d.ts.map +1 -1
  31. package/dist/pack-manager.js +280 -245
  32. package/dist/pack-manager.js.map +7 -1
  33. package/dist/paths.d.ts +8 -3
  34. package/dist/paths.d.ts.map +1 -1
  35. package/dist/paths.js +53 -39
  36. package/dist/paths.js.map +7 -1
  37. package/dist/qqutil.d.ts.map +1 -1
  38. package/dist/qqutil.js +90 -87
  39. package/dist/qqutil.js.map +7 -1
  40. package/dist/recovery.js +26 -37
  41. package/dist/recovery.js.map +7 -1
  42. package/dist/rollback.d.ts +1 -1
  43. package/dist/rollback.js +55 -74
  44. package/dist/rollback.js.map +7 -1
  45. package/dist/server-properties.d.ts +12 -0
  46. package/dist/server-properties.d.ts.map +1 -0
  47. package/dist/server-properties.js +28 -0
  48. package/dist/server-properties.js.map +7 -0
  49. package/dist/taskbar.d.ts +2 -0
  50. package/dist/taskbar.d.ts.map +1 -1
  51. package/dist/taskbar.js +51 -82
  52. package/dist/taskbar.js.map +7 -1
  53. package/dist/types.js +1 -5
  54. package/dist/types.js.map +7 -1
  55. package/dist/update-result.d.ts +12 -0
  56. package/dist/update-result.d.ts.map +1 -0
  57. package/dist/update-result.js +15 -0
  58. package/dist/update-result.js.map +7 -0
  59. package/dist/upstream.js +94 -108
  60. package/dist/upstream.js.map +7 -1
  61. package/dist/version.js +63 -81
  62. package/dist/version.js.map +7 -1
  63. package/dist/world-packs.d.ts +112 -0
  64. package/dist/world-packs.d.ts.map +1 -0
  65. package/dist/world-packs.js +335 -0
  66. package/dist/world-packs.js.map +7 -0
  67. package/dist/zipx.d.ts +10 -0
  68. package/dist/zipx.d.ts.map +1 -0
  69. package/dist/zipx.js +48 -0
  70. package/dist/zipx.js.map +7 -0
  71. 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
- * 流式计算文件哈希 (sha1 / sha256),
10
- * 用于大文件 (例如 bedrock_server.exe ~80MB),不会 OOM。
11
- */
12
- export async function hashFileAsync(filePath, algo = "sha256") {
13
- return new Promise((resolve, reject) => {
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
- export function hashFileSync(filePath, algo = "sha256") {
25
- try {
26
- const h = crypto.createHash(algo);
27
- h.update(fs.readFileSync(filePath));
28
- return h.digest("hex").toLowerCase();
29
- }
30
- catch {
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
- export async function copyFileAsync(src, dest) {
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
- export function copyDirSync(src, dest) {
40
- fs.mkdirSync(dest, { recursive: true });
41
- for (const entry of fs.readdirSync(src)) {
42
- const srcPath = path.join(src, entry);
43
- const destPath = path.join(dest, entry);
44
- if (fs.statSync(srcPath).isDirectory()) {
45
- copyDirSync(srcPath, destPath);
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
- export async function copyDirAsync(src, dest) {
54
- fs.mkdirSync(dest, { recursive: true });
55
- await Promise.all(fs.readdirSync(src).map(async (entry) => {
56
- const srcPath = path.join(src, entry);
57
- const destPath = path.join(dest, entry);
58
- const stat = fs.statSync(srcPath);
59
- if (stat.isDirectory()) {
60
- await copyDirAsync(srcPath, destPath);
61
- }
62
- else {
63
- await copyFileAsync(srcPath, destPath);
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
- export function emptyDirSync(dir) {
84
- if (!fs.existsSync(dir))
85
- return;
86
- for (const entry of fs.readdirSync(dir)) {
87
- const full = path.join(dir, entry);
88
- try {
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
- export function rmSafe(path) {
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
- fs.rmSync(path, { recursive: true, force: true });
104
- }
105
- catch {
106
- /* ignore */
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
- /** 安全写文件 (原子: 写临时文件 → rename) */
110
- export function writeFileSafe(filePath, data) {
111
- const dir = path.dirname(filePath);
112
- fs.mkdirSync(dir, { recursive: true });
113
- const tmp = `${filePath}.${process.pid}.tmp`;
114
- fs.writeFileSync(tmp, data);
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
- //# sourceMappingURL=fsx.js.map
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
- {"version":3,"file":"fsx.js","sourceRoot":"","sources":["../src/fsx.ts"],"names":[],"mappings":"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"}
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
+ }
@@ -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,CAwGjB"}
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 = 15_000;
16
- const DEFAULT_TOTAL_TIMEOUT = 600_000;
17
- export async function httpRequest(url, opts = {}) {
18
- const redirects = opts.redirects ?? 0;
19
- const totalTimeoutMs = opts.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT;
20
- const connectTimeoutMs = opts.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT;
21
- const u = new URL(url);
22
- const isHttps = u.protocol === "https:";
23
- const mod = isHttps ? https : http;
24
- return new Promise((resolve, reject) => {
25
- const req = mod.request({
26
- hostname: u.hostname,
27
- port: u.port || (isHttps ? 443 : 80),
28
- path: u.pathname + u.search,
29
- method: opts.method ?? "GET",
30
- headers: { "User-Agent": "BDSUpdater/2.0", ...(opts.headers ?? {}) },
31
- }, (res) => {
32
- const chunks = [];
33
- res.on("data", (c) => chunks.push(c));
34
- res.on("end", () => {
35
- const body = Buffer.concat(chunks);
36
- const status = res.statusCode ?? 0;
37
- // 3xx 跟随 Location
38
- if (status >= 300 && status < 400 && redirects < MAX_REDIRECTS) {
39
- const loc = res.headers.location;
40
- if (!loc)
41
- return reject(new Error(`HTTP ${status} 但缺少 Location`));
42
- const next = loc.startsWith("http") ? loc : new URL(loc, url).href;
43
- httpRequest(next, { ...opts, redirects: redirects + 1 })
44
- .then(resolve)
45
- .catch(reject);
46
- return;
47
- }
48
- if (status >= 400) {
49
- return reject(new Error(`HTTP ${status} for ${url}`));
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
- const totalTimer = setTimeout(() => {
55
- req.destroy(new Error(`HTTP 总超时 ${totalTimeoutMs}ms: ${url}`));
56
- }, totalTimeoutMs);
57
- const connectTimer = setTimeout(() => {
58
- req.destroy(new Error(`HTTP 连接超时 ${connectTimeoutMs}ms: ${url}`));
59
- }, connectTimeoutMs);
60
- req.on("socket", () => clearTimeout(connectTimer));
61
- req.on("error", (err) => {
62
- clearTimeout(totalTimer);
63
- clearTimeout(connectTimer);
64
- reject(err);
65
- });
66
- if (opts.body)
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
- export async function httpGetJson(url, opts = {}) {
72
- const res = await httpRequest(url, opts);
73
- try {
74
- return JSON.parse(res.body.toString("utf-8"));
75
- }
76
- catch (e) {
77
- throw new Error(`JSON 解析失败 ${url}: ${e.message}`);
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
- export async function httpGetText(url, opts = {}) {
81
- const res = await httpRequest(url, opts);
82
- return res.body.toString("utf-8");
69
+ async function httpGetText(url, opts = {}) {
70
+ const res = await httpRequest(url, opts);
71
+ return res.body.toString("utf-8");
83
72
  }
84
- /** 多个源并发请求,谁先成功返回(每个源都用 JSON 解析,解析失败也失败) */
85
- export async function fetchJsonWithFallback(sources, timeoutMs = 15_000) {
86
- const tasks = sources.map(async (url) => {
87
- const res = await httpGetJson(url, { totalTimeoutMs: timeoutMs });
88
- return { url, value: res };
89
- });
90
- const settled = await Promise.allSettled(tasks);
91
- for (const r of settled) {
92
- if (r.status === "fulfilled")
93
- return r.value.value;
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
- export async function httpDownload(url, destPath, opts = {}) {
102
- const totalTimeoutMs = opts.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT;
103
- const connectTimeoutMs = opts.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT;
104
- const u = new URL(url);
105
- const isHttps = u.protocol === "https:";
106
- const mod = isHttps ? https : http;
107
- let redirects = opts.redirects ?? 0;
108
- return new Promise((resolve, reject) => {
109
- const req = mod.request({
110
- hostname: u.hostname,
111
- port: u.port || (isHttps ? 443 : 80),
112
- path: u.pathname + u.search,
113
- method: "GET",
114
- headers: { "User-Agent": "BDSUpdater/2.0", ...(opts.headers ?? {}) },
115
- }, (res) => {
116
- const status = res.statusCode ?? 0;
117
- if (status >= 300 && status < 400 && redirects < MAX_REDIRECTS) {
118
- const loc = res.headers.location;
119
- if (!loc)
120
- return reject(new Error(`HTTP ${status} 缺少 Location`));
121
- redirects++;
122
- const next = loc.startsWith("http") ? loc : new URL(loc, url).href;
123
- res.resume();
124
- httpDownload(next, destPath, { ...opts, redirects })
125
- .then(resolve)
126
- .catch(reject);
127
- return;
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
- if (status >= 400)
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
- const totalTimer = setTimeout(() => {
188
- req.destroy(new Error(`下载总超时 ${totalTimeoutMs}ms: ${url}`));
189
- }, totalTimeoutMs);
190
- const connectTimer = setTimeout(() => {
191
- req.destroy(new Error(`下载连接超时 ${connectTimeoutMs}ms: ${url}`));
192
- }, connectTimeoutMs);
193
- req.on("socket", () => clearTimeout(connectTimer));
194
- req.on("error", (e) => {
195
- clearTimeout(totalTimer);
196
- clearTimeout(connectTimer);
197
- log.warn(`HTTP 错误 ${url}: ${e.message}`);
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
- req.end();
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
- //# sourceMappingURL=http.js.map
177
+ export {
178
+ fetchJsonWithFallback,
179
+ httpDownload,
180
+ httpGetJson,
181
+ httpGetText,
182
+ httpRequest
183
+ };
184
+ //# sourceMappingURL=http.js.map