esp32tool 1.6.5 → 1.6.7
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/apple-touch-icon.png +0 -0
- package/css/style.css +325 -25
- package/dist/util/console-color.d.ts +5 -1
- package/dist/util/console-color.js +293 -15
- package/dist/util/timestamp-transformer.js +29 -8
- package/electron/cli-main.cjs +19 -19
- package/electron/main.cjs +167 -148
- package/electron/preload.js +16 -18
- 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/js/console.js +21 -12
- package/js/hex-editor.js +216 -163
- package/js/improv.js +59 -21
- package/js/nvs-editor.js +1189 -182
- package/js/script.js +1048 -845
- package/js/util/console-color.js +293 -15
- package/js/util/timestamp-transformer.js +29 -8
- package/js/webusb-serial.js +1075 -950
- package/package.cli.json +2 -2
- package/package.json +15 -16
- package/screenshots/desktop.png +0 -0
- package/screenshots/mobile.png +0 -0
- package/src/util/console-color.ts +290 -15
- package/src/util/timestamp-transformer.ts +32 -8
- package/sw.js +1 -1
package/package.cli.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "esp32tool",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.7",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "ESP32Tool - Standalone command-line tool (build-time config only)",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"usb": "^2.17.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"electron": "^
|
|
26
|
+
"electron": "^42.0.1"
|
|
27
27
|
},
|
|
28
28
|
"_comment": "This file is ONLY used during CLI builds. It is temporarily swapped with package.json by build-electron-cli.cjs, then restored. Never publish this file to npm. Keep version in sync with main package.json when releasing."
|
|
29
29
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "esp32tool",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Flash & Read ESP devices using WebSerial, Electron, and also Android mobile via WebUSB",
|
|
6
6
|
"main": "electron/main.cjs",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"build": "npm run prebuild && node update-sw-version.cjs && tsc --skipLibCheck && tsc -p tsconfig.util.json && rollup -c && node -e \"const fs=require('fs'); fs.readdirSync('dist/web').filter(f=>f.endsWith('.js')).forEach(f=>fs.copyFileSync('dist/web/'+f,'js/modules/'+f)); fs.renameSync('js/modules/index.js','js/modules/esptool.js');\" && node fix-cli-imports.cjs",
|
|
25
25
|
"build:binary": "node build-single-binary.cjs",
|
|
26
26
|
"build:cli-electron": "node build-electron-cli.cjs",
|
|
27
|
-
"format": "npm exec -- prettier --write src",
|
|
27
|
+
"format": "npm exec -- prettier --write src \"js/*.js\" electron css",
|
|
28
28
|
"dev:clean": "node -e \"const fs=require('fs'); fs.rmSync('dist',{recursive:true,force:true});\"",
|
|
29
29
|
"dev:tsc-once": "tsc",
|
|
30
30
|
"dev:tsc": "tsc --watch",
|
|
@@ -50,27 +50,26 @@
|
|
|
50
50
|
"@electron-forge/maker-zip": "^7.11.1",
|
|
51
51
|
"@electron-forge/plugin-auto-unpack-natives": "^7.11.1",
|
|
52
52
|
"@electron/fuses": "^2.1.1",
|
|
53
|
-
"@eslint/js": "^9.39.
|
|
53
|
+
"@eslint/js": "^9.39.4",
|
|
54
54
|
"@rollup/plugin-json": "^6.1.0",
|
|
55
|
-
"@rollup/plugin-node-resolve": "^16.0.
|
|
55
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
56
56
|
"@rollup/plugin-terser": "^1.0.0",
|
|
57
57
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
58
|
-
"@types/node": "^25.
|
|
58
|
+
"@types/node": "^25.7.0",
|
|
59
59
|
"@types/pako": "^2.0.4",
|
|
60
|
-
"@types/
|
|
61
|
-
"@types/w3c-web-serial": "^1.0.7",
|
|
60
|
+
"@types/w3c-web-serial": "^1.0.8",
|
|
62
61
|
"archiver": "^7.0.1",
|
|
63
|
-
"electron": "^
|
|
62
|
+
"electron": "^42.0.1",
|
|
64
63
|
"electron-squirrel-startup": "^1.0.1",
|
|
65
|
-
"eslint": "^10.
|
|
64
|
+
"eslint": "^10.3.0",
|
|
66
65
|
"eslint-config-prettier": "^10.1.8",
|
|
67
66
|
"eslint-plugin-prettier": "^5.5.5",
|
|
68
67
|
"npm-run-all": "^4.1.5",
|
|
69
|
-
"prettier": "^3.8.
|
|
70
|
-
"rollup": "^4.60.
|
|
68
|
+
"prettier": "^3.8.3",
|
|
69
|
+
"rollup": "^4.60.3",
|
|
71
70
|
"serve": "^14.2.6",
|
|
72
71
|
"typescript": "^5.9.3",
|
|
73
|
-
"typescript-eslint": "^8.
|
|
72
|
+
"typescript-eslint": "^8.59.1"
|
|
74
73
|
},
|
|
75
74
|
"dependencies": {
|
|
76
75
|
"pako": "^2.1.0",
|
|
@@ -78,11 +77,11 @@
|
|
|
78
77
|
"usb": "^2.17.0"
|
|
79
78
|
},
|
|
80
79
|
"overrides": {
|
|
81
|
-
"tmp": "^0.2.
|
|
82
|
-
"tar": "^7.5.
|
|
83
|
-
"minimatch": "^10.2.
|
|
80
|
+
"tmp": "^0.2.5",
|
|
81
|
+
"tar": "^7.5.13",
|
|
82
|
+
"minimatch": "^10.2.5",
|
|
84
83
|
"serve": {
|
|
85
|
-
"ajv": "^8.
|
|
84
|
+
"ajv": "^8.20.0"
|
|
86
85
|
},
|
|
87
86
|
"@electron/asar": "^4.0.1",
|
|
88
87
|
"serialize-javascript": "^7.0.3",
|
package/screenshots/desktop.png
CHANGED
|
Binary file
|
package/screenshots/mobile.png
CHANGED
|
Binary file
|
|
@@ -1,3 +1,58 @@
|
|
|
1
|
+
const ANSI_256: string[] = (() => {
|
|
2
|
+
const t: string[] = [];
|
|
3
|
+
// Standard colors 0-7
|
|
4
|
+
t[0] = "rgb(0,0,0)";
|
|
5
|
+
t[1] = "rgb(128,0,0)";
|
|
6
|
+
t[2] = "rgb(0,128,0)";
|
|
7
|
+
t[3] = "rgb(128,128,0)";
|
|
8
|
+
t[4] = "rgb(0,0,128)";
|
|
9
|
+
t[5] = "rgb(128,0,128)";
|
|
10
|
+
t[6] = "rgb(0,128,128)";
|
|
11
|
+
t[7] = "rgb(192,192,192)";
|
|
12
|
+
// Bright colors 8-15
|
|
13
|
+
t[8] = "rgb(128,128,128)";
|
|
14
|
+
t[9] = "rgb(255,0,0)";
|
|
15
|
+
t[10] = "rgb(0,255,0)";
|
|
16
|
+
t[11] = "rgb(255,255,0)";
|
|
17
|
+
t[12] = "rgb(99,153,255)";
|
|
18
|
+
t[13] = "rgb(255,0,255)";
|
|
19
|
+
t[14] = "rgb(0,255,255)";
|
|
20
|
+
t[15] = "rgb(255,255,255)";
|
|
21
|
+
// 6x6x6 color cube 16-231
|
|
22
|
+
for (let i = 0; i < 216; i++) {
|
|
23
|
+
const r = Math.floor(i / 36);
|
|
24
|
+
const g = Math.floor((i % 36) / 6);
|
|
25
|
+
const b = i % 6;
|
|
26
|
+
t[16 + i] =
|
|
27
|
+
"rgb(" +
|
|
28
|
+
(r ? r * 40 + 55 : 0) +
|
|
29
|
+
"," +
|
|
30
|
+
(g ? g * 40 + 55 : 0) +
|
|
31
|
+
"," +
|
|
32
|
+
(b ? b * 40 + 55 : 0) +
|
|
33
|
+
")";
|
|
34
|
+
}
|
|
35
|
+
// Grayscale ramp 232-255
|
|
36
|
+
for (let i = 0; i < 24; i++) {
|
|
37
|
+
const v = i * 10 + 8;
|
|
38
|
+
t[232 + i] = "rgb(" + v + "," + v + "," + v + ")";
|
|
39
|
+
}
|
|
40
|
+
return t;
|
|
41
|
+
})();
|
|
42
|
+
|
|
43
|
+
// Maps 256-color indices 0–7 to the named CSS class tokens so that
|
|
44
|
+
// \x1b[38;5;1m renders the same red as \x1b[31m.
|
|
45
|
+
const ANSI_NAMED: (string | null)[] = [
|
|
46
|
+
"black",
|
|
47
|
+
"red",
|
|
48
|
+
"green",
|
|
49
|
+
"yellow",
|
|
50
|
+
"blue",
|
|
51
|
+
"magenta",
|
|
52
|
+
"cyan",
|
|
53
|
+
"white",
|
|
54
|
+
];
|
|
55
|
+
|
|
1
56
|
interface ConsoleState {
|
|
2
57
|
bold: boolean;
|
|
3
58
|
italic: boolean;
|
|
@@ -5,6 +60,10 @@ interface ConsoleState {
|
|
|
5
60
|
strikethrough: boolean;
|
|
6
61
|
foregroundColor: string | null;
|
|
7
62
|
backgroundColor: string | null;
|
|
63
|
+
fgRgb: string | null;
|
|
64
|
+
bgRgb: string | null;
|
|
65
|
+
dim: boolean;
|
|
66
|
+
reverse: boolean;
|
|
8
67
|
carriageReturn: boolean;
|
|
9
68
|
lines: string[];
|
|
10
69
|
secret: boolean;
|
|
@@ -20,6 +79,10 @@ export class ColoredConsole {
|
|
|
20
79
|
strikethrough: false,
|
|
21
80
|
foregroundColor: null,
|
|
22
81
|
backgroundColor: null,
|
|
82
|
+
fgRgb: null,
|
|
83
|
+
bgRgb: null,
|
|
84
|
+
dim: false,
|
|
85
|
+
reverse: false,
|
|
23
86
|
carriageReturn: false,
|
|
24
87
|
lines: [],
|
|
25
88
|
secret: false,
|
|
@@ -39,27 +102,57 @@ export class ColoredConsole {
|
|
|
39
102
|
processLine(line: string): Element {
|
|
40
103
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences
|
|
41
104
|
// eslint-disable-next-line no-control-regex
|
|
42
|
-
const re = /(?:\x1B|\\x1B)(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1B\\))/g;
|
|
105
|
+
const re = /(?:\x1B|\\x1B)(?:\[(.*?)([@-~])|\].*?(?:\x07|\x1B\\))/g;
|
|
43
106
|
let i = 0;
|
|
44
107
|
|
|
45
108
|
const lineSpan = document.createElement("span");
|
|
46
109
|
lineSpan.classList.add("line");
|
|
47
110
|
|
|
48
111
|
const addSpan = (content: string) => {
|
|
49
|
-
if (content === "") return;
|
|
50
|
-
|
|
51
112
|
const span = document.createElement("span");
|
|
52
113
|
if (this.state.bold) span.classList.add("log-bold");
|
|
114
|
+
if (this.state.dim) span.classList.add("log-dim");
|
|
53
115
|
if (this.state.italic) span.classList.add("log-italic");
|
|
54
116
|
if (this.state.underline) span.classList.add("log-underline");
|
|
55
117
|
if (this.state.strikethrough) span.classList.add("log-strikethrough");
|
|
56
118
|
if (this.state.secret) span.classList.add("log-secret");
|
|
57
119
|
if (this.state.blink) span.classList.add("log-blink");
|
|
58
120
|
if (this.state.rapidBlink) span.classList.add("log-rapid-blink");
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
121
|
+
|
|
122
|
+
// Resolve colors with reverse-video support
|
|
123
|
+
let fgRgb = this.state.fgRgb;
|
|
124
|
+
let bgRgb = this.state.bgRgb;
|
|
125
|
+
let fg = this.state.foregroundColor;
|
|
126
|
+
let bg = this.state.backgroundColor;
|
|
127
|
+
|
|
128
|
+
if (this.state.reverse) {
|
|
129
|
+
fgRgb = this.state.bgRgb;
|
|
130
|
+
bgRgb = this.state.fgRgb;
|
|
131
|
+
fg = this.state.backgroundColor;
|
|
132
|
+
bg = this.state.foregroundColor;
|
|
133
|
+
// When one side is unset, fill in the terminal defaults so the
|
|
134
|
+
// swap is always visible (fg default=#ddd, bg default=#1c1c1c).
|
|
135
|
+
if (!fgRgb && !fg && !bgRgb && !bg) {
|
|
136
|
+
span.classList.add("log-reverse");
|
|
137
|
+
} else {
|
|
138
|
+
if (!fgRgb && !fg) fgRgb = "rgb(28,28,28)";
|
|
139
|
+
if (!bgRgb && !bg) bgRgb = "rgb(221,221,221)";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Inline rgb() style takes priority over CSS class
|
|
144
|
+
if (fgRgb) {
|
|
145
|
+
span.style.color = fgRgb;
|
|
146
|
+
} else if (fg !== null) {
|
|
147
|
+
span.classList.add(`log-fg-${fg}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (bgRgb) {
|
|
151
|
+
span.style.backgroundColor = bgRgb;
|
|
152
|
+
} else if (bg !== null) {
|
|
153
|
+
span.classList.add(`log-bg-${bg}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
63
156
|
span.appendChild(document.createTextNode(content));
|
|
64
157
|
lineSpan.appendChild(span);
|
|
65
158
|
|
|
@@ -79,18 +172,40 @@ export class ColoredConsole {
|
|
|
79
172
|
addSpan(line.substring(i, j));
|
|
80
173
|
i = j + match[0].length;
|
|
81
174
|
|
|
82
|
-
|
|
175
|
+
// Only process SGR sequences (final byte 'm'); skip cursor, erase, etc.
|
|
176
|
+
if (match[1] === undefined || match[2] !== "m") continue;
|
|
83
177
|
|
|
84
|
-
|
|
85
|
-
|
|
178
|
+
const rawCodes = match[1] === "" ? [""] : match[1].split(";");
|
|
179
|
+
const codes = [];
|
|
180
|
+
let invalidSgr = false;
|
|
181
|
+
for (const rawCode of rawCodes) {
|
|
182
|
+
if (rawCode === "") {
|
|
183
|
+
codes.push(0);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (!/^\d+$/.test(rawCode)) {
|
|
187
|
+
invalidSgr = true;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
codes.push(Number(rawCode));
|
|
191
|
+
}
|
|
192
|
+
if (invalidSgr) continue;
|
|
193
|
+
|
|
194
|
+
let ci = 0;
|
|
195
|
+
while (ci < codes.length) {
|
|
196
|
+
const code = codes[ci];
|
|
197
|
+
switch (code) {
|
|
86
198
|
case 0:
|
|
87
|
-
// reset
|
|
88
199
|
this.state.bold = false;
|
|
200
|
+
this.state.dim = false;
|
|
89
201
|
this.state.italic = false;
|
|
90
202
|
this.state.underline = false;
|
|
91
203
|
this.state.strikethrough = false;
|
|
92
204
|
this.state.foregroundColor = null;
|
|
93
205
|
this.state.backgroundColor = null;
|
|
206
|
+
this.state.fgRgb = null;
|
|
207
|
+
this.state.bgRgb = null;
|
|
208
|
+
this.state.reverse = false;
|
|
94
209
|
this.state.secret = false;
|
|
95
210
|
this.state.blink = false;
|
|
96
211
|
this.state.rapidBlink = false;
|
|
@@ -98,6 +213,9 @@ export class ColoredConsole {
|
|
|
98
213
|
case 1:
|
|
99
214
|
this.state.bold = true;
|
|
100
215
|
break;
|
|
216
|
+
case 2:
|
|
217
|
+
this.state.dim = true;
|
|
218
|
+
break;
|
|
101
219
|
case 3:
|
|
102
220
|
this.state.italic = true;
|
|
103
221
|
break;
|
|
@@ -112,6 +230,9 @@ export class ColoredConsole {
|
|
|
112
230
|
this.state.rapidBlink = true;
|
|
113
231
|
this.state.blink = false;
|
|
114
232
|
break;
|
|
233
|
+
case 7:
|
|
234
|
+
this.state.reverse = true;
|
|
235
|
+
break;
|
|
115
236
|
case 8:
|
|
116
237
|
this.state.secret = true;
|
|
117
238
|
break;
|
|
@@ -120,6 +241,7 @@ export class ColoredConsole {
|
|
|
120
241
|
break;
|
|
121
242
|
case 22:
|
|
122
243
|
this.state.bold = false;
|
|
244
|
+
this.state.dim = false;
|
|
123
245
|
break;
|
|
124
246
|
case 23:
|
|
125
247
|
this.state.italic = false;
|
|
@@ -131,6 +253,9 @@ export class ColoredConsole {
|
|
|
131
253
|
this.state.blink = false;
|
|
132
254
|
this.state.rapidBlink = false;
|
|
133
255
|
break;
|
|
256
|
+
case 27:
|
|
257
|
+
this.state.reverse = false;
|
|
258
|
+
break;
|
|
134
259
|
case 28:
|
|
135
260
|
this.state.secret = false;
|
|
136
261
|
break;
|
|
@@ -139,59 +264,206 @@ export class ColoredConsole {
|
|
|
139
264
|
break;
|
|
140
265
|
case 30:
|
|
141
266
|
this.state.foregroundColor = "black";
|
|
267
|
+
this.state.fgRgb = null;
|
|
142
268
|
break;
|
|
143
269
|
case 31:
|
|
144
270
|
this.state.foregroundColor = "red";
|
|
271
|
+
this.state.fgRgb = null;
|
|
145
272
|
break;
|
|
146
273
|
case 32:
|
|
147
274
|
this.state.foregroundColor = "green";
|
|
275
|
+
this.state.fgRgb = null;
|
|
148
276
|
break;
|
|
149
277
|
case 33:
|
|
150
278
|
this.state.foregroundColor = "yellow";
|
|
279
|
+
this.state.fgRgb = null;
|
|
151
280
|
break;
|
|
152
281
|
case 34:
|
|
153
282
|
this.state.foregroundColor = "blue";
|
|
283
|
+
this.state.fgRgb = null;
|
|
154
284
|
break;
|
|
155
285
|
case 35:
|
|
156
286
|
this.state.foregroundColor = "magenta";
|
|
287
|
+
this.state.fgRgb = null;
|
|
157
288
|
break;
|
|
158
289
|
case 36:
|
|
159
290
|
this.state.foregroundColor = "cyan";
|
|
291
|
+
this.state.fgRgb = null;
|
|
160
292
|
break;
|
|
161
293
|
case 37:
|
|
162
294
|
this.state.foregroundColor = "white";
|
|
295
|
+
this.state.fgRgb = null;
|
|
296
|
+
break;
|
|
297
|
+
case 38:
|
|
298
|
+
// Extended foreground: 38;5;n (256-color) or 38;2;r;g;b (true-color)
|
|
299
|
+
if (ci + 1 < codes.length) {
|
|
300
|
+
if (codes[ci + 1] === 5) {
|
|
301
|
+
if (ci + 2 < codes.length) {
|
|
302
|
+
const idx = codes[ci + 2];
|
|
303
|
+
if (idx >= 0 && idx <= 7 && ANSI_NAMED[idx]) {
|
|
304
|
+
this.state.foregroundColor = ANSI_NAMED[idx];
|
|
305
|
+
this.state.fgRgb = null;
|
|
306
|
+
} else if (idx >= 0 && idx <= 255 && ANSI_256[idx]) {
|
|
307
|
+
this.state.foregroundColor = null;
|
|
308
|
+
this.state.fgRgb = ANSI_256[idx];
|
|
309
|
+
}
|
|
310
|
+
ci += 2;
|
|
311
|
+
} else {
|
|
312
|
+
ci += 1;
|
|
313
|
+
}
|
|
314
|
+
} else if (codes[ci + 1] === 2) {
|
|
315
|
+
if (ci + 4 < codes.length) {
|
|
316
|
+
this.state.foregroundColor = null;
|
|
317
|
+
const r = Math.max(0, Math.min(255, codes[ci + 2]));
|
|
318
|
+
const g = Math.max(0, Math.min(255, codes[ci + 3]));
|
|
319
|
+
const b = Math.max(0, Math.min(255, codes[ci + 4]));
|
|
320
|
+
this.state.fgRgb = "rgb(" + r + "," + g + "," + b + ")";
|
|
321
|
+
ci += 4;
|
|
322
|
+
} else {
|
|
323
|
+
ci = codes.length - 1;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
163
327
|
break;
|
|
164
328
|
case 39:
|
|
165
329
|
this.state.foregroundColor = null;
|
|
330
|
+
this.state.fgRgb = null;
|
|
166
331
|
break;
|
|
167
332
|
case 40:
|
|
168
333
|
this.state.backgroundColor = "black";
|
|
334
|
+
this.state.bgRgb = null;
|
|
169
335
|
break;
|
|
170
336
|
case 41:
|
|
171
337
|
this.state.backgroundColor = "red";
|
|
338
|
+
this.state.bgRgb = null;
|
|
172
339
|
break;
|
|
173
340
|
case 42:
|
|
174
341
|
this.state.backgroundColor = "green";
|
|
342
|
+
this.state.bgRgb = null;
|
|
175
343
|
break;
|
|
176
344
|
case 43:
|
|
177
345
|
this.state.backgroundColor = "yellow";
|
|
346
|
+
this.state.bgRgb = null;
|
|
178
347
|
break;
|
|
179
348
|
case 44:
|
|
180
349
|
this.state.backgroundColor = "blue";
|
|
350
|
+
this.state.bgRgb = null;
|
|
181
351
|
break;
|
|
182
352
|
case 45:
|
|
183
353
|
this.state.backgroundColor = "magenta";
|
|
354
|
+
this.state.bgRgb = null;
|
|
184
355
|
break;
|
|
185
356
|
case 46:
|
|
186
357
|
this.state.backgroundColor = "cyan";
|
|
358
|
+
this.state.bgRgb = null;
|
|
187
359
|
break;
|
|
188
360
|
case 47:
|
|
189
361
|
this.state.backgroundColor = "white";
|
|
362
|
+
this.state.bgRgb = null;
|
|
363
|
+
break;
|
|
364
|
+
case 48:
|
|
365
|
+
// Extended background: 48;5;n (256-color) or 48;2;r;g;b (true-color)
|
|
366
|
+
if (ci + 1 < codes.length) {
|
|
367
|
+
if (codes[ci + 1] === 5) {
|
|
368
|
+
if (ci + 2 < codes.length) {
|
|
369
|
+
const idx = codes[ci + 2];
|
|
370
|
+
if (idx >= 0 && idx <= 7 && ANSI_NAMED[idx]) {
|
|
371
|
+
this.state.backgroundColor = ANSI_NAMED[idx];
|
|
372
|
+
this.state.bgRgb = null;
|
|
373
|
+
} else if (idx >= 0 && idx <= 255 && ANSI_256[idx]) {
|
|
374
|
+
this.state.backgroundColor = null;
|
|
375
|
+
this.state.bgRgb = ANSI_256[idx];
|
|
376
|
+
}
|
|
377
|
+
ci += 2;
|
|
378
|
+
} else {
|
|
379
|
+
ci += 1;
|
|
380
|
+
}
|
|
381
|
+
} else if (codes[ci + 1] === 2) {
|
|
382
|
+
if (ci + 4 < codes.length) {
|
|
383
|
+
this.state.backgroundColor = null;
|
|
384
|
+
const r = Math.max(0, Math.min(255, codes[ci + 2]));
|
|
385
|
+
const g = Math.max(0, Math.min(255, codes[ci + 3]));
|
|
386
|
+
const b = Math.max(0, Math.min(255, codes[ci + 4]));
|
|
387
|
+
this.state.bgRgb = "rgb(" + r + "," + g + "," + b + ")";
|
|
388
|
+
ci += 4;
|
|
389
|
+
} else {
|
|
390
|
+
ci = codes.length - 1;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
190
394
|
break;
|
|
191
395
|
case 49:
|
|
192
396
|
this.state.backgroundColor = null;
|
|
397
|
+
this.state.bgRgb = null;
|
|
398
|
+
break;
|
|
399
|
+
// Bright foreground colors
|
|
400
|
+
case 90:
|
|
401
|
+
this.state.foregroundColor = null;
|
|
402
|
+
this.state.fgRgb = ANSI_256[8];
|
|
403
|
+
break;
|
|
404
|
+
case 91:
|
|
405
|
+
this.state.foregroundColor = null;
|
|
406
|
+
this.state.fgRgb = ANSI_256[9];
|
|
407
|
+
break;
|
|
408
|
+
case 92:
|
|
409
|
+
this.state.foregroundColor = null;
|
|
410
|
+
this.state.fgRgb = ANSI_256[10];
|
|
411
|
+
break;
|
|
412
|
+
case 93:
|
|
413
|
+
this.state.foregroundColor = null;
|
|
414
|
+
this.state.fgRgb = ANSI_256[11];
|
|
415
|
+
break;
|
|
416
|
+
case 94:
|
|
417
|
+
this.state.foregroundColor = null;
|
|
418
|
+
this.state.fgRgb = ANSI_256[12];
|
|
419
|
+
break;
|
|
420
|
+
case 95:
|
|
421
|
+
this.state.foregroundColor = null;
|
|
422
|
+
this.state.fgRgb = ANSI_256[13];
|
|
423
|
+
break;
|
|
424
|
+
case 96:
|
|
425
|
+
this.state.foregroundColor = null;
|
|
426
|
+
this.state.fgRgb = ANSI_256[14];
|
|
427
|
+
break;
|
|
428
|
+
case 97:
|
|
429
|
+
this.state.foregroundColor = null;
|
|
430
|
+
this.state.fgRgb = ANSI_256[15];
|
|
431
|
+
break;
|
|
432
|
+
// Bright background colors
|
|
433
|
+
case 100:
|
|
434
|
+
this.state.backgroundColor = null;
|
|
435
|
+
this.state.bgRgb = ANSI_256[8];
|
|
436
|
+
break;
|
|
437
|
+
case 101:
|
|
438
|
+
this.state.backgroundColor = null;
|
|
439
|
+
this.state.bgRgb = ANSI_256[9];
|
|
440
|
+
break;
|
|
441
|
+
case 102:
|
|
442
|
+
this.state.backgroundColor = null;
|
|
443
|
+
this.state.bgRgb = ANSI_256[10];
|
|
444
|
+
break;
|
|
445
|
+
case 103:
|
|
446
|
+
this.state.backgroundColor = null;
|
|
447
|
+
this.state.bgRgb = ANSI_256[11];
|
|
448
|
+
break;
|
|
449
|
+
case 104:
|
|
450
|
+
this.state.backgroundColor = null;
|
|
451
|
+
this.state.bgRgb = ANSI_256[12];
|
|
452
|
+
break;
|
|
453
|
+
case 105:
|
|
454
|
+
this.state.backgroundColor = null;
|
|
455
|
+
this.state.bgRgb = ANSI_256[13];
|
|
456
|
+
break;
|
|
457
|
+
case 106:
|
|
458
|
+
this.state.backgroundColor = null;
|
|
459
|
+
this.state.bgRgb = ANSI_256[14];
|
|
460
|
+
break;
|
|
461
|
+
case 107:
|
|
462
|
+
this.state.backgroundColor = null;
|
|
463
|
+
this.state.bgRgb = ANSI_256[15];
|
|
193
464
|
break;
|
|
194
465
|
}
|
|
466
|
+
ci++;
|
|
195
467
|
}
|
|
196
468
|
}
|
|
197
469
|
addSpan(line.substring(i));
|
|
@@ -210,8 +482,6 @@ export class ColoredConsole {
|
|
|
210
482
|
}
|
|
211
483
|
|
|
212
484
|
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
485
|
if (line === "\r") {
|
|
216
486
|
this.state.carriageReturn = true;
|
|
217
487
|
continue;
|
|
@@ -238,14 +508,12 @@ export class ColoredConsole {
|
|
|
238
508
|
|
|
239
509
|
this.state.lines = [];
|
|
240
510
|
|
|
241
|
-
// Keep scroll at bottom
|
|
242
511
|
if (atBottom) {
|
|
243
512
|
this.targetElement.scrollTop = this.targetElement.scrollHeight;
|
|
244
513
|
}
|
|
245
514
|
}
|
|
246
515
|
|
|
247
516
|
addLine(line: string) {
|
|
248
|
-
// Processing of lines is deferred for performance reasons
|
|
249
517
|
if (this.state.lines.length === 0) {
|
|
250
518
|
setTimeout(() => this.processLines(), 0);
|
|
251
519
|
}
|
|
@@ -272,6 +540,9 @@ export const coloredConsoleStyles = `
|
|
|
272
540
|
.log-bold {
|
|
273
541
|
font-weight: bold;
|
|
274
542
|
}
|
|
543
|
+
.log-dim {
|
|
544
|
+
opacity: 0.5;
|
|
545
|
+
}
|
|
275
546
|
.log-italic {
|
|
276
547
|
font-style: italic;
|
|
277
548
|
}
|
|
@@ -306,6 +577,10 @@ export const coloredConsoleStyles = `
|
|
|
306
577
|
width: 1px;
|
|
307
578
|
font-size: 1px;
|
|
308
579
|
}
|
|
580
|
+
.log-reverse {
|
|
581
|
+
background: #ddd;
|
|
582
|
+
color: #1c1c1c;
|
|
583
|
+
}
|
|
309
584
|
.log-fg-black {
|
|
310
585
|
color: rgb(128, 128, 128);
|
|
311
586
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
// Matches lines that already carry a wall-clock
|
|
2
|
-
//
|
|
3
|
-
// like
|
|
4
|
-
//
|
|
1
|
+
// Matches lines that already carry a wall-clock timestamp so we don't add a
|
|
2
|
+
// redundant one. Only real wall-clock formats are matched — tick-based
|
|
3
|
+
// formats like FreeRTOS "(12345)" or ESP-IDF "I (15) boot:" are NOT matched
|
|
4
|
+
// because they don't carry time-of-day information
|
|
5
5
|
// Covered formats:
|
|
6
|
-
// (123456) FreeRTOS ms-tick e.g. "(12345) "
|
|
7
6
|
// [HH:MM:SS] wall-clock bracket
|
|
8
7
|
// [HH:MM:SS.mmm] wall-clock bracket with millis
|
|
9
|
-
// I (1234) tag: ESP-IDF log level + tick e.g. "I (1234) wifi: ..."
|
|
10
8
|
// HH:MM:SS.mmm plain wall-clock
|
|
11
9
|
const DEVICE_TIMESTAMP_RE =
|
|
12
|
-
/^\s*(?:\
|
|
10
|
+
/^\s*(?:\[\d{2}:\d{2}:\d{2}(?:\.\d+)?\]|(?:\d{2}:){2}\d{2}\.\d)/;
|
|
11
|
+
|
|
12
|
+
// Matches leading ANSI SGR (color/style) codes at the start of a string
|
|
13
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences
|
|
14
|
+
// eslint-disable-next-line no-control-regex
|
|
15
|
+
const LEADING_ANSI_RE = /^(\x1b\[(?:\d+;)*\d*m)+/;
|
|
13
16
|
|
|
14
17
|
export class TimestampTransformer implements Transformer<string, string> {
|
|
15
18
|
private deviceHasTimestamps = false;
|
|
@@ -34,11 +37,32 @@ export class TimestampTransformer implements Transformer<string, string> {
|
|
|
34
37
|
return;
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
// Extract leading ANSI codes to preserve them across line splits
|
|
41
|
+
const ansiMatch = chunk.match(LEADING_ANSI_RE);
|
|
42
|
+
const leadingAnsi = ansiMatch ? ansiMatch[0] : "";
|
|
43
|
+
const contentWithoutAnsi = leadingAnsi
|
|
44
|
+
? chunk.slice(leadingAnsi.length)
|
|
45
|
+
: chunk;
|
|
46
|
+
|
|
37
47
|
const date = new Date();
|
|
38
48
|
const h = date.getHours().toString().padStart(2, "0");
|
|
39
49
|
const m = date.getMinutes().toString().padStart(2, "0");
|
|
40
50
|
const s = date.getSeconds().toString().padStart(2, "0");
|
|
41
|
-
|
|
51
|
+
const timestamp = `[${h}:${m}:${s}]`;
|
|
52
|
+
|
|
53
|
+
// For multi-line chunks, we need to preserve ANSI codes on each line
|
|
54
|
+
// Split on newlines, but keep the newline characters
|
|
55
|
+
const lines = contentWithoutAnsi.split(/(\r?\n)/);
|
|
56
|
+
let result = "";
|
|
57
|
+
for (const part of lines) {
|
|
58
|
+
if (part === "\n" || part === "\r\n") {
|
|
59
|
+
result += part;
|
|
60
|
+
} else if (part !== "") {
|
|
61
|
+
result += leadingAnsi + timestamp + " " + part;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
controller.enqueue(result);
|
|
42
66
|
}
|
|
43
67
|
|
|
44
68
|
reset() {
|
package/sw.js
CHANGED