esp32tool 1.1.9 → 1.3.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.
Files changed (64) hide show
  1. package/.nojekyll +0 -0
  2. package/README.md +100 -6
  3. package/apple-touch-icon.png +0 -0
  4. package/build-electron-cli.cjs +177 -0
  5. package/build-single-binary.cjs +295 -0
  6. package/css/light.css +11 -0
  7. package/css/style.css +261 -41
  8. package/dist/cli.d.ts +17 -0
  9. package/dist/cli.js +458 -0
  10. package/dist/console.d.ts +15 -0
  11. package/dist/console.js +237 -0
  12. package/dist/const.d.ts +99 -0
  13. package/dist/const.js +129 -8
  14. package/dist/esp_loader.d.ts +244 -22
  15. package/dist/esp_loader.js +1960 -251
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.js +37 -4
  18. package/dist/node-usb-adapter.d.ts +47 -0
  19. package/dist/node-usb-adapter.js +725 -0
  20. package/dist/stubs/index.d.ts +1 -2
  21. package/dist/stubs/index.js +4 -0
  22. package/dist/util/console-color.d.ts +19 -0
  23. package/dist/util/console-color.js +272 -0
  24. package/dist/util/line-break-transformer.d.ts +5 -0
  25. package/dist/util/line-break-transformer.js +17 -0
  26. package/dist/web/index.js +1 -1
  27. package/electron/cli-main.cjs +74 -0
  28. package/electron/main.cjs +338 -0
  29. package/electron/main.js +7 -2
  30. package/favicon.ico +0 -0
  31. package/fix-cli-imports.cjs +127 -0
  32. package/generate-icons.sh +89 -0
  33. package/icons/icon-128.png +0 -0
  34. package/icons/icon-144.png +0 -0
  35. package/icons/icon-152.png +0 -0
  36. package/icons/icon-192.png +0 -0
  37. package/icons/icon-384.png +0 -0
  38. package/icons/icon-512.png +0 -0
  39. package/icons/icon-72.png +0 -0
  40. package/icons/icon-96.png +0 -0
  41. package/index.html +143 -73
  42. package/install-android.html +411 -0
  43. package/js/console.js +269 -0
  44. package/js/modules/esptool.js +1 -1
  45. package/js/script.js +750 -175
  46. package/js/util/console-color.js +282 -0
  47. package/js/util/line-break-transformer.js +19 -0
  48. package/js/webusb-serial.js +1017 -0
  49. package/license.md +1 -1
  50. package/manifest.json +89 -0
  51. package/package.cli.json +29 -0
  52. package/package.json +35 -24
  53. package/screenshots/desktop.png +0 -0
  54. package/screenshots/mobile.png +0 -0
  55. package/src/cli.ts +618 -0
  56. package/src/console.ts +278 -0
  57. package/src/const.ts +165 -8
  58. package/src/esp_loader.ts +2354 -302
  59. package/src/index.ts +69 -3
  60. package/src/node-usb-adapter.ts +924 -0
  61. package/src/stubs/index.ts +4 -1
  62. package/src/util/console-color.ts +290 -0
  63. package/src/util/line-break-transformer.ts +20 -0
  64. package/sw.js +155 -0
@@ -25,7 +25,7 @@ interface LoadedStub {
25
25
  data_start: number;
26
26
  }
27
27
 
28
- interface Stub {
28
+ export interface Stub {
29
29
  text: number[];
30
30
  data: number[];
31
31
  text_start: number;
@@ -75,6 +75,9 @@ export const getStubCode = async (
75
75
  } else {
76
76
  stubcode = await import("./esp32p4.json");
77
77
  }
78
+ } else {
79
+ // Unknown chip family - no stub available
80
+ return null;
78
81
  }
79
82
 
80
83
  // Base64 decode the text and data
@@ -0,0 +1,290 @@
1
+ interface ConsoleState {
2
+ bold: boolean;
3
+ italic: boolean;
4
+ underline: boolean;
5
+ strikethrough: boolean;
6
+ foregroundColor: string | null;
7
+ backgroundColor: string | null;
8
+ carriageReturn: boolean;
9
+ secret: boolean;
10
+ }
11
+
12
+ export class ColoredConsole {
13
+ public state: ConsoleState = {
14
+ bold: false,
15
+ italic: false,
16
+ underline: false,
17
+ strikethrough: false,
18
+ foregroundColor: null,
19
+ backgroundColor: null,
20
+ carriageReturn: false,
21
+ secret: false,
22
+ };
23
+
24
+ constructor(public targetElement: HTMLElement) {}
25
+
26
+ logs(): string {
27
+ return this.targetElement.innerText;
28
+ }
29
+
30
+ addLine(line: string) {
31
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences
32
+ const re = /(?:\x1B|\\x1B)(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1B\\))/g;
33
+ let i = 0;
34
+
35
+ if (this.state.carriageReturn) {
36
+ if (line !== "\n") {
37
+ // don't remove if \r\n
38
+ if (this.targetElement.lastChild) {
39
+ this.targetElement.removeChild(this.targetElement.lastChild);
40
+ }
41
+ }
42
+ this.state.carriageReturn = false;
43
+ }
44
+
45
+ const hasBareCR = line.endsWith("\r") && !line.endsWith("\r\n");
46
+ if (hasBareCR) {
47
+ this.state.carriageReturn = true;
48
+ }
49
+
50
+ const lineSpan = document.createElement("span");
51
+ lineSpan.classList.add("line");
52
+ this.targetElement.appendChild(lineSpan);
53
+
54
+ const addSpan = (content: string) => {
55
+ if (content === "") return;
56
+
57
+ const span = document.createElement("span");
58
+ if (this.state.bold) span.classList.add("log-bold");
59
+ if (this.state.italic) span.classList.add("log-italic");
60
+ if (this.state.underline) span.classList.add("log-underline");
61
+ if (this.state.strikethrough) span.classList.add("log-strikethrough");
62
+ if (this.state.secret) span.classList.add("log-secret");
63
+ if (this.state.foregroundColor !== null)
64
+ span.classList.add(`log-fg-${this.state.foregroundColor}`);
65
+ if (this.state.backgroundColor !== null)
66
+ span.classList.add(`log-bg-${this.state.backgroundColor}`);
67
+ span.appendChild(document.createTextNode(content));
68
+ lineSpan.appendChild(span);
69
+
70
+ if (this.state.secret) {
71
+ const redacted = document.createElement("span");
72
+ redacted.classList.add("log-secret-redacted");
73
+ redacted.appendChild(document.createTextNode("[redacted]"));
74
+ lineSpan.appendChild(redacted);
75
+ }
76
+ };
77
+
78
+ while (true) {
79
+ const match = re.exec(line);
80
+ if (match === null) break;
81
+
82
+ const j = match.index;
83
+ addSpan(line.substring(i, j));
84
+ i = j + match[0].length;
85
+
86
+ if (match[1] === undefined) continue;
87
+
88
+ for (const colorCode of match[1].split(";")) {
89
+ switch (parseInt(colorCode)) {
90
+ case 0:
91
+ // reset
92
+ this.state.bold = false;
93
+ this.state.italic = false;
94
+ this.state.underline = false;
95
+ this.state.strikethrough = false;
96
+ this.state.foregroundColor = null;
97
+ this.state.backgroundColor = null;
98
+ this.state.secret = false;
99
+ break;
100
+ case 1:
101
+ this.state.bold = true;
102
+ break;
103
+ case 3:
104
+ this.state.italic = true;
105
+ break;
106
+ case 4:
107
+ this.state.underline = true;
108
+ break;
109
+ case 5:
110
+ this.state.secret = true;
111
+ break;
112
+ case 6:
113
+ this.state.secret = false;
114
+ break;
115
+ case 9:
116
+ this.state.strikethrough = true;
117
+ break;
118
+ case 22:
119
+ this.state.bold = false;
120
+ break;
121
+ case 23:
122
+ this.state.italic = false;
123
+ break;
124
+ case 24:
125
+ this.state.underline = false;
126
+ break;
127
+ case 29:
128
+ this.state.strikethrough = false;
129
+ break;
130
+ case 30:
131
+ this.state.foregroundColor = "black";
132
+ break;
133
+ case 31:
134
+ this.state.foregroundColor = "red";
135
+ break;
136
+ case 32:
137
+ this.state.foregroundColor = "green";
138
+ break;
139
+ case 33:
140
+ this.state.foregroundColor = "yellow";
141
+ break;
142
+ case 34:
143
+ this.state.foregroundColor = "blue";
144
+ break;
145
+ case 35:
146
+ this.state.foregroundColor = "magenta";
147
+ break;
148
+ case 36:
149
+ this.state.foregroundColor = "cyan";
150
+ break;
151
+ case 37:
152
+ this.state.foregroundColor = "white";
153
+ break;
154
+ case 39:
155
+ this.state.foregroundColor = null;
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 40:
179
+ this.state.backgroundColor = "black";
180
+ break;
181
+ case 49:
182
+ this.state.backgroundColor = null;
183
+ break;
184
+ }
185
+ }
186
+ }
187
+ const atBottom =
188
+ this.targetElement.scrollTop >
189
+ this.targetElement.scrollHeight - this.targetElement.offsetHeight - 50;
190
+
191
+ addSpan(line.substring(i));
192
+
193
+ // Keep scroll at bottom
194
+ if (atBottom) {
195
+ this.targetElement.scrollTop = this.targetElement.scrollHeight;
196
+ }
197
+ }
198
+ }
199
+
200
+ export const coloredConsoleStyles = `
201
+ .log {
202
+ flex: 1;
203
+ background-color: #1c1c1c;
204
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier,
205
+ monospace;
206
+ font-size: 12px;
207
+ padding: 16px;
208
+ overflow: auto;
209
+ line-height: 1.45;
210
+ border-radius: 3px;
211
+ white-space: pre-wrap;
212
+ overflow-wrap: break-word;
213
+ color: #ddd;
214
+ }
215
+
216
+ .log-bold {
217
+ font-weight: bold;
218
+ }
219
+ .log-italic {
220
+ font-style: italic;
221
+ }
222
+ .log-underline {
223
+ text-decoration: underline;
224
+ }
225
+ .log-strikethrough {
226
+ text-decoration: line-through;
227
+ }
228
+ .log-underline.log-strikethrough {
229
+ text-decoration: underline line-through;
230
+ }
231
+ .log-secret {
232
+ -webkit-user-select: none;
233
+ -moz-user-select: none;
234
+ -ms-user-select: none;
235
+ user-select: none;
236
+ }
237
+ .log-secret-redacted {
238
+ opacity: 0;
239
+ width: 1px;
240
+ font-size: 1px;
241
+ }
242
+ .log-fg-black {
243
+ color: rgb(128, 128, 128);
244
+ }
245
+ .log-fg-red {
246
+ color: rgb(255, 0, 0);
247
+ }
248
+ .log-fg-green {
249
+ color: rgb(0, 255, 0);
250
+ }
251
+ .log-fg-yellow {
252
+ color: rgb(255, 255, 0);
253
+ }
254
+ .log-fg-blue {
255
+ color: rgb(0, 0, 255);
256
+ }
257
+ .log-fg-magenta {
258
+ color: rgb(255, 0, 255);
259
+ }
260
+ .log-fg-cyan {
261
+ color: rgb(0, 255, 255);
262
+ }
263
+ .log-fg-white {
264
+ color: rgb(187, 187, 187);
265
+ }
266
+ .log-bg-black {
267
+ background-color: rgb(0, 0, 0);
268
+ }
269
+ .log-bg-red {
270
+ background-color: rgb(255, 0, 0);
271
+ }
272
+ .log-bg-green {
273
+ background-color: rgb(0, 255, 0);
274
+ }
275
+ .log-bg-yellow {
276
+ background-color: rgb(255, 255, 0);
277
+ }
278
+ .log-bg-blue {
279
+ background-color: rgb(0, 0, 255);
280
+ }
281
+ .log-bg-magenta {
282
+ background-color: rgb(255, 0, 255);
283
+ }
284
+ .log-bg-cyan {
285
+ background-color: rgb(0, 255, 255);
286
+ }
287
+ .log-bg-white {
288
+ background-color: rgb(255, 255, 255);
289
+ }
290
+ `;
@@ -0,0 +1,20 @@
1
+ export class LineBreakTransformer implements Transformer<string, string> {
2
+ private chunks = "";
3
+
4
+ transform(
5
+ chunk: string,
6
+ controller: TransformStreamDefaultController<string>,
7
+ ) {
8
+ // Append new chunks to existing chunks.
9
+ this.chunks += chunk;
10
+ // For each line breaks in chunks, send the parsed lines out.
11
+ const lines = this.chunks.split("\r\n");
12
+ this.chunks = lines.pop()!;
13
+ lines.forEach((line) => controller.enqueue(line + "\r\n"));
14
+ }
15
+
16
+ flush(controller: TransformStreamDefaultController<string>) {
17
+ // When the stream is closed, flush any remaining chunks out.
18
+ controller.enqueue(this.chunks);
19
+ }
20
+ }
package/sw.js ADDED
@@ -0,0 +1,155 @@
1
+ // Service Worker for ESP32Tool PWA
2
+ const CACHE_NAME = 'esp32tool-v1.2.0';
3
+ const RUNTIME_CACHE = 'esp32tool-runtime';
4
+
5
+ // Core files to cache on install (relative paths work for any deployment path)
6
+ // This ensures the app works completely offline after installation
7
+ const CORE_ASSETS = [
8
+ // App shell
9
+ './',
10
+ './index.html',
11
+ './install-android.html',
12
+
13
+ // Stylesheets
14
+ './css/style.css',
15
+ './css/light.css',
16
+ './css/dark.css',
17
+
18
+ // JavaScript
19
+ './js/script.js',
20
+ './js/utilities.js',
21
+ './js/webusb-serial.js',
22
+ './js/modules/esptool.js',
23
+
24
+ // PWA manifest
25
+ './manifest.json',
26
+
27
+ // Icons (all sizes referenced in manifest)
28
+ './icons/icon-72.png',
29
+ './icons/icon-96.png',
30
+ './icons/icon-128.png',
31
+ './icons/icon-144.png',
32
+ './icons/icon-152.png',
33
+ './icons/icon-192.png',
34
+ './icons/icon-384.png',
35
+ './icons/icon-512.png',
36
+ './apple-touch-icon.png',
37
+ './favicon.ico',
38
+
39
+ // WASM modules (required for filesystem operations)
40
+ './src/wasm/littlefs/index.js',
41
+ './src/wasm/littlefs/littlefs.js',
42
+ './src/wasm/littlefs/littlefs.wasm',
43
+ './src/wasm/fatfs/index.js',
44
+ './src/wasm/fatfs/fatfs.wasm'
45
+ ];
46
+
47
+ // Install event - cache core assets
48
+ self.addEventListener('install', (event) => {
49
+ console.log('[SW] Installing service worker...');
50
+ event.waitUntil(
51
+ caches.open(CACHE_NAME)
52
+ .then((cache) => {
53
+ console.log('[SW] Caching core assets');
54
+ return cache.addAll(CORE_ASSETS);
55
+ })
56
+ .then(() => {
57
+ console.log('[SW] Skipping waiting - activating immediately');
58
+ return self.skipWaiting();
59
+ })
60
+ );
61
+ });
62
+
63
+ // Activate event - clean up old caches
64
+ self.addEventListener('activate', (event) => {
65
+ console.log('[SW] Activating service worker...');
66
+ event.waitUntil(
67
+ caches.keys().then((cacheNames) => {
68
+ return Promise.all(
69
+ cacheNames
70
+ .filter((name) => name !== CACHE_NAME && name !== RUNTIME_CACHE)
71
+ .map((name) => {
72
+ console.log('[SW] Deleting old cache:', name);
73
+ return caches.delete(name);
74
+ })
75
+ );
76
+ }).then(() => self.clients.claim())
77
+ );
78
+ });
79
+
80
+ // Fetch event - network first, fallback to cache
81
+ self.addEventListener('fetch', (event) => {
82
+ const { request } = event;
83
+ const url = new URL(request.url);
84
+
85
+ // Skip non-GET requests
86
+ if (request.method !== 'GET') {
87
+ return;
88
+ }
89
+
90
+ // Skip chrome-extension and other non-http(s) requests
91
+ if (!url.protocol.startsWith('http')) {
92
+ return;
93
+ }
94
+
95
+ // Network first strategy for HTML and API calls
96
+ if (request.headers.get('accept')?.includes('text/html')) {
97
+ event.respondWith(
98
+ fetch(request)
99
+ .then((response) => {
100
+ // Only cache successful responses
101
+ if (response && response.ok) {
102
+ // Clone and cache the response
103
+ const responseClone = response.clone();
104
+ caches.open(RUNTIME_CACHE).then((cache) => {
105
+ cache.put(request, responseClone);
106
+ });
107
+ }
108
+ return response;
109
+ })
110
+ .catch(() => {
111
+ // Fallback to cache
112
+ return caches.match(request);
113
+ })
114
+ );
115
+ return;
116
+ }
117
+
118
+ // Cache first strategy for static assets
119
+ event.respondWith(
120
+ caches.match(request)
121
+ .then((cachedResponse) => {
122
+ if (cachedResponse) {
123
+ return cachedResponse;
124
+ }
125
+
126
+ return fetch(request).then((response) => {
127
+ // Don't cache non-successful responses
128
+ if (!response || response.status !== 200 || response.type === 'error') {
129
+ return response;
130
+ }
131
+
132
+ // Clone and cache the response
133
+ const responseClone = response.clone();
134
+ caches.open(RUNTIME_CACHE).then((cache) => {
135
+ cache.put(request, responseClone);
136
+ });
137
+
138
+ // CRITICAL: Return the response after caching
139
+ return response;
140
+ }).catch(() => {
141
+ // Network failed and not in cache - return a basic error response
142
+ // or optionally return an offline fallback
143
+ console.warn('[SW] Network request failed for:', request.url);
144
+ return new Response('Network unavailable', { status: 503 });
145
+ });
146
+ })
147
+ );
148
+ });
149
+
150
+ // Handle messages from clients
151
+ self.addEventListener('message', (event) => {
152
+ if (event.data && event.data.type === 'SKIP_WAITING') {
153
+ self.skipWaiting();
154
+ }
155
+ });