esp32tool 1.6.3 → 1.6.5
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/README.md +2 -0
- package/apple-touch-icon.png +0 -0
- package/css/style.css +23 -0
- package/dist/cli.js +13 -2
- package/dist/console.d.ts +4 -0
- package/dist/console.js +54 -2
- package/dist/esp_loader.js +12 -0
- package/dist/util/console-color.d.ts +6 -1
- package/dist/util/console-color.js +82 -21
- package/dist/util/line-break-transformer.js +18 -4
- package/dist/util/timestamp-transformer.d.ts +5 -0
- package/dist/util/timestamp-transformer.js +39 -0
- package/dist/web/index.js +1 -1
- package/icons/icon-128.png +0 -0
- package/icons/icon-144.png +0 -0
- package/icons/icon-152.png +0 -0
- package/icons/icon-192.png +0 -0
- package/icons/icon-384.png +0 -0
- package/icons/icon-512.png +0 -0
- package/icons/icon-72.png +0 -0
- package/icons/icon-96.png +0 -0
- package/index.html +1 -0
- package/js/console.js +52 -3
- package/js/modules/esptool.js +1 -1
- package/js/script.js +3 -0
- package/js/util/console-color.js +236 -185
- package/js/util/line-break-transformer.js +29 -17
- package/js/util/timestamp-transformer.js +39 -0
- package/package.cli.json +2 -2
- package/package.json +19 -14
- package/screenshots/desktop.png +0 -0
- package/screenshots/mobile.png +0 -0
- package/src/cli.ts +13 -2
- package/src/console.ts +57 -2
- package/src/esp_loader.ts +24 -0
- package/src/util/console-color.ts +89 -23
- package/src/util/line-break-transformer.ts +18 -4
- package/src/util/timestamp-transformer.ts +47 -0
- package/sw.js +1 -1
- package/tsconfig.util.json +9 -0
package/src/cli.ts
CHANGED
|
@@ -399,8 +399,19 @@ async function cmdEraseFlash(esploader: ESPLoader) {
|
|
|
399
399
|
// Use stub for erasing
|
|
400
400
|
const stub = await esploader.runStub();
|
|
401
401
|
|
|
402
|
-
//
|
|
403
|
-
|
|
402
|
+
// Show animated progress while erasing
|
|
403
|
+
const frames = ["|", "/", "-", "\\"];
|
|
404
|
+
let frameIdx = 0;
|
|
405
|
+
const spinner = setInterval(() => {
|
|
406
|
+
process.stdout.write(`\rErasing... ${frames[frameIdx++ % frames.length]}`);
|
|
407
|
+
}, 200);
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
await stub.eraseFlash();
|
|
411
|
+
} finally {
|
|
412
|
+
clearInterval(spinner);
|
|
413
|
+
process.stdout.write("\r \r");
|
|
414
|
+
}
|
|
404
415
|
|
|
405
416
|
cliLogger.log("Erase complete!");
|
|
406
417
|
}
|
package/src/console.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ColoredConsole, coloredConsoleStyles } from "./util/console-color.js";
|
|
2
2
|
import { LineBreakTransformer } from "./util/line-break-transformer.js";
|
|
3
|
+
import { TimestampTransformer } from "./util/timestamp-transformer.js";
|
|
3
4
|
|
|
4
5
|
export class ESP32ToolConsole {
|
|
5
6
|
private port: SerialPort;
|
|
@@ -8,6 +9,11 @@ export class ESP32ToolConsole {
|
|
|
8
9
|
private containerElement: HTMLElement;
|
|
9
10
|
private allowInput: boolean;
|
|
10
11
|
|
|
12
|
+
// Command history buffer
|
|
13
|
+
private commandHistory: string[] = [];
|
|
14
|
+
private historyIndex: number = -1;
|
|
15
|
+
private currentInput: string = "";
|
|
16
|
+
|
|
11
17
|
constructor(
|
|
12
18
|
port: SerialPort,
|
|
13
19
|
containerElement: HTMLElement,
|
|
@@ -163,6 +169,17 @@ export class ESP32ToolConsole {
|
|
|
163
169
|
ev.preventDefault();
|
|
164
170
|
ev.stopPropagation();
|
|
165
171
|
this._sendCommand();
|
|
172
|
+
} else if (ev.key === "ArrowUp") {
|
|
173
|
+
ev.preventDefault();
|
|
174
|
+
this._navigateHistory(1, input);
|
|
175
|
+
} else if (ev.key === "ArrowDown") {
|
|
176
|
+
ev.preventDefault();
|
|
177
|
+
this._navigateHistory(-1, input);
|
|
178
|
+
} else {
|
|
179
|
+
// User is editing — reset history navigation to live input
|
|
180
|
+
if (this.historyIndex !== -1) {
|
|
181
|
+
this.historyIndex = -1;
|
|
182
|
+
}
|
|
166
183
|
}
|
|
167
184
|
});
|
|
168
185
|
}
|
|
@@ -218,11 +235,11 @@ export class ESP32ToolConsole {
|
|
|
218
235
|
},
|
|
219
236
|
)
|
|
220
237
|
.pipeThrough(new TransformStream(new LineBreakTransformer()))
|
|
238
|
+
.pipeThrough(new TransformStream(new TimestampTransformer()))
|
|
221
239
|
.pipeTo(
|
|
222
240
|
new WritableStream({
|
|
223
241
|
write: (chunk) => {
|
|
224
|
-
|
|
225
|
-
this.console!.addLine(cleaned);
|
|
242
|
+
this.console!.addLine(chunk);
|
|
226
243
|
},
|
|
227
244
|
}),
|
|
228
245
|
);
|
|
@@ -241,11 +258,49 @@ export class ESP32ToolConsole {
|
|
|
241
258
|
}
|
|
242
259
|
}
|
|
243
260
|
|
|
261
|
+
private _navigateHistory(direction: 1 | -1, input: HTMLInputElement) {
|
|
262
|
+
if (this.commandHistory.length === 0) return;
|
|
263
|
+
|
|
264
|
+
// Save current unsent input before navigating away
|
|
265
|
+
if (this.historyIndex === -1) {
|
|
266
|
+
this.currentInput = input.value;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const nextIndex = this.historyIndex + direction;
|
|
270
|
+
|
|
271
|
+
if (nextIndex < 0) {
|
|
272
|
+
// Back to unsent draft
|
|
273
|
+
this.historyIndex = -1;
|
|
274
|
+
input.value = this.currentInput;
|
|
275
|
+
} else if (nextIndex < this.commandHistory.length) {
|
|
276
|
+
this.historyIndex = nextIndex;
|
|
277
|
+
input.value = this.commandHistory[this.historyIndex];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Move cursor to end
|
|
281
|
+
const len = input.value.length;
|
|
282
|
+
input.setSelectionRange(len, len);
|
|
283
|
+
}
|
|
284
|
+
|
|
244
285
|
private async _sendCommand() {
|
|
245
286
|
const input = this.containerElement.querySelector<HTMLInputElement>(
|
|
246
287
|
".esp32tool-console-input",
|
|
247
288
|
)!;
|
|
248
289
|
const command = input.value;
|
|
290
|
+
|
|
291
|
+
if (command.trim() !== "") {
|
|
292
|
+
// Avoid consecutive duplicates, cap at 100
|
|
293
|
+
if (this.commandHistory[0] !== command) {
|
|
294
|
+
this.commandHistory.unshift(command);
|
|
295
|
+
if (this.commandHistory.length > 100) {
|
|
296
|
+
this.commandHistory.pop();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Reset history navigation state
|
|
301
|
+
this.historyIndex = -1;
|
|
302
|
+
this.currentInput = "";
|
|
303
|
+
|
|
249
304
|
if (!this.port.writable) {
|
|
250
305
|
this.console!.addLine("Terminal disconnected: port not writable");
|
|
251
306
|
return;
|
package/src/esp_loader.ts
CHANGED
|
@@ -4335,6 +4335,8 @@ export class ESPLoader extends EventTarget {
|
|
|
4335
4335
|
// Flush serial buffers before flash read operation
|
|
4336
4336
|
await this.flushSerialBuffers();
|
|
4337
4337
|
|
|
4338
|
+
const readStartTime = Date.now();
|
|
4339
|
+
|
|
4338
4340
|
this.logger.log(
|
|
4339
4341
|
`Reading ${size} bytes from flash at address 0x${addr.toString(16)}...`,
|
|
4340
4342
|
);
|
|
@@ -4442,6 +4444,8 @@ export class ESPLoader extends EventTarget {
|
|
|
4442
4444
|
maxInFlight,
|
|
4443
4445
|
);
|
|
4444
4446
|
|
|
4447
|
+
const chunkStartTime = Date.now();
|
|
4448
|
+
|
|
4445
4449
|
const [res] = await this.checkCommand(ESP_READ_FLASH, pkt);
|
|
4446
4450
|
|
|
4447
4451
|
if (res != 0) {
|
|
@@ -4520,6 +4524,16 @@ export class ESPLoader extends EventTarget {
|
|
|
4520
4524
|
|
|
4521
4525
|
chunkSuccess = true;
|
|
4522
4526
|
|
|
4527
|
+
const chunkDuration = Date.now() - chunkStartTime;
|
|
4528
|
+
const speedKBs = (
|
|
4529
|
+
resp.length /
|
|
4530
|
+
1024 /
|
|
4531
|
+
(chunkDuration / 1000)
|
|
4532
|
+
).toFixed(1);
|
|
4533
|
+
this.logger.debug(
|
|
4534
|
+
`Chunk read took ${chunkDuration} ms (${resp.length} bytes, ${speedKBs} KB/s)`,
|
|
4535
|
+
);
|
|
4536
|
+
|
|
4523
4537
|
// ADAPTIVE SPEED ADJUSTMENT: Only for CDC devices
|
|
4524
4538
|
// Non-CDC devices (CH340, CP2102) stay at fixed blockSize=31, maxInFlight=31
|
|
4525
4539
|
if (this.isWebUSB() && this._isCDCDevice && retryCount === 0) {
|
|
@@ -4666,6 +4680,16 @@ export class ESPLoader extends EventTarget {
|
|
|
4666
4680
|
);
|
|
4667
4681
|
}
|
|
4668
4682
|
|
|
4683
|
+
const totalDuration = Date.now() - readStartTime;
|
|
4684
|
+
const totalSpeedKBs = (
|
|
4685
|
+
allData.length /
|
|
4686
|
+
1024 /
|
|
4687
|
+
(totalDuration / 1000)
|
|
4688
|
+
).toFixed(1);
|
|
4689
|
+
this.logger.log(
|
|
4690
|
+
`Read complete: ${allData.length} bytes in ${(totalDuration / 1000).toFixed(1)} s (${totalSpeedKBs} KB/s)`,
|
|
4691
|
+
);
|
|
4692
|
+
|
|
4669
4693
|
return allData;
|
|
4670
4694
|
}
|
|
4671
4695
|
}
|
|
@@ -6,7 +6,10 @@ interface ConsoleState {
|
|
|
6
6
|
foregroundColor: string | null;
|
|
7
7
|
backgroundColor: string | null;
|
|
8
8
|
carriageReturn: boolean;
|
|
9
|
+
lines: string[];
|
|
9
10
|
secret: boolean;
|
|
11
|
+
blink: boolean;
|
|
12
|
+
rapidBlink: boolean;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
export class ColoredConsole {
|
|
@@ -18,39 +21,29 @@ export class ColoredConsole {
|
|
|
18
21
|
foregroundColor: null,
|
|
19
22
|
backgroundColor: null,
|
|
20
23
|
carriageReturn: false,
|
|
24
|
+
lines: [],
|
|
21
25
|
secret: false,
|
|
26
|
+
blink: false,
|
|
27
|
+
rapidBlink: false,
|
|
22
28
|
};
|
|
23
29
|
|
|
24
30
|
constructor(public targetElement: HTMLElement) {}
|
|
25
31
|
|
|
26
32
|
logs(): string {
|
|
33
|
+
if (this.state.lines.length > 0) {
|
|
34
|
+
this.processLines();
|
|
35
|
+
}
|
|
27
36
|
return this.targetElement.innerText;
|
|
28
37
|
}
|
|
29
38
|
|
|
30
|
-
|
|
39
|
+
processLine(line: string): Element {
|
|
31
40
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences
|
|
32
41
|
// eslint-disable-next-line no-control-regex
|
|
33
42
|
const re = /(?:\x1B|\\x1B)(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1B\\))/g;
|
|
34
43
|
let i = 0;
|
|
35
44
|
|
|
36
|
-
if (this.state.carriageReturn) {
|
|
37
|
-
if (line !== "\n") {
|
|
38
|
-
// don't remove if \r\n
|
|
39
|
-
if (this.targetElement.lastChild) {
|
|
40
|
-
this.targetElement.removeChild(this.targetElement.lastChild);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
this.state.carriageReturn = false;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const hasBareCR = line.endsWith("\r") && !line.endsWith("\r\n");
|
|
47
|
-
if (hasBareCR) {
|
|
48
|
-
this.state.carriageReturn = true;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
45
|
const lineSpan = document.createElement("span");
|
|
52
46
|
lineSpan.classList.add("line");
|
|
53
|
-
this.targetElement.appendChild(lineSpan);
|
|
54
47
|
|
|
55
48
|
const addSpan = (content: string) => {
|
|
56
49
|
if (content === "") return;
|
|
@@ -61,6 +54,8 @@ export class ColoredConsole {
|
|
|
61
54
|
if (this.state.underline) span.classList.add("log-underline");
|
|
62
55
|
if (this.state.strikethrough) span.classList.add("log-strikethrough");
|
|
63
56
|
if (this.state.secret) span.classList.add("log-secret");
|
|
57
|
+
if (this.state.blink) span.classList.add("log-blink");
|
|
58
|
+
if (this.state.rapidBlink) span.classList.add("log-rapid-blink");
|
|
64
59
|
if (this.state.foregroundColor !== null)
|
|
65
60
|
span.classList.add(`log-fg-${this.state.foregroundColor}`);
|
|
66
61
|
if (this.state.backgroundColor !== null)
|
|
@@ -97,6 +92,8 @@ export class ColoredConsole {
|
|
|
97
92
|
this.state.foregroundColor = null;
|
|
98
93
|
this.state.backgroundColor = null;
|
|
99
94
|
this.state.secret = false;
|
|
95
|
+
this.state.blink = false;
|
|
96
|
+
this.state.rapidBlink = false;
|
|
100
97
|
break;
|
|
101
98
|
case 1:
|
|
102
99
|
this.state.bold = true;
|
|
@@ -108,10 +105,15 @@ export class ColoredConsole {
|
|
|
108
105
|
this.state.underline = true;
|
|
109
106
|
break;
|
|
110
107
|
case 5:
|
|
111
|
-
this.state.
|
|
108
|
+
this.state.blink = true;
|
|
109
|
+
this.state.rapidBlink = false;
|
|
112
110
|
break;
|
|
113
111
|
case 6:
|
|
114
|
-
this.state.
|
|
112
|
+
this.state.rapidBlink = true;
|
|
113
|
+
this.state.blink = false;
|
|
114
|
+
break;
|
|
115
|
+
case 8:
|
|
116
|
+
this.state.secret = true;
|
|
115
117
|
break;
|
|
116
118
|
case 9:
|
|
117
119
|
this.state.strikethrough = true;
|
|
@@ -125,6 +127,13 @@ export class ColoredConsole {
|
|
|
125
127
|
case 24:
|
|
126
128
|
this.state.underline = false;
|
|
127
129
|
break;
|
|
130
|
+
case 25:
|
|
131
|
+
this.state.blink = false;
|
|
132
|
+
this.state.rapidBlink = false;
|
|
133
|
+
break;
|
|
134
|
+
case 28:
|
|
135
|
+
this.state.secret = false;
|
|
136
|
+
break;
|
|
128
137
|
case 29:
|
|
129
138
|
this.state.strikethrough = false;
|
|
130
139
|
break;
|
|
@@ -155,6 +164,9 @@ export class ColoredConsole {
|
|
|
155
164
|
case 39:
|
|
156
165
|
this.state.foregroundColor = null;
|
|
157
166
|
break;
|
|
167
|
+
case 40:
|
|
168
|
+
this.state.backgroundColor = "black";
|
|
169
|
+
break;
|
|
158
170
|
case 41:
|
|
159
171
|
this.state.backgroundColor = "red";
|
|
160
172
|
break;
|
|
@@ -176,26 +188,69 @@ export class ColoredConsole {
|
|
|
176
188
|
case 47:
|
|
177
189
|
this.state.backgroundColor = "white";
|
|
178
190
|
break;
|
|
179
|
-
case 40:
|
|
180
|
-
this.state.backgroundColor = "black";
|
|
181
|
-
break;
|
|
182
191
|
case 49:
|
|
183
192
|
this.state.backgroundColor = null;
|
|
184
193
|
break;
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
196
|
}
|
|
197
|
+
addSpan(line.substring(i));
|
|
198
|
+
return lineSpan;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
processLines() {
|
|
188
202
|
const atBottom =
|
|
189
203
|
this.targetElement.scrollTop >
|
|
190
204
|
this.targetElement.scrollHeight - this.targetElement.offsetHeight - 50;
|
|
205
|
+
const prevCarriageReturn = this.state.carriageReturn;
|
|
206
|
+
const fragment = document.createDocumentFragment();
|
|
191
207
|
|
|
192
|
-
|
|
208
|
+
if (this.state.lines.length === 0) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for (const line of this.state.lines) {
|
|
213
|
+
// A lone \r is a pure carriage-return signal — update state but don't
|
|
214
|
+
// create a DOM node for it (it has no renderable content).
|
|
215
|
+
if (line === "\r") {
|
|
216
|
+
this.state.carriageReturn = true;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (this.state.carriageReturn && line !== "\n") {
|
|
220
|
+
if (fragment.childElementCount) {
|
|
221
|
+
fragment.removeChild(fragment.lastChild!);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const hadCarriageReturn = line.endsWith("\r");
|
|
225
|
+
fragment.appendChild(this.processLine(line.replace(/\r/g, "")));
|
|
226
|
+
this.state.carriageReturn = hadCarriageReturn;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (
|
|
230
|
+
prevCarriageReturn &&
|
|
231
|
+
fragment.childElementCount > 0 &&
|
|
232
|
+
this.targetElement.lastChild
|
|
233
|
+
) {
|
|
234
|
+
this.targetElement.replaceChild(fragment, this.targetElement.lastChild!);
|
|
235
|
+
} else {
|
|
236
|
+
this.targetElement.appendChild(fragment);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this.state.lines = [];
|
|
193
240
|
|
|
194
241
|
// Keep scroll at bottom
|
|
195
242
|
if (atBottom) {
|
|
196
243
|
this.targetElement.scrollTop = this.targetElement.scrollHeight;
|
|
197
244
|
}
|
|
198
245
|
}
|
|
246
|
+
|
|
247
|
+
addLine(line: string) {
|
|
248
|
+
// Processing of lines is deferred for performance reasons
|
|
249
|
+
if (this.state.lines.length === 0) {
|
|
250
|
+
setTimeout(() => this.processLines(), 0);
|
|
251
|
+
}
|
|
252
|
+
this.state.lines.push(line);
|
|
253
|
+
}
|
|
199
254
|
}
|
|
200
255
|
|
|
201
256
|
export const coloredConsoleStyles = `
|
|
@@ -229,6 +284,17 @@ export const coloredConsoleStyles = `
|
|
|
229
284
|
.log-underline.log-strikethrough {
|
|
230
285
|
text-decoration: underline line-through;
|
|
231
286
|
}
|
|
287
|
+
.log-blink {
|
|
288
|
+
animation: blink 1s step-end infinite;
|
|
289
|
+
}
|
|
290
|
+
.log-rapid-blink {
|
|
291
|
+
animation: blink 0.4s step-end infinite;
|
|
292
|
+
}
|
|
293
|
+
@keyframes blink {
|
|
294
|
+
50% {
|
|
295
|
+
opacity: 0;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
232
298
|
.log-secret {
|
|
233
299
|
-webkit-user-select: none;
|
|
234
300
|
-moz-user-select: none;
|
|
@@ -7,10 +7,24 @@ export class LineBreakTransformer implements Transformer<string, string> {
|
|
|
7
7
|
) {
|
|
8
8
|
// Append new chunks to existing chunks.
|
|
9
9
|
this.chunks += chunk;
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
// Split on \r\n, lone \r, or lone \n — capturing the separator so we can
|
|
11
|
+
// distinguish a lone \r (overwrite intent) from a normal newline.
|
|
12
|
+
const re = /\r\n|\r|\n/g;
|
|
13
|
+
let lastIndex = 0;
|
|
14
|
+
let match: RegExpExecArray | null;
|
|
15
|
+
while ((match = re.exec(this.chunks)) !== null) {
|
|
16
|
+
// If this is a lone \r at the very end of the buffer, leave it so it can
|
|
17
|
+
// be combined with a possible following \n in the next chunk.
|
|
18
|
+
if (match[0] === "\r" && match.index === this.chunks.length - 1) {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
const line = this.chunks.substring(lastIndex, match.index);
|
|
22
|
+
// Emit with \r suffix only for lone \r (overwrite), \n for everything else.
|
|
23
|
+
const suffix = match[0] === "\r" ? "\r" : "\n";
|
|
24
|
+
controller.enqueue(line + suffix);
|
|
25
|
+
lastIndex = re.lastIndex;
|
|
26
|
+
}
|
|
27
|
+
this.chunks = this.chunks.substring(lastIndex);
|
|
14
28
|
}
|
|
15
29
|
|
|
16
30
|
flush(controller: TransformStreamDefaultController<string>) {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Matches lines that already carry a wall-clock or tick timestamp so we don't
|
|
2
|
+
// add a redundant one. Intentionally does NOT match bare log-level prefixes
|
|
3
|
+
// like ESPHome's [I][tag:line]: — those have no time information.
|
|
4
|
+
//
|
|
5
|
+
// Covered formats:
|
|
6
|
+
// (123456) FreeRTOS ms-tick e.g. "(12345) "
|
|
7
|
+
// [HH:MM:SS] wall-clock bracket
|
|
8
|
+
// [HH:MM:SS.mmm] wall-clock bracket with millis
|
|
9
|
+
// I (1234) tag: ESP-IDF log level + tick e.g. "I (1234) wifi: ..."
|
|
10
|
+
// HH:MM:SS.mmm plain wall-clock
|
|
11
|
+
const DEVICE_TIMESTAMP_RE =
|
|
12
|
+
/^\s*(?:\(\d+\)\s|\[\d{2}:\d{2}:\d{2}(?:\.\d+)?\]|[DIWEACV] \(\d+\) \w|(?:\d{2}:){2}\d{2}\.\d)/;
|
|
13
|
+
|
|
14
|
+
export class TimestampTransformer implements Transformer<string, string> {
|
|
15
|
+
private deviceHasTimestamps = false;
|
|
16
|
+
|
|
17
|
+
transform(
|
|
18
|
+
chunk: string,
|
|
19
|
+
controller: TransformStreamDefaultController<string>,
|
|
20
|
+
) {
|
|
21
|
+
// Pass through pure newline / empty sentinel unchanged so that
|
|
22
|
+
// carriage-return overwrite logic in console-color.ts still works.
|
|
23
|
+
if (chunk === "" || chunk === "\n" || chunk === "\r") {
|
|
24
|
+
controller.enqueue(chunk);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!this.deviceHasTimestamps && DEVICE_TIMESTAMP_RE.test(chunk)) {
|
|
29
|
+
this.deviceHasTimestamps = true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (this.deviceHasTimestamps) {
|
|
33
|
+
controller.enqueue(chunk);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const date = new Date();
|
|
38
|
+
const h = date.getHours().toString().padStart(2, "0");
|
|
39
|
+
const m = date.getMinutes().toString().padStart(2, "0");
|
|
40
|
+
const s = date.getSeconds().toString().padStart(2, "0");
|
|
41
|
+
controller.enqueue(`[${h}:${m}:${s}] ${chunk}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
reset() {
|
|
45
|
+
this.deviceHasTimestamps = false;
|
|
46
|
+
}
|
|
47
|
+
}
|
package/sw.js
CHANGED