diff --git a/modules/sdk/@sfmc-sdk/src/logs/terminal-progress.ts b/modules/sdk/@sfmc-sdk/src/logs/terminal-progress.ts index 5cb6e80..814472c 100644 --- a/modules/sdk/@sfmc-sdk/src/logs/terminal-progress.ts +++ b/modules/sdk/@sfmc-sdk/src/logs/terminal-progress.ts @@ -145,9 +145,9 @@ export function bindByteProgressToBar( /* 未知→已知:重 start 以修正 total(ProgressHandle 无独立 setTotal) */ bar.start(totalMb, dlMb, { speed: speedText }); knownTotal = true; - } else { - bar.update(dlMb, { speed: speedText }); } + /* 始终 update:首帧反映已下载字节;非 TTY logger 也走统一刻度(LSP) */ + bar.update(dlMb, { speed: speedText }); if (speedSampleMs <= 0 || now - lastTime >= speedSampleMs) { lastTime = now; @@ -251,8 +251,10 @@ export function createTerminalProgress(opts: TerminalProgressOptions = {}): Prog if (!id) register(); draw(); } else if (opts.logger) { - opts.logger(`进度 0% (0/${total})`); - lastLoggedPct = 0; + /* 尊重 startValue(LSP:与 ProgressHandle.start 契约一致;重 start 修正总量时勿写死 0%) */ + const pct = total > 0 ? Math.min(100, Math.round((value / total) * 100)) : 0; + opts.logger(`进度 ${pct}% (${value.toFixed(1)}/${total.toFixed(1)})`); + lastLoggedPct = Math.floor(pct / stepPercent) * stepPercent; } }, update(v, p) { diff --git a/sfmc/terminal-progress.test.mjs b/sfmc/terminal-progress.test.mjs index 107b31b..be831d2 100644 --- a/sfmc/terminal-progress.test.mjs +++ b/sfmc/terminal-progress.test.mjs @@ -5,9 +5,33 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { Writable } from "node:stream"; -import { bindByteProgressToBar, createTerminalProgress } from "@sfmc-bds/sdk/logs"; +import { + bindByteProgressToBar, + createTerminalProgress, + pauseAllProgress, + resumeAllProgress, +} from "@sfmc-bds/sdk/logs"; describe("progress pause/resume contract (registry)", () => { + it("嵌套 pause 只在最外层 resume 时重绘", () => { + const stream = new Writable({ + write(_chunk, _e, cb) { + cb(); + }, + }); + const bar = createTerminalProgress({ stream, forceBar: true }); + bar.start(10, 0, { speed: "0 KB/s" }); + + pauseAllProgress(); + pauseAllProgress(); + /* 嵌套 pause:内层 resume 不应重绘(pausedDepth 仍 >0) */ + resumeAllProgress(); + assert.equal(bar.active, true); + resumeAllProgress(); + assert.equal(bar.active, true); + bar.stop(); + }); + it("Writable 可接收进度输出", () => { const chunks = []; const stream = new Writable({ @@ -45,6 +69,8 @@ describe("bindByteProgressToBar LSP(未知总量→已知)", () => { onByte(512 * 1024, 0); assert.equal(starts.length, 1); assert.equal(starts[0].total, 1); /* 占位 1MB */ + assert.equal(updates.length, 1, "首帧须 update 反映已下载字节"); + assert.equal(updates[0].value, 0.5); assert.equal(seen[0][0], 512 * 1024); assert.equal(seen[0][1], 0); @@ -54,17 +80,22 @@ describe("bindByteProgressToBar LSP(未知总量→已知)", () => { assert.equal(starts.length, 2, "总量从未知变为已知时应重 start"); assert.equal(starts[1].total, 5); assert.equal(starts[1].value, 5); + assert.equal(updates.length, 2, "重 start 后仍须 update(非 TTY logger 对齐)"); + assert.equal(updates[1].value, 5); assert.deepEqual(seen[1], [finalBytes, finalBytes]); }); - it("一开始就有 Content-Length 时不重复 start", () => { + it("一开始就有 Content-Length 时不重复 start,且首帧 update", () => { const starts = []; + const updates = []; const bar = { active: true, start(total, value = 0) { starts.push({ total, value }); }, - update() {}, + update(value) { + updates.push(value); + }, stop() {}, }; const onByte = bindByteProgressToBar(bar, { speedSampleMs: 0 }); @@ -72,6 +103,28 @@ describe("bindByteProgressToBar LSP(未知总量→已知)", () => { onByte(2 * 1024 * 1024, 10 * 1024 * 1024); assert.equal(starts.length, 1); assert.equal(starts[0].total, 10); + assert.equal(updates.length, 2); + assert.ok(updates[0] > 0); + }); + + it("createTerminalProgress 非 TTY:重 start 尊重 startValue(勿写死 0%)", () => { + const logs = []; + const stream = new Writable({ + write(_chunk, _e, cb) { + cb(); + }, + }); + /* forceBar:false + logger → 非 TTY 路径 */ + const bar = createTerminalProgress({ + stream, + forceBar: false, + logger: (m) => logs.push(m), + }); + bar.start(5, 5, { speed: "1 MB/s" }); + assert.ok(logs.length >= 1); + assert.match(logs[0], /100%/); + assert.match(logs[0], /5(?:\.0)?\/5/); + bar.stop(); }); it("createTerminalProgress + binder 在 forceBar 下可跑通收尾", () => {