esp32tool 1.6.4 → 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.
@@ -1,210 +1,235 @@
1
1
  export class ColoredConsole {
2
- constructor(targetElement) {
3
- this.targetElement = targetElement;
4
- this.state = {
5
- bold: false,
6
- italic: false,
7
- underline: false,
8
- strikethrough: false,
9
- foregroundColor: null,
10
- backgroundColor: null,
11
- carriageReturn: false,
12
- secret: false,
13
- blink: false,
14
- rapidBlink: false,
15
- };
16
- }
17
-
18
- logs() {
19
- return this.targetElement.innerText;
20
- }
21
-
22
- addLine(line) {
23
- // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences
24
- const re = /(?:\x1B|\\x1B)(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1B\\))/g;
25
- let i = 0;
26
-
27
- if (this.state.carriageReturn) {
28
- if (line !== "\n") {
29
- // don't remove if \r\n
30
- if (this.targetElement.lastChild) {
31
- this.targetElement.removeChild(this.targetElement.lastChild);
2
+ constructor(targetElement) {
3
+ this.targetElement = targetElement;
4
+ this.state = {
5
+ bold: false,
6
+ italic: false,
7
+ underline: false,
8
+ strikethrough: false,
9
+ foregroundColor: null,
10
+ backgroundColor: null,
11
+ carriageReturn: false,
12
+ lines: [],
13
+ secret: false,
14
+ blink: false,
15
+ rapidBlink: false,
16
+ };
17
+ }
18
+ logs() {
19
+ if (this.state.lines.length > 0) {
20
+ this.processLines();
32
21
  }
33
- }
34
- this.state.carriageReturn = false;
22
+ return this.targetElement.innerText;
35
23
  }
36
-
37
- if (line.includes("\r")) {
38
- this.state.carriageReturn = true;
24
+ processLine(line) {
25
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences
26
+ // eslint-disable-next-line no-control-regex
27
+ const re = /(?:\x1B|\\x1B)(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1B\\))/g;
28
+ let i = 0;
29
+ const lineSpan = document.createElement("span");
30
+ lineSpan.classList.add("line");
31
+ const addSpan = (content) => {
32
+ if (content === "")
33
+ return;
34
+ const span = document.createElement("span");
35
+ if (this.state.bold)
36
+ span.classList.add("log-bold");
37
+ if (this.state.italic)
38
+ span.classList.add("log-italic");
39
+ if (this.state.underline)
40
+ span.classList.add("log-underline");
41
+ if (this.state.strikethrough)
42
+ span.classList.add("log-strikethrough");
43
+ if (this.state.secret)
44
+ span.classList.add("log-secret");
45
+ if (this.state.blink)
46
+ span.classList.add("log-blink");
47
+ if (this.state.rapidBlink)
48
+ span.classList.add("log-rapid-blink");
49
+ if (this.state.foregroundColor !== null)
50
+ span.classList.add(`log-fg-${this.state.foregroundColor}`);
51
+ if (this.state.backgroundColor !== null)
52
+ span.classList.add(`log-bg-${this.state.backgroundColor}`);
53
+ span.appendChild(document.createTextNode(content));
54
+ lineSpan.appendChild(span);
55
+ if (this.state.secret) {
56
+ const redacted = document.createElement("span");
57
+ redacted.classList.add("log-secret-redacted");
58
+ redacted.appendChild(document.createTextNode("[redacted]"));
59
+ lineSpan.appendChild(redacted);
60
+ }
61
+ };
62
+ while (true) {
63
+ const match = re.exec(line);
64
+ if (match === null)
65
+ break;
66
+ const j = match.index;
67
+ addSpan(line.substring(i, j));
68
+ i = j + match[0].length;
69
+ if (match[1] === undefined)
70
+ continue;
71
+ for (const colorCode of match[1].split(";")) {
72
+ switch (parseInt(colorCode)) {
73
+ case 0:
74
+ // reset
75
+ this.state.bold = false;
76
+ this.state.italic = false;
77
+ this.state.underline = false;
78
+ this.state.strikethrough = false;
79
+ this.state.foregroundColor = null;
80
+ this.state.backgroundColor = null;
81
+ this.state.secret = false;
82
+ this.state.blink = false;
83
+ this.state.rapidBlink = false;
84
+ break;
85
+ case 1:
86
+ this.state.bold = true;
87
+ break;
88
+ case 3:
89
+ this.state.italic = true;
90
+ break;
91
+ case 4:
92
+ this.state.underline = true;
93
+ break;
94
+ case 5:
95
+ this.state.blink = true;
96
+ this.state.rapidBlink = false;
97
+ break;
98
+ case 6:
99
+ this.state.rapidBlink = true;
100
+ this.state.blink = false;
101
+ break;
102
+ case 8:
103
+ this.state.secret = true;
104
+ break;
105
+ case 9:
106
+ this.state.strikethrough = true;
107
+ break;
108
+ case 22:
109
+ this.state.bold = false;
110
+ break;
111
+ case 23:
112
+ this.state.italic = false;
113
+ break;
114
+ case 24:
115
+ this.state.underline = false;
116
+ break;
117
+ case 25:
118
+ this.state.blink = false;
119
+ this.state.rapidBlink = false;
120
+ break;
121
+ case 28:
122
+ this.state.secret = false;
123
+ break;
124
+ case 29:
125
+ this.state.strikethrough = false;
126
+ break;
127
+ case 30:
128
+ this.state.foregroundColor = "black";
129
+ break;
130
+ case 31:
131
+ this.state.foregroundColor = "red";
132
+ break;
133
+ case 32:
134
+ this.state.foregroundColor = "green";
135
+ break;
136
+ case 33:
137
+ this.state.foregroundColor = "yellow";
138
+ break;
139
+ case 34:
140
+ this.state.foregroundColor = "blue";
141
+ break;
142
+ case 35:
143
+ this.state.foregroundColor = "magenta";
144
+ break;
145
+ case 36:
146
+ this.state.foregroundColor = "cyan";
147
+ break;
148
+ case 37:
149
+ this.state.foregroundColor = "white";
150
+ break;
151
+ case 39:
152
+ this.state.foregroundColor = null;
153
+ break;
154
+ case 40:
155
+ this.state.backgroundColor = "black";
156
+ break;
157
+ case 41:
158
+ this.state.backgroundColor = "red";
159
+ break;
160
+ case 42:
161
+ this.state.backgroundColor = "green";
162
+ break;
163
+ case 43:
164
+ this.state.backgroundColor = "yellow";
165
+ break;
166
+ case 44:
167
+ this.state.backgroundColor = "blue";
168
+ break;
169
+ case 45:
170
+ this.state.backgroundColor = "magenta";
171
+ break;
172
+ case 46:
173
+ this.state.backgroundColor = "cyan";
174
+ break;
175
+ case 47:
176
+ this.state.backgroundColor = "white";
177
+ break;
178
+ case 49:
179
+ this.state.backgroundColor = null;
180
+ break;
181
+ }
182
+ }
183
+ }
184
+ addSpan(line.substring(i));
185
+ return lineSpan;
39
186
  }
40
-
41
- const lineSpan = document.createElement("span");
42
- lineSpan.classList.add("line");
43
- this.targetElement.appendChild(lineSpan);
44
-
45
- const addSpan = (content) => {
46
- if (content === "") return;
47
-
48
- const span = document.createElement("span");
49
- if (this.state.bold) span.classList.add("log-bold");
50
- if (this.state.italic) span.classList.add("log-italic");
51
- if (this.state.underline) span.classList.add("log-underline");
52
- if (this.state.strikethrough) span.classList.add("log-strikethrough");
53
- if (this.state.secret) span.classList.add("log-secret");
54
- if (this.state.blink) span.classList.add("log-blink");
55
- if (this.state.rapidBlink) span.classList.add("log-rapid-blink");
56
- if (this.state.foregroundColor !== null)
57
- span.classList.add(`log-fg-${this.state.foregroundColor}`);
58
- if (this.state.backgroundColor !== null)
59
- span.classList.add(`log-bg-${this.state.backgroundColor}`);
60
- span.appendChild(document.createTextNode(content));
61
- lineSpan.appendChild(span);
62
-
63
- if (this.state.secret) {
64
- const redacted = document.createElement("span");
65
- redacted.classList.add("log-secret-redacted");
66
- redacted.appendChild(document.createTextNode("[redacted]"));
67
- lineSpan.appendChild(redacted);
68
- }
69
- };
70
-
71
- while (true) {
72
- const match = re.exec(line);
73
- if (match === null) break;
74
-
75
- const j = match.index;
76
- addSpan(line.substring(i, j));
77
- i = j + match[0].length;
78
-
79
- if (match[1] === undefined) continue;
80
-
81
- for (const colorCode of match[1].split(";")) {
82
- switch (parseInt(colorCode)) {
83
- case 0:
84
- // reset
85
- this.state.bold = false;
86
- this.state.italic = false;
87
- this.state.underline = false;
88
- this.state.strikethrough = false;
89
- this.state.foregroundColor = null;
90
- this.state.backgroundColor = null;
91
- this.state.secret = false;
92
- this.state.blink = false;
93
- this.state.rapidBlink = false;
94
- break;
95
- case 1:
96
- this.state.bold = true;
97
- break;
98
- case 3:
99
- this.state.italic = true;
100
- break;
101
- case 4:
102
- this.state.underline = true;
103
- break;
104
- case 5:
105
- this.state.blink = true;
106
- break;
107
- case 6:
108
- this.state.rapidBlink = true;
109
- break;
110
- case 8:
111
- this.state.secret = true;
112
- break;
113
- case 9:
114
- this.state.strikethrough = true;
115
- break;
116
- case 22:
117
- this.state.bold = false;
118
- break;
119
- case 23:
120
- this.state.italic = false;
121
- break;
122
- case 24:
123
- this.state.underline = false;
124
- break;
125
- case 25:
126
- this.state.blink = false;
127
- this.state.rapidBlink = false;
128
- break;
129
- case 28:
130
- this.state.secret = false;
131
- break;
132
- case 29:
133
- this.state.strikethrough = false;
134
- break;
135
- case 30:
136
- this.state.foregroundColor = "black";
137
- break;
138
- case 31:
139
- this.state.foregroundColor = "red";
140
- break;
141
- case 32:
142
- this.state.foregroundColor = "green";
143
- break;
144
- case 33:
145
- this.state.foregroundColor = "yellow";
146
- break;
147
- case 34:
148
- this.state.foregroundColor = "blue";
149
- break;
150
- case 35:
151
- this.state.foregroundColor = "magenta";
152
- break;
153
- case 36:
154
- this.state.foregroundColor = "cyan";
155
- break;
156
- case 37:
157
- this.state.foregroundColor = "white";
158
- break;
159
- case 39:
160
- this.state.foregroundColor = null;
161
- break;
162
- case 41:
163
- this.state.backgroundColor = "red";
164
- break;
165
- case 42:
166
- this.state.backgroundColor = "green";
167
- break;
168
- case 43:
169
- this.state.backgroundColor = "yellow";
170
- break;
171
- case 44:
172
- this.state.backgroundColor = "blue";
173
- break;
174
- case 45:
175
- this.state.backgroundColor = "magenta";
176
- break;
177
- case 46:
178
- this.state.backgroundColor = "cyan";
179
- break;
180
- case 47:
181
- this.state.backgroundColor = "white";
182
- break;
183
- case 40:
184
- this.state.backgroundColor = "black";
185
- break;
186
- case 49:
187
- this.state.backgroundColor = null;
188
- break;
187
+ processLines() {
188
+ const atBottom = this.targetElement.scrollTop >
189
+ this.targetElement.scrollHeight - this.targetElement.offsetHeight - 50;
190
+ const prevCarriageReturn = this.state.carriageReturn;
191
+ const fragment = document.createDocumentFragment();
192
+ if (this.state.lines.length === 0) {
193
+ return;
194
+ }
195
+ for (const line of this.state.lines) {
196
+ // A lone \r is a pure carriage-return signal — update state but don't
197
+ // create a DOM node for it (it has no renderable content).
198
+ if (line === "\r") {
199
+ this.state.carriageReturn = true;
200
+ continue;
201
+ }
202
+ if (this.state.carriageReturn && line !== "\n") {
203
+ if (fragment.childElementCount) {
204
+ fragment.removeChild(fragment.lastChild);
205
+ }
206
+ }
207
+ const hadCarriageReturn = line.endsWith("\r");
208
+ fragment.appendChild(this.processLine(line.replace(/\r/g, "")));
209
+ this.state.carriageReturn = hadCarriageReturn;
210
+ }
211
+ if (prevCarriageReturn &&
212
+ fragment.childElementCount > 0 &&
213
+ this.targetElement.lastChild) {
214
+ this.targetElement.replaceChild(fragment, this.targetElement.lastChild);
215
+ }
216
+ else {
217
+ this.targetElement.appendChild(fragment);
218
+ }
219
+ this.state.lines = [];
220
+ // Keep scroll at bottom
221
+ if (atBottom) {
222
+ this.targetElement.scrollTop = this.targetElement.scrollHeight;
189
223
  }
190
- }
191
224
  }
192
-
193
- // Use percentage-based threshold (5% of viewport height) for better UX across screen sizes
194
- const scrollThreshold = this.targetElement.offsetHeight * 0.05;
195
- const atBottom =
196
- this.targetElement.scrollTop >
197
- this.targetElement.scrollHeight - this.targetElement.offsetHeight - scrollThreshold;
198
-
199
- addSpan(line.substring(i));
200
-
201
- // Keep scroll at bottom
202
- if (atBottom) {
203
- this.targetElement.scrollTop = this.targetElement.scrollHeight;
225
+ addLine(line) {
226
+ // Processing of lines is deferred for performance reasons
227
+ if (this.state.lines.length === 0) {
228
+ setTimeout(() => this.processLines(), 0);
229
+ }
230
+ this.state.lines.push(line);
204
231
  }
205
- }
206
232
  }
207
-
208
233
  export const coloredConsoleStyles = `
209
234
  .log {
210
235
  flex: 1;
@@ -215,11 +240,10 @@ export const coloredConsoleStyles = `
215
240
  padding: 16px;
216
241
  overflow: auto;
217
242
  line-height: 1.45;
218
- border-radius: 0;
243
+ border-radius: 3px;
219
244
  white-space: pre-wrap;
220
245
  overflow-wrap: break-word;
221
246
  color: #ddd;
222
- min-height: 0;
223
247
  }
224
248
 
225
249
  .log-bold {
@@ -237,6 +261,17 @@ export const coloredConsoleStyles = `
237
261
  .log-underline.log-strikethrough {
238
262
  text-decoration: underline line-through;
239
263
  }
264
+ .log-blink {
265
+ animation: blink 1s step-end infinite;
266
+ }
267
+ .log-rapid-blink {
268
+ animation: blink 0.4s step-end infinite;
269
+ }
270
+ @keyframes blink {
271
+ 50% {
272
+ opacity: 0;
273
+ }
274
+ }
240
275
  .log-secret {
241
276
  -webkit-user-select: none;
242
277
  -moz-user-select: none;
@@ -248,17 +283,6 @@ export const coloredConsoleStyles = `
248
283
  width: 1px;
249
284
  font-size: 1px;
250
285
  }
251
- .log-blink {
252
- animation: blink 1s step-start infinite;
253
- }
254
- .log-rapid-blink {
255
- animation: blink 0.3s step-start infinite;
256
- }
257
- @keyframes blink {
258
- 50% {
259
- opacity: 0;
260
- }
261
- }
262
286
  .log-fg-black {
263
287
  color: rgb(128, 128, 128);
264
288
  }
@@ -1,19 +1,31 @@
1
1
  export class LineBreakTransformer {
2
- constructor() {
3
- this.chunks = "";
4
- }
5
-
6
- transform(chunk, controller) {
7
- // Append new chunks to existing chunks.
8
- this.chunks += chunk;
9
- // For each line breaks in chunks, send the parsed lines out.
10
- const lines = this.chunks.split("\r\n");
11
- this.chunks = lines.pop();
12
- lines.forEach((line) => controller.enqueue(line + "\r\n"));
13
- }
14
-
15
- flush(controller) {
16
- // When the stream is closed, flush any remaining chunks out.
17
- controller.enqueue(this.chunks);
18
- }
2
+ constructor() {
3
+ this.chunks = "";
4
+ }
5
+ transform(chunk, controller) {
6
+ // Append new chunks to existing chunks.
7
+ this.chunks += chunk;
8
+ // Split on \r\n, lone \r, or lone \n — capturing the separator so we can
9
+ // distinguish a lone \r (overwrite intent) from a normal newline.
10
+ const re = /\r\n|\r|\n/g;
11
+ let lastIndex = 0;
12
+ let match;
13
+ while ((match = re.exec(this.chunks)) !== null) {
14
+ // If this is a lone \r at the very end of the buffer, leave it so it can
15
+ // be combined with a possible following \n in the next chunk.
16
+ if (match[0] === "\r" && match.index === this.chunks.length - 1) {
17
+ break;
18
+ }
19
+ const line = this.chunks.substring(lastIndex, match.index);
20
+ // Emit with \r suffix only for lone \r (overwrite), \n for everything else.
21
+ const suffix = match[0] === "\r" ? "\r" : "\n";
22
+ controller.enqueue(line + suffix);
23
+ lastIndex = re.lastIndex;
24
+ }
25
+ this.chunks = this.chunks.substring(lastIndex);
26
+ }
27
+ flush(controller) {
28
+ // When the stream is closed, flush any remaining chunks out.
29
+ controller.enqueue(this.chunks);
30
+ }
19
31
  }
@@ -0,0 +1,39 @@
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 = /^\s*(?:\(\d+\)\s|\[\d{2}:\d{2}:\d{2}(?:\.\d+)?\]|[DIWEACV] \(\d+\) \w|(?:\d{2}:){2}\d{2}\.\d)/;
12
+ export class TimestampTransformer {
13
+ constructor() {
14
+ this.deviceHasTimestamps = false;
15
+ }
16
+ transform(chunk, controller) {
17
+ // Pass through pure newline / empty sentinel unchanged so that
18
+ // carriage-return overwrite logic in console-color.ts still works.
19
+ if (chunk === "" || chunk === "\n" || chunk === "\r") {
20
+ controller.enqueue(chunk);
21
+ return;
22
+ }
23
+ if (!this.deviceHasTimestamps && DEVICE_TIMESTAMP_RE.test(chunk)) {
24
+ this.deviceHasTimestamps = true;
25
+ }
26
+ if (this.deviceHasTimestamps) {
27
+ controller.enqueue(chunk);
28
+ return;
29
+ }
30
+ const date = new Date();
31
+ const h = date.getHours().toString().padStart(2, "0");
32
+ const m = date.getMinutes().toString().padStart(2, "0");
33
+ const s = date.getSeconds().toString().padStart(2, "0");
34
+ controller.enqueue(`[${h}:${m}:${s}] ${chunk}`);
35
+ }
36
+ reset() {
37
+ this.deviceHasTimestamps = false;
38
+ }
39
+ }
package/package.cli.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esp32tool",
3
- "version": "1.6.3",
3
+ "version": "1.6.5",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "ESP32Tool - Standalone command-line tool (build-time config only)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esp32tool",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
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",
@@ -20,14 +20,15 @@
20
20
  "node": ">=25.4.0"
21
21
  },
22
22
  "scripts": {
23
- "prebuild": "node -e \"const fs=require('fs'); fs.rmSync('dist',{recursive:true,force:true}); fs.rmSync('js/modules',{recursive:true,force:true}); fs.mkdirSync('js/modules',{recursive:true});\"",
24
- "build": "npm run prebuild && node update-sw-version.cjs && tsc --skipLibCheck && 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",
23
+ "prebuild": "node -e \"const fs=require('fs'); fs.rmSync('dist',{recursive:true,force:true}); fs.rmSync('js/modules',{recursive:true,force:true}); fs.rmSync('js/util',{recursive:true,force:true}); fs.mkdirSync('js/modules',{recursive:true}); fs.mkdirSync('js/util',{recursive:true});\"",
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
27
  "format": "npm exec -- prettier --write src",
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",
31
+ "dev:tsc-util": "tsc -p tsconfig.util.json --watch",
31
32
  "dev:rollup": "rollup -c --watch",
32
33
  "dev:serve": "serve -p 5004",
33
34
  "develop": "npm run dev:clean && npm run dev:tsc-once && npm-run-all --parallel dev:serve dev:tsc dev:rollup",
@@ -54,14 +55,14 @@
54
55
  "@rollup/plugin-node-resolve": "^16.0.0",
55
56
  "@rollup/plugin-terser": "^1.0.0",
56
57
  "@rollup/plugin-typescript": "^12.3.0",
57
- "@types/node": "^25.4.0",
58
+ "@types/node": "^25.5.2",
58
59
  "@types/pako": "^2.0.4",
59
60
  "@types/serialport": "^10.2.0",
60
61
  "@types/w3c-web-serial": "^1.0.7",
61
62
  "archiver": "^7.0.1",
62
63
  "electron": "^41.1.0",
63
64
  "electron-squirrel-startup": "^1.0.1",
64
- "eslint": "^10.0.2",
65
+ "eslint": "^10.2.0",
65
66
  "eslint-config-prettier": "^10.1.8",
66
67
  "eslint-plugin-prettier": "^5.5.5",
67
68
  "npm-run-all": "^4.1.5",
@@ -69,7 +70,7 @@
69
70
  "rollup": "^4.60.1",
70
71
  "serve": "^14.2.6",
71
72
  "typescript": "^5.9.3",
72
- "typescript-eslint": "^8.57.2"
73
+ "typescript-eslint": "^8.58.0"
73
74
  },
74
75
  "dependencies": {
75
76
  "pako": "^2.1.0",
Binary file
Binary file