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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/LICENSE +663 -0
  2. package/dist/bds-manager.d.ts +16 -6
  3. package/dist/bds-manager.d.ts.map +1 -1
  4. package/dist/bds-manager.js +40 -65
  5. package/dist/bds-manager.js.map +2 -2
  6. package/dist/check-update.d.ts.map +1 -1
  7. package/dist/check-update.js +15 -17
  8. package/dist/check-update.js.map +2 -2
  9. package/dist/cli-pack-manager.js +7 -3
  10. package/dist/cli-pack-manager.js.map +2 -2
  11. package/dist/fsx.d.ts +6 -1
  12. package/dist/fsx.d.ts.map +1 -1
  13. package/dist/fsx.js +9 -1
  14. package/dist/fsx.js.map +2 -2
  15. package/dist/host-platform.d.ts +25 -0
  16. package/dist/host-platform.d.ts.map +1 -0
  17. package/dist/host-platform.js +50 -0
  18. package/dist/host-platform.js.map +7 -0
  19. package/dist/http.d.ts +49 -11
  20. package/dist/http.d.ts.map +1 -1
  21. package/dist/http.js.map +2 -2
  22. package/dist/log.d.ts +2 -3
  23. package/dist/log.d.ts.map +1 -1
  24. package/dist/log.js +0 -1
  25. package/dist/log.js.map +2 -2
  26. package/dist/pack-manager-lifecycle.test.js +252 -0
  27. package/dist/pack-manager-lifecycle.test.js.map +7 -0
  28. package/dist/pack-manager.d.ts +24 -29
  29. package/dist/pack-manager.d.ts.map +1 -1
  30. package/dist/pack-manager.js +11 -4
  31. package/dist/pack-manager.js.map +2 -2
  32. package/dist/pack-update/bindings.d.ts +16 -0
  33. package/dist/pack-update/bindings.d.ts.map +1 -0
  34. package/dist/pack-update/bindings.js +69 -0
  35. package/dist/pack-update/bindings.js.map +7 -0
  36. package/dist/pack-update/config.d.ts +16 -0
  37. package/dist/pack-update/config.d.ts.map +1 -0
  38. package/dist/pack-update/config.js +146 -0
  39. package/dist/pack-update/config.js.map +7 -0
  40. package/dist/pack-update/config.test.js +75 -0
  41. package/dist/pack-update/config.test.js.map +7 -0
  42. package/dist/pack-update/deps.d.ts +28 -0
  43. package/dist/pack-update/deps.d.ts.map +1 -0
  44. package/dist/pack-update/deps.js +20 -0
  45. package/dist/pack-update/deps.js.map +7 -0
  46. package/dist/pack-update/index.d.ts +55 -0
  47. package/dist/pack-update/index.d.ts.map +1 -0
  48. package/dist/pack-update/index.js +72 -0
  49. package/dist/pack-update/index.js.map +7 -0
  50. package/dist/pack-update/providers/curseforge.d.ts +23 -0
  51. package/dist/pack-update/providers/curseforge.d.ts.map +1 -0
  52. package/dist/pack-update/providers/curseforge.js +229 -0
  53. package/dist/pack-update/providers/curseforge.js.map +7 -0
  54. package/dist/pack-update/providers/index.d.ts +16 -0
  55. package/dist/pack-update/providers/index.d.ts.map +1 -0
  56. package/dist/pack-update/providers/index.js +37 -0
  57. package/dist/pack-update/providers/index.js.map +7 -0
  58. package/dist/pack-update/service.d.ts +28 -0
  59. package/dist/pack-update/service.d.ts.map +1 -0
  60. package/dist/pack-update/service.js +570 -0
  61. package/dist/pack-update/service.js.map +7 -0
  62. package/dist/pack-update/types.d.ts +126 -0
  63. package/dist/pack-update/types.d.ts.map +1 -0
  64. package/dist/pack-update/types.js +8 -0
  65. package/dist/pack-update/types.js.map +7 -0
  66. package/dist/pack-update/version-policy.d.ts +81 -0
  67. package/dist/pack-update/version-policy.d.ts.map +1 -0
  68. package/dist/pack-update/version-policy.js +210 -0
  69. package/dist/pack-update/version-policy.js.map +7 -0
  70. package/dist/pack-update/version-policy.test.js +137 -0
  71. package/dist/pack-update/version-policy.test.js.map +7 -0
  72. package/dist/paths.d.ts +1 -1
  73. package/dist/paths.d.ts.map +1 -1
  74. package/dist/paths.js +3 -5
  75. package/dist/paths.js.map +2 -2
  76. package/dist/process-probe.d.ts +34 -0
  77. package/dist/process-probe.d.ts.map +1 -0
  78. package/dist/process-probe.js +152 -0
  79. package/dist/process-probe.js.map +7 -0
  80. package/dist/process-probe.test.js +76 -0
  81. package/dist/process-probe.test.js.map +7 -0
  82. package/dist/qq-events-notify.d.ts +16 -0
  83. package/dist/qq-events-notify.d.ts.map +1 -0
  84. package/dist/qq-events-notify.js +70 -0
  85. package/dist/qq-events-notify.js.map +7 -0
  86. package/dist/qqutil.d.ts +10 -5
  87. package/dist/qqutil.d.ts.map +1 -1
  88. package/dist/qqutil.js +49 -8
  89. package/dist/qqutil.js.map +2 -2
  90. package/dist/update-result.d.ts +8 -2
  91. package/dist/update-result.d.ts.map +1 -1
  92. package/dist/update-result.js +7 -5
  93. package/dist/update-result.js.map +2 -2
  94. package/dist/upstream.d.ts +18 -9
  95. package/dist/upstream.d.ts.map +1 -1
  96. package/dist/upstream.js +8 -7
  97. package/dist/upstream.js.map +2 -2
  98. package/dist/version.js +2 -2
  99. package/dist/version.js.map +2 -2
  100. package/dist/world-packs.d.ts +158 -5
  101. package/dist/world-packs.d.ts.map +1 -1
  102. package/dist/world-packs.js +294 -48
  103. package/dist/world-packs.js.map +2 -2
  104. package/dist/world-packs.test.js +676 -0
  105. package/dist/world-packs.test.js.map +7 -0
  106. package/dist/zipx.test.js +36 -0
  107. package/dist/zipx.test.js.map +7 -0
  108. package/package.json +43 -39
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/cli-pack-manager.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n/**\n * pack-manager CLI \u2014 thin wrapper around the pack-manager pure functions\n * so spawnService can call it as a sub-process.\n *\n * node bds-tools/dist/pack-manager.js assemble-bp --src <dir> --out <dir> --name <name> [--uuid <uuid>] [--module-uuid <uuid>] [--version 1,0,0] [--description \"...\"] [--icon <png>]\n * node bds-tools/dist/pack-manager.js assemble-rp (--modules-dir <dir>|--modules-json <file>) --out <dir> --name <name> [--uuid <uuid>] [--module-uuid <uuid>] [--version 1,0,0] [--description \"...\"]\n * node bds-tools/dist/pack-manager.js deploy --bds-root <dir> --level <name> --bp-src <dir> [--rp-src <dir>] --bp-name <name> [--rp-name <name>] [--clear-rp]\n * node bds-tools/dist/pack-manager.js enable-pack --worlds-dir <dir> --level <name> --kind behavior|resource --pack-id <uuid> --version 1,0,0\n * node bds-tools/dist/pack-manager.js disable-pack --worlds-dir <dir> --level <name> --kind behavior|resource --pack-id <uuid>\n * node bds-tools/dist/pack-manager.js ensure-permission --bds-root <dir> --pack-id <uuid>\n * node bds-tools/dist/pack-manager.js read-level --bds-root <dir>\n * node bds-tools/dist/pack-manager.js read-manifest --pack-dir <dir>\n * node bds-tools/dist/pack-manager.js has-pack --worlds-dir <dir> --level <name> --kind behavior|resource --pack-id <uuid>\n * node bds-tools/dist/pack-manager.js list-packs --worlds-dir <dir> --level <name> --kind behavior|resource\n *\n * The pure-function API lives in pack-manager.ts. This CLI exists so a\n * spawnService child process doesn't have to deal with module resolution \u2014\n * it just gets SFMC_ROOT injected, finds its own dist sibling,\n * and reads JSON flags from argv.\n */\n\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { readLevelName } from \"./pack-manager.js\";\n\nfunction die(msg: string, code = 1): never {\n process.stderr.write(`[pack-manager] ${msg}\\n`);\n process.exit(code);\n}\n\nfunction parseArgs(argv: string[]): { [k: string]: string | undefined } {\n const out: { [k: string]: string | undefined } = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a && a.startsWith(\"--\")) {\n const key = a.slice(2);\n const next = argv[++i];\n /* \u5E03\u5C14\u65D7\u6807(\u65E0\u503C\u6216\u4E0B\u4E00\u53C2\u4E5F\u662F --xxx) */\n if (next === undefined || next.startsWith(\"--\")) {\n out[key] = \"1\";\n if (next !== undefined) i--;\n } else {\n out[key] = next;\n }\n }\n }\n return out;\n}\n\nfunction need(args: { [k: string]: string | undefined }, key: string): string {\n const v = args[key];\n if (!v) die(`--${key} required`);\n return v;\n}\n\nfunction parseVersion(s: string | undefined): [number, number, number] {\n if (!s) return [1, 0, 0];\n const parts = s.split(\",\").map((x) => Number(x.trim()));\n if (parts.length !== 3 || parts.some((n) => !Number.isInteger(n) || (n as number) < 0)) {\n die(`invalid --version (expected \"M,m,p\"): ${s}`);\n }\n return parts as [number, number, number];\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n if (argv.length === 0) {\n die(\"usage: pack-manager <verb> [--flags]\");\n }\n const verb = argv[0];\n const args = parseArgs(argv.slice(1));\n\n const mod = await import(\"./pack-manager.js\");\n\n switch (verb) {\n case \"assemble-bp\": {\n const src = need(args, \"src\");\n const out = need(args, \"out\");\n const name = need(args, \"name\");\n const icon = args[\"icon\"];\n await mod.assembleBehaviorPack({\n srcDir: path.resolve(src),\n outDir: path.resolve(out),\n projectName: name,\n version: parseVersion(args[\"version\"]),\n description: args[\"description\"],\n ...(icon ? { iconSrc: path.resolve(icon) } : {}),\n ...(args[\"uuid\"] ? { uuid: args[\"uuid\"] } : {}),\n ...(args[\"module-uuid\"] ? { moduleUuid: args[\"module-uuid\"] } : {}),\n });\n process.stdout.write(`[pack-manager] assembled BP at ${out}\\n`);\n return;\n }\n case \"assemble-rp\": {\n const out = need(args, \"out\");\n const name = need(args, \"name\");\n /* OCP:\u663E\u5F0F map \u4F18\u5148;\u5426\u5219\u56DE\u9000\u626B\u63CF --modules-dir */\n let map: Record<string, string>;\n if (args[\"modules-json\"]) {\n map = mod.loadModuleResourcePackMap(path.resolve(args[\"modules-json\"]));\n } else {\n const modulesDir = need(args, \"modules-dir\");\n map = mod.scanModuleResourcePacks(path.resolve(modulesDir));\n }\n await mod.assembleResourcePack({\n moduleResourceDirs: map,\n outDir: path.resolve(out),\n projectName: name,\n version: parseVersion(args[\"version\"]),\n description: args[\"description\"],\n ...(args[\"uuid\"] ? { uuid: args[\"uuid\"] } : {}),\n ...(args[\"module-uuid\"] ? { moduleUuid: args[\"module-uuid\"] } : {}),\n });\n process.stdout.write(`[pack-manager] assembled RP at ${out} (${Object.keys(map).length} modules)\\n`);\n return;\n }\n case \"deploy\": {\n const bdsRoot = need(args, \"bds-root\");\n const level = need(args, \"level\");\n const bpSrc = need(args, \"bp-src\");\n const bpName = need(args, \"bp-name\");\n const rpSrc = args[\"rp-src\"];\n const rpName = args[\"rp-name\"];\n const clearRp = args[\"clear-rp\"] === \"1\" || args[\"clear-rp\"] === \"true\";\n await mod.deployToBDS({\n bdsRoot: path.resolve(bdsRoot),\n levelName: level,\n behaviorPackSrc: path.resolve(bpSrc),\n ...(rpSrc ? { resourcePackSrc: path.resolve(rpSrc) } : {}),\n bpName,\n ...(rpName ? { rpName } : {}),\n ...(clearRp ? { clearResourcePack: true } : {}),\n });\n process.stdout.write(`[pack-manager] deployed to ${mod.bdsWorldLevelDir(path.resolve(bdsRoot), level)}\\n`);\n return;\n }\n case \"enable-pack\": {\n const worldsDir = need(args, \"worlds-dir\");\n const level = need(args, \"level\");\n const kind = need(args, \"kind\") as \"behavior\" | \"resource\";\n const packId = need(args, \"pack-id\");\n await mod.enablePackInWorld({\n worldsDir: path.resolve(worldsDir),\n levelName: level,\n kind,\n packUuid: packId,\n version: parseVersion(args[\"version\"]),\n });\n process.stdout.write(`[pack-manager] enabled ${kind} pack ${packId} in ${level}\\n`);\n return;\n }\n case \"disable-pack\": {\n const worldsDir = need(args, \"worlds-dir\");\n const level = need(args, \"level\");\n const kind = need(args, \"kind\") as \"behavior\" | \"resource\";\n const packId = need(args, \"pack-id\");\n await mod.disablePackInWorld({\n worldsDir: path.resolve(worldsDir),\n levelName: level,\n kind,\n packUuid: packId,\n version: parseVersion(args[\"version\"] ?? \"1,0,0\"),\n });\n process.stdout.write(`[pack-manager] disabled ${kind} pack ${packId} in ${level}\\n`);\n return;\n }\n case \"ensure-permission\": {\n const bdsRoot = need(args, \"bds-root\");\n const packId = need(args, \"pack-id\");\n const root = path.resolve(bdsRoot);\n const wrote = await mod.ensureConfigPermission(root, packId);\n const rel = path.relative(root, mod.configPermissionPath(root, packId));\n process.stdout.write(\n wrote ? `[pack-manager] wrote ${rel}\\n` : `[pack-manager] ${rel} already exists \u2014 skipped\\n`\n );\n return;\n }\n case \"read-level\": {\n const root = need(args, \"bds-root\");\n const name = await readLevelName(path.resolve(root));\n process.stdout.write(`${name}\\n`);\n return;\n }\n case \"read-manifest\": {\n const packDir = need(args, \"pack-dir\");\n const header = mod.readPackManifestHeader(path.resolve(packDir));\n if (!header) {\n process.stdout.write(\"null\\n\");\n return;\n }\n process.stdout.write(`${JSON.stringify(header)}\\n`);\n return;\n }\n case \"has-pack\": {\n const worldsDir = need(args, \"worlds-dir\");\n const level = need(args, \"level\");\n const kind = need(args, \"kind\") as \"behavior\" | \"resource\";\n const packId = need(args, \"pack-id\");\n const ok = mod.worldPackListHas(path.resolve(worldsDir), level, kind, packId);\n process.stdout.write(ok ? \"1\\n\" : \"0\\n\");\n return;\n }\n case \"list-packs\": {\n /* \u6743\u5A01\u4E16\u754C\u6E05\u5355\u8BFB\u53D6 \u2014 \u8C03\u7528\u65B9\u52FF\u518D\u76F4\u63A5\u89E3\u6790 world_*_packs.json(Demeter/DRY) */\n const worldsDir = need(args, \"worlds-dir\");\n const level = need(args, \"level\");\n const kind = need(args, \"kind\") as \"behavior\" | \"resource\";\n const list = mod.readWorldPackList(path.resolve(worldsDir), level, kind);\n process.stdout.write(`${JSON.stringify(list)}\\n`);\n return;\n }\n case \"list-installed\":\n case \"bump-version\":\n case \"install-dir\":\n case \"discover-packs\": {\n /* OCP: world-packs \u52A8\u8BCD\u8868\u6269\u5C55\uFF0C\u907F\u514D\u6BCF case \u91CD\u590D dynamic import\uFF08DRY\uFF09 */\n const wp = await import(\"./world-packs.js\");\n if (verb === \"list-installed\") {\n const bdsRoot = need(args, \"bds-root\");\n const level = need(args, \"level\");\n const list = wp.listInstalledWorldPacks(path.resolve(bdsRoot), level);\n process.stdout.write(`${JSON.stringify(list)}\\n`);\n return;\n }\n if (verb === \"bump-version\") {\n const packDir = need(args, \"pack-dir\");\n const next = wp.bumpPackPatchVersion(path.resolve(packDir));\n process.stdout.write(`${JSON.stringify({ version: next })}\\n`);\n return;\n }\n if (verb === \"install-dir\") {\n const src = need(args, \"src\");\n const destParent = need(args, \"dest-parent\");\n const force = !!args[\"force\"];\n const r = await wp.installPackDirectory({\n srcDir: path.resolve(src),\n destParent: path.resolve(destParent),\n force,\n ...(args[\"folder-name\"] ? { folderName: args[\"folder-name\"] } : {}),\n });\n process.stdout.write(`${JSON.stringify(r)}\\n`);\n if (!r.ok && !r.conflict) process.exit(1);\n return;\n }\n {\n const root = need(args, \"root\");\n const depth = args[\"max-depth\"] ? Number(args[\"max-depth\"]) : 2;\n const roots = wp.discoverPackRoots(path.resolve(root), { maxDepth: depth });\n process.stdout.write(`${JSON.stringify(roots)}\\n`);\n return;\n }\n }\n default:\n die(`unknown verb: ${verb}`);\n }\n}\n\nmain().catch((err: unknown) => {\n const msg = err instanceof Error ? (err.stack ?? err.message) : String(err);\n die(msg);\n});\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["#!/usr/bin/env node\n/**\n * pack-manager CLI \u2014 thin wrapper around the pack-manager pure functions\n * so spawnService can call it as a sub-process.\n *\n * node bds-tools/dist/pack-manager.js assemble-bp --src <dir> --out <dir> --name <name> [--uuid <uuid>] [--module-uuid <uuid>] [--version 1,0,0] [--description \"...\"] [--icon <png>]\n * node bds-tools/dist/pack-manager.js assemble-rp (--modules-dir <dir>|--modules-json <file>) --out <dir> --name <name> [--uuid <uuid>] [--module-uuid <uuid>] [--version 1,0,0] [--description \"...\"]\n * node bds-tools/dist/pack-manager.js deploy --bds-root <dir> --level <name> --bp-src <dir> [--rp-src <dir>] --bp-name <name> [--rp-name <name>] [--clear-rp]\n * node bds-tools/dist/pack-manager.js enable-pack --worlds-dir <dir> --level <name> --kind behavior|resource --pack-id <uuid> --version 1,0,0\n * node bds-tools/dist/pack-manager.js disable-pack --worlds-dir <dir> --level <name> --kind behavior|resource --pack-id <uuid>\n * node bds-tools/dist/pack-manager.js ensure-permission --bds-root <dir> --pack-id <uuid>\n * node bds-tools/dist/pack-manager.js read-level --bds-root <dir>\n * node bds-tools/dist/pack-manager.js read-manifest --pack-dir <dir>\n * node bds-tools/dist/pack-manager.js has-pack --worlds-dir <dir> --level <name> --kind behavior|resource --pack-id <uuid>\n * node bds-tools/dist/pack-manager.js list-packs --worlds-dir <dir> --level <name> --kind behavior|resource\n *\n * The pure-function API lives in pack-manager.ts. This CLI exists so a\n * spawnService child process doesn't have to deal with module resolution \u2014\n * it just gets SFMC_ROOT injected, finds its own dist sibling,\n * and reads JSON flags from argv.\n */\n\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { readLevelName } from \"./pack-manager.js\";\n\nfunction die(msg: string, code = 1): never {\n process.stderr.write(`[pack-manager] ${msg}\\n`);\n process.exit(code);\n}\n\nfunction parseArgs(argv: string[]): { [k: string]: string | undefined } {\n const out: { [k: string]: string | undefined } = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a && a.startsWith(\"--\")) {\n const key = a.slice(2);\n const next = argv[++i];\n /* \u5E03\u5C14\u65D7\u6807(\u65E0\u503C\u6216\u4E0B\u4E00\u53C2\u4E5F\u662F --xxx) */\n if (next === undefined || next.startsWith(\"--\")) {\n out[key] = \"1\";\n if (next !== undefined) i--;\n } else {\n out[key] = next;\n }\n }\n }\n return out;\n}\n\nfunction need(args: { [k: string]: string | undefined }, key: string): string {\n const v = args[key];\n if (!v) die(`--${key} required`);\n return v;\n}\n\nfunction parseVersion(s: string | undefined): [number, number, number] {\n if (!s) return [1, 0, 0];\n const parts = s.split(\",\").map((x) => Number(x.trim()));\n if (parts.length !== 3 || parts.some((n) => !Number.isInteger(n) || (n as number) < 0)) {\n die(`invalid --version (expected \"M,m,p\"): ${s}`);\n }\n return parts as [number, number, number];\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n if (argv.length === 0) {\n die(\"usage: pack-manager <verb> [--flags]\");\n }\n const verb = argv[0];\n const args = parseArgs(argv.slice(1));\n\n const mod = await import(\"./pack-manager.js\");\n\n switch (verb) {\n case \"assemble-bp\": {\n const src = need(args, \"src\");\n const out = need(args, \"out\");\n const name = need(args, \"name\");\n const icon = args[\"icon\"];\n await mod.assembleBehaviorPack({\n srcDir: path.resolve(src),\n outDir: path.resolve(out),\n projectName: name,\n version: parseVersion(args[\"version\"]),\n description: args[\"description\"],\n ...(icon ? { iconSrc: path.resolve(icon) } : {}),\n ...(args[\"uuid\"] ? { uuid: args[\"uuid\"] } : {}),\n ...(args[\"module-uuid\"] ? { moduleUuid: args[\"module-uuid\"] } : {}),\n });\n process.stdout.write(`[pack-manager] assembled BP at ${out}\\n`);\n return;\n }\n case \"assemble-rp\": {\n const out = need(args, \"out\");\n const name = need(args, \"name\");\n /* OCP:\u663E\u5F0F map \u4F18\u5148;\u5426\u5219\u56DE\u9000\u626B\u63CF --modules-dir */\n let map: Record<string, string>;\n if (args[\"modules-json\"]) {\n map = mod.loadModuleResourcePackMap(path.resolve(args[\"modules-json\"]));\n } else {\n const modulesDir = need(args, \"modules-dir\");\n map = mod.scanModuleResourcePacks(path.resolve(modulesDir));\n }\n await mod.assembleResourcePack({\n moduleResourceDirs: map,\n outDir: path.resolve(out),\n projectName: name,\n version: parseVersion(args[\"version\"]),\n description: args[\"description\"],\n ...(args[\"uuid\"] ? { uuid: args[\"uuid\"] } : {}),\n ...(args[\"module-uuid\"] ? { moduleUuid: args[\"module-uuid\"] } : {}),\n });\n process.stdout.write(`[pack-manager] assembled RP at ${out} (${Object.keys(map).length} modules)\\n`);\n return;\n }\n case \"deploy\": {\n const bdsRoot = need(args, \"bds-root\");\n const level = need(args, \"level\");\n const bpSrc = need(args, \"bp-src\");\n const bpName = need(args, \"bp-name\");\n const rpSrc = args[\"rp-src\"];\n const rpName = args[\"rp-name\"];\n const clearRp = args[\"clear-rp\"] === \"1\" || args[\"clear-rp\"] === \"true\";\n await mod.deployToBDS({\n bdsRoot: path.resolve(bdsRoot),\n levelName: level,\n behaviorPackSrc: path.resolve(bpSrc),\n ...(rpSrc ? { resourcePackSrc: path.resolve(rpSrc) } : {}),\n bpName,\n ...(rpName ? { rpName } : {}),\n ...(clearRp ? { clearResourcePack: true } : {}),\n });\n process.stdout.write(`[pack-manager] deployed to ${mod.bdsWorldLevelDir(path.resolve(bdsRoot), level)}\\n`);\n return;\n }\n case \"enable-pack\": {\n const worldsDir = need(args, \"worlds-dir\");\n const level = need(args, \"level\");\n const kind = need(args, \"kind\") as \"behavior\" | \"resource\";\n const packId = need(args, \"pack-id\");\n await mod.enablePackInWorld({\n worldsDir: path.resolve(worldsDir),\n levelName: level,\n kind,\n packUuid: packId,\n version: parseVersion(args[\"version\"]),\n });\n process.stdout.write(`[pack-manager] enabled ${kind} pack ${packId} in ${level}\\n`);\n return;\n }\n case \"disable-pack\": {\n const worldsDir = need(args, \"worlds-dir\");\n const level = need(args, \"level\");\n const kind = need(args, \"kind\") as \"behavior\" | \"resource\";\n const packId = need(args, \"pack-id\");\n await mod.disablePackInWorld({\n worldsDir: path.resolve(worldsDir),\n levelName: level,\n kind,\n packUuid: packId,\n version: parseVersion(args[\"version\"] ?? \"1,0,0\"),\n });\n process.stdout.write(`[pack-manager] disabled ${kind} pack ${packId} in ${level}\\n`);\n return;\n }\n case \"ensure-permission\": {\n const bdsRoot = need(args, \"bds-root\");\n const packId = need(args, \"pack-id\");\n const root = path.resolve(bdsRoot);\n const wrote = await mod.ensureConfigPermission(root, packId);\n const rel = path.relative(root, mod.configPermissionPath(root, packId));\n process.stdout.write(\n wrote ? `[pack-manager] wrote ${rel}\\n` : `[pack-manager] ${rel} already exists \u2014 skipped\\n`\n );\n return;\n }\n case \"read-level\": {\n const root = need(args, \"bds-root\");\n const name = await readLevelName(path.resolve(root));\n process.stdout.write(`${name}\\n`);\n return;\n }\n case \"read-manifest\": {\n const packDir = need(args, \"pack-dir\");\n const header = mod.readPackManifestHeader(path.resolve(packDir));\n if (!header) {\n process.stdout.write(\"null\\n\");\n return;\n }\n process.stdout.write(`${JSON.stringify(header)}\\n`);\n return;\n }\n case \"has-pack\": {\n const worldsDir = need(args, \"worlds-dir\");\n const level = need(args, \"level\");\n const kind = need(args, \"kind\") as \"behavior\" | \"resource\";\n const packId = need(args, \"pack-id\");\n const ok = mod.worldPackListHas(path.resolve(worldsDir), level, kind, packId);\n process.stdout.write(ok ? \"1\\n\" : \"0\\n\");\n return;\n }\n case \"list-packs\": {\n /* \u6743\u5A01\u4E16\u754C\u6E05\u5355\u8BFB\u53D6 \u2014 \u8C03\u7528\u65B9\u52FF\u518D\u76F4\u63A5\u89E3\u6790 world_*_packs.json(Demeter/DRY) */\n const worldsDir = need(args, \"worlds-dir\");\n const level = need(args, \"level\");\n const kind = need(args, \"kind\") as \"behavior\" | \"resource\";\n const list = mod.readWorldPackList(path.resolve(worldsDir), level, kind);\n process.stdout.write(`${JSON.stringify(list)}\\n`);\n return;\n }\n case \"list-installed\":\n case \"bump-version\":\n case \"install-dir\":\n case \"discover-packs\": {\n /* OCP: world-packs \u52A8\u8BCD\u8868\u6269\u5C55\uFF0C\u907F\u514D\u6BCF case \u91CD\u590D dynamic import\uFF08DRY\uFF09 */\n const wp = await import(\"./world-packs.js\");\n if (verb === \"list-installed\") {\n const bdsRoot = need(args, \"bds-root\");\n const level = need(args, \"level\");\n const list = wp.listInstalledWorldPacks(path.resolve(bdsRoot), level);\n process.stdout.write(`${JSON.stringify(list)}\\n`);\n return;\n }\n if (verb === \"bump-version\") {\n const packDir = need(args, \"pack-dir\");\n const next = wp.bumpPackPatchVersion(path.resolve(packDir));\n process.stdout.write(`${JSON.stringify({ version: next })}\\n`);\n return;\n }\n if (verb === \"install-dir\") {\n const src = need(args, \"src\");\n const destParent = need(args, \"dest-parent\");\n const force = !!args[\"force\"];\n const r = await wp.installPackDirectory({\n srcDir: path.resolve(src),\n destParent: path.resolve(destParent),\n force,\n ...(args[\"folder-name\"] ? { folderName: args[\"folder-name\"] } : {}),\n });\n process.stdout.write(`${JSON.stringify(r)}\\n`);\n if (!r.ok && !r.conflict) process.exit(1);\n return;\n }\n {\n const root = need(args, \"root\");\n const depth = args[\"max-depth\"] ? Number(args[\"max-depth\"]) : 4;\n const resolved = await wp.resolvePackRoots(path.resolve(root), { maxDirDepth: depth });\n try {\n process.stdout.write(`${JSON.stringify(resolved.roots)}\\n`);\n } finally {\n resolved.dispose();\n }\n return;\n }\n }\n default:\n die(`unknown verb: ${verb}`);\n }\n}\n\nmain().catch((err: unknown) => {\n const msg = err instanceof Error ? (err.stack ?? err.message) : String(err);\n die(msg);\n});\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/fsx.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  /**
5
5
  * 流式计算文件哈希 (sha1 / sha256),
6
- * 用于大文件 (例如 bedrock_server.exe ~80MB),不会 OOM。
6
+ * 用于大文件 (例如 BDS 可执行文件 ~80MB),不会 OOM。
7
7
  */
8
8
  export declare function hashFileAsync(filePath: string, algo?: "sha1" | "sha256"): Promise<string>;
9
9
  /** 同步版本(用于小文件) */
@@ -22,4 +22,9 @@ export declare function emptyDirSync(dir: string): void;
22
22
  export declare function rmSafe(path: string): void;
23
23
  /** 安全写文件 (原子: 写临时文件 → rename) */
24
24
  export declare function writeFileSafe(filePath: string, data: string | Buffer): void;
25
+ /**
26
+ * 读 JSON 文件(utf8)。
27
+ * 若文件以 UTF-8 BOM(U+FEFF)开头则剥离后再解析(记事本「UTF-8 with BOM」)。
28
+ */
29
+ export declare function readJsonFile<T = unknown>(filePath: string): T;
25
30
  //# sourceMappingURL=fsx.d.ts.map
package/dist/fsx.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"fsx.d.ts","sourceRoot":"","sources":["../src/fsx.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;;GAGG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,GAAG,QAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAUzG;AAED,kBAAkB;AAClB,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,GAAG,QAAmB,GAAG,MAAM,CAQzF;AAED,sBAAsB;AACtB,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED,kBAAkB;AAClB,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAW3D;AAED,mBAAmB;AACnB,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAc3E;AAED,kBAAkB;AAClB,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAU9C;AAED,yBAAyB;AACzB,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAY9C;AAED,eAAe;AACf,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMzC;AAED,iCAAiC;AACjC,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3E"}
1
+ {"version":3,"file":"fsx.d.ts","sourceRoot":"","sources":["../src/fsx.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;;GAGG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,GAAG,QAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAUzG;AAED,kBAAkB;AAClB,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,GAAG,QAAmB,GAAG,MAAM,CAQzF;AAED,sBAAsB;AACtB,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED,kBAAkB;AAClB,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAW3D;AAED,mBAAmB;AACnB,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAc3E;AAED,kBAAkB;AAClB,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAU9C;AAED,yBAAyB;AACzB,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAY9C;AAED,eAAe;AACf,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMzC;AAED,iCAAiC;AACjC,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,CAM7D"}
package/dist/fsx.js CHANGED
@@ -1,6 +1,6 @@
1
+ import crypto from "node:crypto";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
- import crypto from "node:crypto";
4
4
  import { pipeline } from "node:stream/promises";
5
5
  async function hashFileAsync(filePath, algo = "sha256") {
6
6
  return new Promise((resolve, reject) => {
@@ -89,6 +89,13 @@ function writeFileSafe(filePath, data) {
89
89
  fs.writeFileSync(tmp, data);
90
90
  fs.renameSync(tmp, filePath);
91
91
  }
92
+ function readJsonFile(filePath) {
93
+ let text = fs.readFileSync(filePath, "utf8");
94
+ if (text.length > 0 && text.charCodeAt(0) === 65279) {
95
+ text = text.slice(1);
96
+ }
97
+ return JSON.parse(text);
98
+ }
92
99
  export {
93
100
  copyDirAsync,
94
101
  copyDirSync,
@@ -97,6 +104,7 @@ export {
97
104
  getDirSize,
98
105
  hashFileAsync,
99
106
  hashFileSync,
107
+ readJsonFile,
100
108
  rmSafe,
101
109
  writeFileSafe
102
110
  };
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 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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",
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 — 稳定 HTTP 工具 (分阶段超时 + 自动重定向 + 流式下载)
2
+ * http.ts — 针对大文件与外部上游设计的 HTTP 网络请求工具
3
3
  *
4
- * 改进:
5
- * - connect timeout vs total timeout 分开
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
- * - 失败时自动清理 partial 文件
29
- * - 支持 progress 回调
30
- * - 支持 connect/total 阶段超时
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
@@ -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,CA4GjB"}
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": "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",
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;;;;;;GAMG;AAKH,eAAO,MAAM,GAAG,gDAGd,CAAC;AAEH,6BAA6B;AAC7B,wBAAgB,QAAQ,IAAI,IAAI,CAE/B"}
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
@@ -7,7 +7,6 @@ const log = createNodeServiceLogger({
7
7
  function closeLog() {
8
8
  log.close();
9
9
  }
10
- process.on("exit", () => closeLog());
11
10
  export {
12
11
  closeLog,
13
12
  log
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,\n * \u89C1\u8BE5\u6587\u4EF6\u5185\u7684 createUpdaterLogger()\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/** \u5173\u95ED\u6587\u4EF6\u6D41 (\u8FDB\u7A0B\u9000\u51FA\u524D\u8C03\u7528,\u786E\u4FDD\u7F13\u51B2\u843D\u76D8) */\nexport function closeLog(): void {\n log.close();\n}\n\nprocess.on(\"exit\", () => closeLog());\n"],
5
- "mappings": "AAQA,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;AAEA,QAAQ,GAAG,QAAQ,MAAM,SAAS,CAAC;",
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
  }