@sfmc-bds/bds-tools 0.1.0 → 0.2.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bds-manager.d.ts.map +1 -1
- package/dist/bds-manager.js +236 -263
- package/dist/bds-manager.js.map +7 -1
- package/dist/changelog.js +39 -50
- package/dist/changelog.js.map +7 -1
- package/dist/check-update.d.ts +2 -2
- package/dist/check-update.d.ts.map +1 -1
- package/dist/check-update.js +361 -401
- package/dist/check-update.js.map +7 -1
- package/dist/cli-pack-manager.d.ts +10 -6
- package/dist/cli-pack-manager.d.ts.map +1 -1
- package/dist/cli-pack-manager.js +226 -135
- package/dist/cli-pack-manager.js.map +7 -1
- package/dist/fsx.js +86 -100
- package/dist/fsx.js.map +7 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +166 -185
- package/dist/http.js.map +7 -1
- package/dist/is-main.d.ts +17 -0
- package/dist/is-main.d.ts.map +1 -0
- package/dist/is-main.js +32 -0
- package/dist/is-main.js.map +7 -0
- package/dist/log.d.ts +2 -4
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +12 -19
- package/dist/log.js.map +7 -1
- package/dist/logger.js +6 -10
- package/dist/logger.js.map +7 -1
- package/dist/pack-manager.d.ts +88 -24
- package/dist/pack-manager.d.ts.map +1 -1
- package/dist/pack-manager.js +280 -245
- package/dist/pack-manager.js.map +7 -1
- package/dist/paths.d.ts +8 -3
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +53 -39
- package/dist/paths.js.map +7 -1
- package/dist/qqutil.d.ts.map +1 -1
- package/dist/qqutil.js +90 -87
- package/dist/qqutil.js.map +7 -1
- package/dist/recovery.js +26 -37
- package/dist/recovery.js.map +7 -1
- package/dist/rollback.d.ts +1 -1
- package/dist/rollback.js +55 -74
- package/dist/rollback.js.map +7 -1
- package/dist/server-properties.d.ts +12 -0
- package/dist/server-properties.d.ts.map +1 -0
- package/dist/server-properties.js +28 -0
- package/dist/server-properties.js.map +7 -0
- package/dist/taskbar.d.ts +2 -0
- package/dist/taskbar.d.ts.map +1 -1
- package/dist/taskbar.js +51 -82
- package/dist/taskbar.js.map +7 -1
- package/dist/types.js +1 -5
- package/dist/types.js.map +7 -1
- package/dist/update-result.d.ts +12 -0
- package/dist/update-result.d.ts.map +1 -0
- package/dist/update-result.js +15 -0
- package/dist/update-result.js.map +7 -0
- package/dist/upstream.js +94 -108
- package/dist/upstream.js.map +7 -1
- package/dist/version.js +63 -81
- package/dist/version.js.map +7 -1
- package/dist/world-packs.d.ts +112 -0
- package/dist/world-packs.d.ts.map +1 -0
- package/dist/world-packs.js +335 -0
- package/dist/world-packs.js.map +7 -0
- package/dist/zipx.d.ts +10 -0
- package/dist/zipx.d.ts.map +1 -0
- package/dist/zipx.js +48 -0
- package/dist/zipx.js.map +7 -0
- package/package.json +17 -6
package/dist/check-update.js
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* 3. 流式 SHA256 校验 - 不把 250MB 文件读入内存
|
|
7
|
-
* 4. 兼容版本白名单 - 不匹配则跳过升级
|
|
8
|
-
* 5. QQ 通知统一加 5s 超时 - 永远不阻塞主流程
|
|
9
|
-
* 6. Rollback marker 落盘 - 跨进程 / 跨重启可恢复
|
|
10
|
-
*/
|
|
11
|
-
import { createFileSink, createLogger, createStdoutSink } from "@sfmc-bds/sdk/logs";
|
|
12
|
-
import cliProgress from "cli-progress";
|
|
13
|
-
import JSZip from "jszip";
|
|
1
|
+
import {
|
|
2
|
+
bindByteProgressToBar,
|
|
3
|
+
createNodeServiceLogger,
|
|
4
|
+
createTerminalProgress
|
|
5
|
+
} from "@sfmc-bds/sdk/logs";
|
|
14
6
|
import fs from "node:fs";
|
|
15
7
|
import os from "node:os";
|
|
16
8
|
import path from "node:path";
|
|
@@ -18,425 +10,393 @@ import { createBdsManager } from "./bds-manager.js";
|
|
|
18
10
|
import { CHANGELOG_BASE, fetchChangelog } from "./changelog.js";
|
|
19
11
|
import { copyDirSync, emptyDirSync, hashFileAsync, rmSafe } from "./fsx.js";
|
|
20
12
|
import { httpDownload } from "./http.js";
|
|
13
|
+
import { isMainModule } from "./is-main.js";
|
|
21
14
|
import { loadConfig, LOG_PATH, resolvePaths } from "./paths.js";
|
|
22
15
|
import { sendText, sendWithImage } from "./qqutil.js";
|
|
23
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
clearRollbackMarker,
|
|
18
|
+
getDirSize,
|
|
19
|
+
rollbackFromBackup,
|
|
20
|
+
verifyBdsInstall,
|
|
21
|
+
writeRollbackMarker
|
|
22
|
+
} from "./rollback.js";
|
|
23
|
+
import { ensureEmitServerTelemetry } from "./server-properties.js";
|
|
24
24
|
import { clearTaskbarProgress, isTaskbarSupported, setTaskbarProgress } from "./taskbar.js";
|
|
25
|
-
import {
|
|
25
|
+
import { emitUpdateResult } from "./update-result.js";
|
|
26
|
+
import {
|
|
27
|
+
buildDownloadUrls,
|
|
28
|
+
fetchVersionDetails,
|
|
29
|
+
getVersionInfo,
|
|
30
|
+
isVersionCompatible,
|
|
31
|
+
verifyFileHash
|
|
32
|
+
} from "./upstream.js";
|
|
26
33
|
import { compareVersions, getCurrentVersionAsync, getCurrentVersionSync, saveVersionCache } from "./version.js";
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const closeLog = () => updaterFileSink.close();
|
|
34
|
+
import { extractZipFileToDir } from "./zipx.js";
|
|
35
|
+
const log = createNodeServiceLogger({ source: "updater", logPath: LOG_PATH });
|
|
36
|
+
const closeLog = () => log.close();
|
|
31
37
|
function parseArgs(argv) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return out;
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const a of argv) {
|
|
40
|
+
const m = a.match(/^--([\w-]+)(?:=(.+))?$/);
|
|
41
|
+
if (m) out[m[1]] = m[2] || true;
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
39
44
|
}
|
|
40
45
|
function buildContext() {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
const cfg = loadConfig();
|
|
47
|
+
const args = parseArgs(process.argv.slice(2));
|
|
48
|
+
const { bds_path, backup_dir, preserve } = resolvePaths(cfg);
|
|
49
|
+
const channel = String(args["channel"] || cfg.channel || "release");
|
|
50
|
+
return {
|
|
51
|
+
cfg,
|
|
52
|
+
channel,
|
|
53
|
+
checkOnly: !!args["check-only"],
|
|
54
|
+
force: !!args["force"],
|
|
55
|
+
bdsPath: bds_path,
|
|
56
|
+
backupDir: backup_dir,
|
|
57
|
+
preserve,
|
|
58
|
+
autoRestart: !args["no-start"] && cfg.auto_restart !== false,
|
|
59
|
+
qqNotify: !!cfg.qq_notify
|
|
60
|
+
};
|
|
56
61
|
}
|
|
57
62
|
async function doBackup(bdsPath, backupDir, preserve) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!anyCopied) {
|
|
77
|
-
log.info(`没有需要备份的文件 (preserve 列表为空或全部缺失)`);
|
|
78
|
-
}
|
|
79
|
-
return { path: dest, size: getDirSize(dest) };
|
|
63
|
+
const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
64
|
+
const dest = path.join(backupDir, dateStr);
|
|
65
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
66
|
+
let anyCopied = false;
|
|
67
|
+
for (const item of preserve) {
|
|
68
|
+
const src = path.join(bdsPath, item);
|
|
69
|
+
const target = path.join(dest, item);
|
|
70
|
+
if (!fs.existsSync(src)) continue;
|
|
71
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
72
|
+
if (fs.statSync(src).isDirectory()) copyDirSync(src, target);
|
|
73
|
+
else fs.copyFileSync(src, target);
|
|
74
|
+
anyCopied = true;
|
|
75
|
+
log.info(`\u5DF2\u5907\u4EFD: ${item}`);
|
|
76
|
+
}
|
|
77
|
+
if (!anyCopied) {
|
|
78
|
+
log.info(`\u6CA1\u6709\u9700\u8981\u5907\u4EFD\u7684\u6587\u4EF6 (preserve \u5217\u8868\u4E3A\u7A7A\u6216\u5168\u90E8\u7F3A\u5931)`);
|
|
79
|
+
}
|
|
80
|
+
return { path: dest, size: getDirSize(dest) };
|
|
80
81
|
}
|
|
81
|
-
/** 异步恢复 preserves 从备份到 BDS 路径 */
|
|
82
82
|
async function restorePreserves(bdsPath, backupDir, preserve) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
log.info(`已恢复: ${item}`);
|
|
99
|
-
}
|
|
100
|
-
catch (e) {
|
|
101
|
-
log.warn(`恢复失败 ${item}: ${e.message}`);
|
|
102
|
-
}
|
|
83
|
+
for (const item of preserve) {
|
|
84
|
+
const src = path.join(backupDir, item);
|
|
85
|
+
const dest = path.join(bdsPath, item);
|
|
86
|
+
if (!fs.existsSync(src)) continue;
|
|
87
|
+
try {
|
|
88
|
+
if (fs.statSync(src).isDirectory()) {
|
|
89
|
+
if (fs.existsSync(dest)) emptyDirSync(dest);
|
|
90
|
+
copyDirSync(src, dest);
|
|
91
|
+
} else {
|
|
92
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
93
|
+
fs.copyFileSync(src, dest);
|
|
94
|
+
}
|
|
95
|
+
log.info(`\u5DF2\u6062\u590D: ${item}`);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
log.warn(`\u6062\u590D\u5931\u8D25 ${item}: ${e.message}`);
|
|
103
98
|
}
|
|
99
|
+
}
|
|
104
100
|
}
|
|
105
|
-
/** 把 srcDir 下的内容移动到 destDir (覆盖) */
|
|
106
101
|
async function extractZipToBds(zipPath, destDir) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
120
|
-
const buf = await e.async("nodebuffer");
|
|
121
|
-
fs.writeFileSync(out, buf);
|
|
122
|
-
}
|
|
123
|
-
// 把临时目录里的内容复制到 BDS 路径
|
|
124
|
-
for (const entry of fs.readdirSync(tmpDir)) {
|
|
125
|
-
const srcPath = path.join(tmpDir, entry);
|
|
126
|
-
const destPath = path.join(destDir, entry);
|
|
127
|
-
if (fs.statSync(srcPath).isDirectory()) {
|
|
128
|
-
if (fs.existsSync(destPath))
|
|
129
|
-
emptyDirSync(destPath);
|
|
130
|
-
copyDirSync(srcPath, destPath);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
fs.copyFileSync(srcPath, destPath);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
finally {
|
|
138
|
-
rmSafe(tmpDir);
|
|
102
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "bds-extract-"));
|
|
103
|
+
try {
|
|
104
|
+
await extractZipFileToDir(zipPath, tmpDir);
|
|
105
|
+
for (const entry of fs.readdirSync(tmpDir)) {
|
|
106
|
+
const srcPath = path.join(tmpDir, entry);
|
|
107
|
+
const destPath = path.join(destDir, entry);
|
|
108
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
109
|
+
if (fs.existsSync(destPath)) emptyDirSync(destPath);
|
|
110
|
+
copyDirSync(srcPath, destPath);
|
|
111
|
+
} else {
|
|
112
|
+
fs.copyFileSync(srcPath, destPath);
|
|
113
|
+
}
|
|
139
114
|
}
|
|
115
|
+
} finally {
|
|
116
|
+
rmSafe(tmpDir);
|
|
117
|
+
}
|
|
140
118
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
119
|
+
async function runUpdate() {
|
|
120
|
+
const startTime = Date.now();
|
|
121
|
+
const ctx = buildContext();
|
|
122
|
+
const { cfg, channel, checkOnly, force, bdsPath, backupDir, preserve, autoRestart, qqNotify } = ctx;
|
|
123
|
+
log.info(`===== \u5F00\u59CB\u68C0\u67E5\u66F4\u65B0 (${channel}) =====`);
|
|
124
|
+
fs.mkdirSync(bdsPath, { recursive: true });
|
|
125
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
126
|
+
const exePath = path.join(bdsPath, "bedrock_server.exe");
|
|
127
|
+
const currentVer = await getCurrentVersionAsync(exePath).catch(() => getCurrentVersionSync(exePath));
|
|
128
|
+
log.info(`\u5F53\u524D\u7248\u672C: ${currentVer}`);
|
|
129
|
+
let latestVer;
|
|
130
|
+
try {
|
|
131
|
+
const info = await getVersionInfo(cfg, channel);
|
|
132
|
+
latestVer = info.version;
|
|
133
|
+
log.info(`\u6700\u65B0\u7248\u672C: ${latestVer}`);
|
|
134
|
+
const cmp = compareVersions(latestVer, currentVer);
|
|
135
|
+
if (cmp <= 0 && !force) {
|
|
136
|
+
log.info("\u5DF2\u662F\u6700\u65B0\u7248\u672C\uFF0C\u65E0\u9700\u66F4\u65B0");
|
|
137
|
+
if (qqNotify) {
|
|
138
|
+
await sendText(`\u2705 BDS \u5DF2\u662F\u6700\u65B0\u7248\u672C
|
|
139
|
+
|
|
140
|
+
\u5F53\u524D: ${currentVer}
|
|
141
|
+
\u6700\u65B0: ${latestVer}`);
|
|
142
|
+
}
|
|
143
|
+
console.log(`CURRENT=${currentVer}`);
|
|
144
|
+
console.log(`LATEST=${latestVer}`);
|
|
145
|
+
emitUpdateResult("uptodate");
|
|
146
|
+
return 0;
|
|
168
147
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
await sendText(`⚠️ BDS ${latestVer} 不在兼容性白名单,已跳过升级。请人工确认。`);
|
|
179
|
-
}
|
|
180
|
-
return 2;
|
|
181
|
-
}
|
|
182
|
-
// 3. 取下载链接
|
|
183
|
-
let downloadUrls;
|
|
184
|
-
let hash;
|
|
185
|
-
try {
|
|
186
|
-
const details = await fetchVersionDetails(cfg, channel, latestVer);
|
|
187
|
-
downloadUrls = buildDownloadUrls(cfg, channel, latestVer, details);
|
|
188
|
-
hash = { sha1: details.sha1, sha256: details.sha256 };
|
|
189
|
-
}
|
|
190
|
-
catch (e) {
|
|
191
|
-
log.error(`获取下载详情失败: ${e.message}`);
|
|
192
|
-
await sendText(`❌ BDS 更新失败\n\n下载详情获取失败: ${e.message}`);
|
|
193
|
-
return 1;
|
|
194
|
-
}
|
|
195
|
-
if (checkOnly) {
|
|
196
|
-
console.log(`CURRENT=${currentVer}`);
|
|
197
|
-
console.log(`LATEST=${latestVer}`);
|
|
198
|
-
console.log(`CHANNEL=${channel}`);
|
|
199
|
-
console.log(`URLS=${downloadUrls.length}`);
|
|
200
|
-
return 0;
|
|
201
|
-
}
|
|
202
|
-
log.info(`发现新版本: ${currentVer} → ${latestVer}`);
|
|
203
|
-
// 4. QQ 预告
|
|
148
|
+
} catch (e) {
|
|
149
|
+
log.error(`\u83B7\u53D6\u7248\u672C\u4FE1\u606F\u5931\u8D25: ${e.message}`);
|
|
150
|
+
await sendText(`\u274C BDS \u66F4\u65B0\u5931\u8D25
|
|
151
|
+
|
|
152
|
+
\u65E0\u6CD5\u83B7\u53D6\u7248\u672C\u4FE1\u606F: ${e.message}`);
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
if (!force && !isVersionCompatible(cfg, latestVer)) {
|
|
156
|
+
log.warn(`${latestVer} \u4E0D\u5728\u517C\u5BB9\u7248\u672C\u767D\u540D\u5355\u4E2D\uFF0C\u8DF3\u8FC7\u5347\u7EA7`);
|
|
204
157
|
if (qqNotify) {
|
|
205
|
-
|
|
206
|
-
`检测到新版本!\n\n` +
|
|
207
|
-
`当前: ${currentVer}\n` +
|
|
208
|
-
`最新: ${latestVer}\n` +
|
|
209
|
-
`频道: ${channel === "preview" ? "预览版" : "正式版"}\n\n` +
|
|
210
|
-
`服务器即将开始更新~ 请耐心等待^(* ̄(oo) ̄)^`);
|
|
211
|
-
const cl = await fetchChangelog(channel);
|
|
212
|
-
if (cl) {
|
|
213
|
-
const text = `📋 更新内容概要\n\n${cl.text.slice(0, 1500)}\n\n完整日志: ${CHANGELOG_BASE}`;
|
|
214
|
-
if (cl.imageBase64)
|
|
215
|
-
await sendWithImage(text, cl.imageBase64);
|
|
216
|
-
else
|
|
217
|
-
await sendText(text);
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
await sendText(`📋 更新日志: ${CHANGELOG_BASE}`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// 5. 备份
|
|
224
|
-
let backupInfo;
|
|
225
|
-
try {
|
|
226
|
-
backupInfo = await doBackup(bdsPath, backupDir, preserve);
|
|
227
|
-
log.info(`备份完成: ${backupInfo.path} (${(backupInfo.size / 1024 / 1024).toFixed(1)} MB)`);
|
|
228
|
-
if (qqNotify) {
|
|
229
|
-
await sendText(`✅ 备份完成\n\n大小: ${(backupInfo.size / 1024 / 1024).toFixed(1)} MB\n位置: ${backupInfo.path}`);
|
|
230
|
-
}
|
|
158
|
+
await sendText(`\u26A0\uFE0F BDS ${latestVer} \u4E0D\u5728\u517C\u5BB9\u6027\u767D\u540D\u5355\uFF0C\u5DF2\u8DF3\u8FC7\u5347\u7EA7\u3002\u8BF7\u4EBA\u5DE5\u786E\u8BA4\u3002`);
|
|
231
159
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
160
|
+
emitUpdateResult("skipped");
|
|
161
|
+
return 2;
|
|
162
|
+
}
|
|
163
|
+
let downloadUrls;
|
|
164
|
+
let hash;
|
|
165
|
+
try {
|
|
166
|
+
const details = await fetchVersionDetails(cfg, channel, latestVer);
|
|
167
|
+
downloadUrls = buildDownloadUrls(cfg, channel, latestVer, details);
|
|
168
|
+
hash = { sha1: details.sha1, sha256: details.sha256 };
|
|
169
|
+
} catch (e) {
|
|
170
|
+
log.error(`\u83B7\u53D6\u4E0B\u8F7D\u8BE6\u60C5\u5931\u8D25: ${e.message}`);
|
|
171
|
+
await sendText(`\u274C BDS \u66F4\u65B0\u5931\u8D25
|
|
172
|
+
|
|
173
|
+
\u4E0B\u8F7D\u8BE6\u60C5\u83B7\u53D6\u5931\u8D25: ${e.message}`);
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
176
|
+
if (checkOnly) {
|
|
177
|
+
console.log(`CURRENT=${currentVer}`);
|
|
178
|
+
console.log(`LATEST=${latestVer}`);
|
|
179
|
+
console.log(`CHANNEL=${channel}`);
|
|
180
|
+
console.log(`URLS=${downloadUrls.length}`);
|
|
181
|
+
emitUpdateResult("check-only");
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
log.info(`\u53D1\u73B0\u65B0\u7248\u672C: ${currentVer} \u2192 ${latestVer}`);
|
|
185
|
+
if (qqNotify) {
|
|
186
|
+
await sendText(
|
|
187
|
+
`\u{1D40C}\u{1D422}\u{1D427}\u{1D41E}\u{1D41C}\u{1D42B}\u{1D41A}\u{1D41F}\u{1D42D} \u{1D401}\u{1D403}\u{1D412} \u66F4\u65B0\u901A\u77E5
|
|
188
|
+
|
|
189
|
+
\u68C0\u6D4B\u5230\u65B0\u7248\u672C\uFF01
|
|
190
|
+
|
|
191
|
+
\u5F53\u524D: ${currentVer}
|
|
192
|
+
\u6700\u65B0: ${latestVer}
|
|
193
|
+
\u9891\u9053: ${channel === "preview" ? "\u9884\u89C8\u7248" : "\u6B63\u5F0F\u7248"}
|
|
194
|
+
|
|
195
|
+
\u670D\u52A1\u5668\u5373\u5C06\u5F00\u59CB\u66F4\u65B0~ \u8BF7\u8010\u5FC3\u7B49\u5F85^(*\uFFE3(oo)\uFFE3)^`
|
|
196
|
+
);
|
|
197
|
+
const cl = await fetchChangelog(channel);
|
|
198
|
+
if (cl) {
|
|
199
|
+
const text = `\u{1F4CB} \u66F4\u65B0\u5185\u5BB9\u6982\u8981
|
|
200
|
+
|
|
201
|
+
${cl.text.slice(0, 1500)}
|
|
202
|
+
|
|
203
|
+
\u5B8C\u6574\u65E5\u5FD7: ${CHANGELOG_BASE}`;
|
|
204
|
+
if (cl.imageBase64) await sendWithImage(text, cl.imageBase64);
|
|
205
|
+
else await sendText(text);
|
|
206
|
+
} else {
|
|
207
|
+
await sendText(`\u{1F4CB} \u66F4\u65B0\u65E5\u5FD7: ${CHANGELOG_BASE}`);
|
|
236
208
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
log.info("停止 BDS 服务...");
|
|
249
|
-
await bds.stop();
|
|
250
|
-
log.info("BDS 已停止");
|
|
209
|
+
}
|
|
210
|
+
let backupInfo;
|
|
211
|
+
try {
|
|
212
|
+
backupInfo = await doBackup(bdsPath, backupDir, preserve);
|
|
213
|
+
log.info(`\u5907\u4EFD\u5B8C\u6210: ${backupInfo.path} (${(backupInfo.size / 1024 / 1024).toFixed(1)} MB)`);
|
|
214
|
+
if (qqNotify) {
|
|
215
|
+
await sendText(`\u2705 \u5907\u4EFD\u5B8C\u6210
|
|
216
|
+
|
|
217
|
+
\u5927\u5C0F: ${(backupInfo.size / 1024 / 1024).toFixed(1)} MB
|
|
218
|
+
\u4F4D\u7F6E: ${backupInfo.path}`);
|
|
251
219
|
}
|
|
252
|
-
|
|
253
|
-
|
|
220
|
+
} catch (e) {
|
|
221
|
+
log.error(`\u5907\u4EFD\u5931\u8D25: ${e.message}`);
|
|
222
|
+
await sendText(`\u274C BDS \u66F4\u65B0\u5931\u8D25
|
|
223
|
+
|
|
224
|
+
\u5907\u4EFD\u5931\u8D25: ${e.message}
|
|
225
|
+
\u64CD\u4F5C\u5DF2\u4E2D\u6B62`);
|
|
226
|
+
return 1;
|
|
227
|
+
}
|
|
228
|
+
writeRollbackMarker({
|
|
229
|
+
timestamp: Date.now(),
|
|
230
|
+
bds_path: bdsPath,
|
|
231
|
+
backup_dir: backupInfo.path,
|
|
232
|
+
preserve,
|
|
233
|
+
previous_version: currentVer
|
|
234
|
+
});
|
|
235
|
+
const bds = createBdsManager({ detached: true });
|
|
236
|
+
try {
|
|
237
|
+
log.info("\u505C\u6B62 BDS \u670D\u52A1...");
|
|
238
|
+
await bds.stop();
|
|
239
|
+
log.info("BDS \u5DF2\u505C\u6B62");
|
|
240
|
+
} catch (e) {
|
|
241
|
+
log.warn(`BDS \u505C\u6B62\u5F02\u5E38: ${e.message}`);
|
|
242
|
+
}
|
|
243
|
+
const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), "bds-update-"));
|
|
244
|
+
const zipPath = path.join(stagingDir, `bedrock-server-${latestVer}.zip`);
|
|
245
|
+
let downloaded = false;
|
|
246
|
+
try {
|
|
247
|
+
log.info(`\u5F00\u59CB\u4E0B\u8F7D ${latestVer}...`);
|
|
248
|
+
if (qqNotify) await sendText(`\u{1F4E5} \u6B63\u5728\u4E0B\u8F7D BDS ${latestVer}...`);
|
|
249
|
+
let lastErr = null;
|
|
250
|
+
const downloadTimeoutMs = (cfg.download_timeout ?? 120) * 1e3;
|
|
251
|
+
if (isTaskbarSupported() && process.stdout.isTTY) {
|
|
252
|
+
log.info("\u68C0\u6D4B\u5230 Windows Terminal,\u4EFB\u52A1\u680F\u8FDB\u5EA6\u5DF2\u542F\u7528 (OSC 9;4)");
|
|
254
253
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const progressBar = new cliProgress.SingleBar({
|
|
267
|
-
format: "下载进度 | {bar} | {percentage}% | {value}/{total} MB | 速度: {speed}",
|
|
268
|
-
barCompleteChar: "\u2588",
|
|
269
|
-
barIncompleteChar: "\u2591",
|
|
270
|
-
hideCursor: true,
|
|
271
|
-
clearOnComplete: false, // 保留进度条
|
|
272
|
-
}, cliProgress.Presets.shades_classic);
|
|
273
|
-
let lastTime = Date.now();
|
|
274
|
-
let lastLoaded = 0;
|
|
275
|
-
let barStarted = false; // 标记是否已启动
|
|
276
|
-
if (isTaskbarSupported()) {
|
|
277
|
-
log.info("检测到 Windows Terminal,任务栏进度已启用 (OSC 9;4)");
|
|
278
|
-
}
|
|
279
|
-
for (const url of downloadUrls) {
|
|
280
|
-
try {
|
|
281
|
-
await httpDownload(url, zipPath, {
|
|
282
|
-
totalTimeoutMs: Math.max(downloadTimeoutMs, 600_000), // 不少于 10 分钟
|
|
283
|
-
onProgress: (dl, total) => {
|
|
284
|
-
// 第一次触发时启动进度条
|
|
285
|
-
if (!barStarted) {
|
|
286
|
-
progressBar.start(total, 0, { speed: "0 KB/s" });
|
|
287
|
-
barStarted = true;
|
|
288
|
-
setTaskbarProgress(0); // 启动任务栏进度(0%)
|
|
289
|
-
}
|
|
290
|
-
const now = Date.now();
|
|
291
|
-
const timeDelta = (now - lastTime) / 1000; // 秒
|
|
292
|
-
const bytesDelta = dl - lastLoaded;
|
|
293
|
-
const speed = timeDelta > 0 ? bytesDelta / timeDelta : 0;
|
|
294
|
-
const speedStr = speed > 1024 * 1024
|
|
295
|
-
? `${(speed / 1024 / 1024).toFixed(1)} MB/s`
|
|
296
|
-
: speed > 1024
|
|
297
|
-
? `${(speed / 1024).toFixed(1)} KB/s`
|
|
298
|
-
: `${speed.toFixed(0)} B/s`;
|
|
299
|
-
progressBar.update(dl, { speed: speedStr });
|
|
300
|
-
// 同步任务栏进度(0-100)。httpDownload 内部已做 100ms 节流,
|
|
301
|
-
// 这里再 set 一次,getSnapshot 比较,相同值直接跳过
|
|
302
|
-
setTaskbarProgress((dl / total) * 100);
|
|
303
|
-
lastLoaded = dl;
|
|
304
|
-
lastTime = now;
|
|
305
|
-
},
|
|
306
|
-
});
|
|
307
|
-
progressBar.stop();
|
|
308
|
-
// 下载完成,任务栏收到 100% 绿条后清掉
|
|
309
|
-
setTaskbarProgress(100);
|
|
310
|
-
clearTaskbarProgress();
|
|
311
|
-
downloaded = true;
|
|
312
|
-
lastErr = null;
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
catch (e) {
|
|
316
|
-
if (barStarted)
|
|
317
|
-
progressBar.stop();
|
|
318
|
-
// 下载失败,任务栏亮红
|
|
319
|
-
setTaskbarProgress(100, "error");
|
|
320
|
-
clearTaskbarProgress();
|
|
321
|
-
lastErr = e;
|
|
322
|
-
log.warn(`${url} 不可用: ${lastErr.message},尝试备用地址...`);
|
|
323
|
-
}
|
|
254
|
+
for (const url of downloadUrls) {
|
|
255
|
+
const progressBar = createTerminalProgress({
|
|
256
|
+
stream: process.stderr,
|
|
257
|
+
logger: (msg) => log.info(msg.startsWith("\u8FDB\u5EA6") ? `\u4E0B\u8F7D${msg.slice(2)}` : msg),
|
|
258
|
+
format: "\u4E0B\u8F7D\u8FDB\u5EA6 | {bar} | {percentage}% | {value}/{total} MB | \u901F\u5EA6: {speed}"
|
|
259
|
+
});
|
|
260
|
+
const onByteProgress = bindByteProgressToBar(progressBar, {
|
|
261
|
+
speedSampleMs: 0,
|
|
262
|
+
onProgress: (dl, total) => {
|
|
263
|
+
const pct = total > 0 ? dl / total * 100 : 0;
|
|
264
|
+
setTaskbarProgress(pct);
|
|
324
265
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
log.info(`校验文件完整性 (${algo})...`);
|
|
333
|
-
const ok = await verifyFileHash(zipPath, hash.sha1, hash.sha256);
|
|
334
|
-
if (!ok)
|
|
335
|
-
throw new Error(`${algo} 校验不通过,文件可能损坏`);
|
|
336
|
-
log.info(`${algo} 校验通过`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
catch (e) {
|
|
340
|
-
log.error(`下载失败: ${e.message}`);
|
|
341
|
-
await sendText(`❌ BDS 更新失败\n\n下载失败: ${e.message}\nBDS 已停止,请手动恢复`);
|
|
342
|
-
// 自动回滚
|
|
343
|
-
rollbackFromBackup({
|
|
344
|
-
timestamp: Date.now(),
|
|
345
|
-
bds_path: bdsPath,
|
|
346
|
-
backup_dir: backupInfo.path,
|
|
347
|
-
preserve,
|
|
348
|
-
previous_version: currentVer,
|
|
266
|
+
});
|
|
267
|
+
try {
|
|
268
|
+
setTaskbarProgress(0);
|
|
269
|
+
await httpDownload(url, zipPath, {
|
|
270
|
+
totalTimeoutMs: Math.max(downloadTimeoutMs, 6e5),
|
|
271
|
+
// 不少于 10 分钟
|
|
272
|
+
onProgress: onByteProgress
|
|
349
273
|
});
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
await sendText(`❌ BDS 更新失败\n\n解压失败: ${e.message}\nBDS 已停止,请手动恢复`);
|
|
364
|
-
// 回滚: 清空 + 从备份恢复 preserves
|
|
365
|
-
emptyDirSync(bdsPath);
|
|
366
|
-
await restorePreserves(bdsPath, backupInfo.path, preserve);
|
|
367
|
-
rmSafe(stagingDir);
|
|
368
|
-
return 1;
|
|
369
|
-
}
|
|
370
|
-
finally {
|
|
371
|
-
rmSafe(zipPath); // 清掉 staging zip
|
|
274
|
+
if (progressBar.active) progressBar.stop();
|
|
275
|
+
setTaskbarProgress(100);
|
|
276
|
+
clearTaskbarProgress();
|
|
277
|
+
downloaded = true;
|
|
278
|
+
lastErr = null;
|
|
279
|
+
break;
|
|
280
|
+
} catch (e) {
|
|
281
|
+
if (progressBar.active) progressBar.stop();
|
|
282
|
+
setTaskbarProgress(100, "error");
|
|
283
|
+
clearTaskbarProgress();
|
|
284
|
+
lastErr = e;
|
|
285
|
+
log.warn(`${url} \u4E0D\u53EF\u7528: ${lastErr.message}\uFF0C\u5C1D\u8BD5\u5907\u7528\u5730\u5740...`);
|
|
286
|
+
}
|
|
372
287
|
}
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
await restorePreserves(bdsPath, backupInfo.path, preserve);
|
|
383
|
-
rmSafe(stagingDir);
|
|
384
|
-
return 1;
|
|
288
|
+
if (!downloaded && lastErr) throw lastErr;
|
|
289
|
+
const zipSizeMB = (fs.statSync(zipPath).size / 1024 / 1024).toFixed(1);
|
|
290
|
+
log.info(`\u4E0B\u8F7D\u5B8C\u6210 (${zipSizeMB} MB)`);
|
|
291
|
+
if (hash.sha1 || hash.sha256) {
|
|
292
|
+
const algo = hash.sha256 ? "SHA256" : "SHA1";
|
|
293
|
+
log.info(`\u6821\u9A8C\u6587\u4EF6\u5B8C\u6574\u6027 (${algo})...`);
|
|
294
|
+
const ok = await verifyFileHash(zipPath, hash.sha1, hash.sha256);
|
|
295
|
+
if (!ok) throw new Error(`${algo} \u6821\u9A8C\u4E0D\u901A\u8FC7\uFF0C\u6587\u4EF6\u53EF\u80FD\u635F\u574F`);
|
|
296
|
+
log.info(`${algo} \u6821\u9A8C\u901A\u8FC7`);
|
|
385
297
|
}
|
|
386
|
-
|
|
298
|
+
} catch (e) {
|
|
299
|
+
log.error(`\u4E0B\u8F7D\u5931\u8D25: ${e.message}`);
|
|
300
|
+
await sendText(`\u274C BDS \u66F4\u65B0\u5931\u8D25
|
|
301
|
+
|
|
302
|
+
\u4E0B\u8F7D\u5931\u8D25: ${e.message}
|
|
303
|
+
BDS \u5DF2\u505C\u6B62\uFF0C\u8BF7\u624B\u52A8\u6062\u590D`);
|
|
304
|
+
rollbackFromBackup({
|
|
305
|
+
timestamp: Date.now(),
|
|
306
|
+
bds_path: bdsPath,
|
|
307
|
+
backup_dir: backupInfo.path,
|
|
308
|
+
preserve,
|
|
309
|
+
previous_version: currentVer
|
|
310
|
+
});
|
|
311
|
+
rmSafe(stagingDir);
|
|
312
|
+
return 1;
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
log.info("\u89E3\u538B\u4E2D...");
|
|
316
|
+
emptyDirSync(bdsPath);
|
|
317
|
+
await extractZipToBds(zipPath, bdsPath);
|
|
318
|
+
log.info("\u89E3\u538B\u5B8C\u6210");
|
|
319
|
+
} catch (e) {
|
|
320
|
+
log.error(`\u89E3\u538B\u5931\u8D25: ${e.message}`);
|
|
321
|
+
await sendText(`\u274C BDS \u66F4\u65B0\u5931\u8D25
|
|
322
|
+
|
|
323
|
+
\u89E3\u538B\u5931\u8D25: ${e.message}
|
|
324
|
+
BDS \u5DF2\u505C\u6B62\uFF0C\u8BF7\u624B\u52A8\u6062\u590D`);
|
|
325
|
+
emptyDirSync(bdsPath);
|
|
387
326
|
await restorePreserves(bdsPath, backupInfo.path, preserve);
|
|
388
|
-
// 12. 写入版本缓存
|
|
389
|
-
const newExeHash = await hashFileAsync(exePath, "sha256").catch(() => "");
|
|
390
|
-
if (newExeHash) {
|
|
391
|
-
saveVersionCache(latestVer, newExeHash);
|
|
392
|
-
log.info(`已记录版本 ${latestVer} 的 SHA256`);
|
|
393
|
-
}
|
|
394
|
-
// 13. 启动 BDS
|
|
395
|
-
if (autoRestart) {
|
|
396
|
-
try {
|
|
397
|
-
log.info("启动 BDS...");
|
|
398
|
-
await bds.start();
|
|
399
|
-
}
|
|
400
|
-
catch (e) {
|
|
401
|
-
log.error(`启动失败: ${e.message}`);
|
|
402
|
-
await sendText(`❌ BDS 更新失败\n\n启动失败: ${e.message}\n请手动启动 BDS`);
|
|
403
|
-
// 注意: 此时 BDS 已经部署完成,启动失败 → 启动问题,不做文件级回滚
|
|
404
|
-
rmSafe(stagingDir);
|
|
405
|
-
clearRollbackMarker();
|
|
406
|
-
return 1;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
327
|
rmSafe(stagingDir);
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
328
|
+
return 1;
|
|
329
|
+
} finally {
|
|
330
|
+
rmSafe(zipPath);
|
|
331
|
+
}
|
|
332
|
+
const verify = verifyBdsInstall(bdsPath, [
|
|
333
|
+
"bedrock_server.exe",
|
|
334
|
+
/*"bedrock_server_symbols.debug",*/
|
|
335
|
+
"permissions.json"
|
|
336
|
+
]);
|
|
337
|
+
if (!verify.ok) {
|
|
338
|
+
log.error(`\u89E3\u538B\u540E\u7684 BDS \u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5931\u6587\u4EF6: ${verify.missing.join(", ")}`);
|
|
339
|
+
await sendText(`\u274C BDS \u66F4\u65B0\u5931\u8D25
|
|
340
|
+
|
|
341
|
+
\u90E8\u7F72\u540E\u7F3A\u5931\u5173\u952E\u6587\u4EF6: ${verify.missing.join(", ")}`);
|
|
342
|
+
emptyDirSync(bdsPath);
|
|
343
|
+
await restorePreserves(bdsPath, backupInfo.path, preserve);
|
|
344
|
+
rmSafe(stagingDir);
|
|
345
|
+
return 1;
|
|
346
|
+
}
|
|
347
|
+
await restorePreserves(bdsPath, backupInfo.path, preserve);
|
|
348
|
+
ensureEmitServerTelemetry(bdsPath, log);
|
|
349
|
+
const newExeHash = await hashFileAsync(exePath, "sha256").catch(() => "");
|
|
350
|
+
if (newExeHash) {
|
|
351
|
+
saveVersionCache(latestVer, newExeHash);
|
|
352
|
+
log.info(`\u5DF2\u8BB0\u5F55\u7248\u672C ${latestVer} \u7684 SHA256`);
|
|
353
|
+
}
|
|
354
|
+
if (autoRestart) {
|
|
355
|
+
try {
|
|
356
|
+
log.info("\u542F\u52A8 BDS...");
|
|
357
|
+
await bds.start();
|
|
358
|
+
} catch (e) {
|
|
359
|
+
log.error(`\u542F\u52A8\u5931\u8D25: ${e.message}`);
|
|
360
|
+
await sendText(`\u274C BDS \u66F4\u65B0\u5931\u8D25
|
|
361
|
+
|
|
362
|
+
\u542F\u52A8\u5931\u8D25: ${e.message}
|
|
363
|
+
\u8BF7\u624B\u52A8\u542F\u52A8 BDS`);
|
|
364
|
+
rmSafe(stagingDir);
|
|
365
|
+
clearRollbackMarker();
|
|
366
|
+
return 1;
|
|
418
367
|
}
|
|
419
|
-
|
|
420
|
-
|
|
368
|
+
}
|
|
369
|
+
rmSafe(stagingDir);
|
|
370
|
+
clearRollbackMarker();
|
|
371
|
+
const durationMs = Date.now() - startTime;
|
|
372
|
+
if (qqNotify) {
|
|
373
|
+
await sendText(
|
|
374
|
+
`\u2705 BDS \u66F4\u65B0\u5B8C\u6210
|
|
375
|
+
|
|
376
|
+
${currentVer} \u2192 ${latestVer}
|
|
377
|
+
\u8017\u65F6: ${(durationMs / 1e3).toFixed(1)}s
|
|
378
|
+
${autoRestart ? "\u670D\u52A1\u5668\u5DF2\u91CD\u65B0\u542F\u52A8" : "\u8BF7\u624B\u52A8\u91CD\u542F\u670D\u52A1\u5668"}`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
log.info(`===== \u66F4\u65B0\u5B8C\u6210 (${(durationMs / 1e3).toFixed(1)}s) =====`);
|
|
382
|
+
emitUpdateResult("deployed");
|
|
383
|
+
return 0;
|
|
421
384
|
}
|
|
422
385
|
function isMain() {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (require.main === module)
|
|
426
|
-
return true;
|
|
427
|
-
const entry = process.argv[1] ?? "";
|
|
428
|
-
return entry.endsWith("check-update.js") || entry.endsWith("check-update.ts");
|
|
386
|
+
if (process.env.SFMC_SERVICE === "update") return true;
|
|
387
|
+
return isMainModule(import.meta.url);
|
|
429
388
|
}
|
|
430
389
|
if (isMain()) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
process.exit(1);
|
|
440
|
-
});
|
|
390
|
+
runUpdate().then((code) => {
|
|
391
|
+
closeLog();
|
|
392
|
+
process.exit(code);
|
|
393
|
+
}).catch((e) => {
|
|
394
|
+
log.error(`\u672A\u6355\u83B7\u9519\u8BEF: ${e.message}`);
|
|
395
|
+
closeLog();
|
|
396
|
+
process.exit(1);
|
|
397
|
+
});
|
|
441
398
|
}
|
|
442
|
-
|
|
399
|
+
export {
|
|
400
|
+
runUpdate
|
|
401
|
+
};
|
|
402
|
+
//# sourceMappingURL=check-update.js.map
|