tasmota-esp-web-tools 9.0.2 → 10.0.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 (85) hide show
  1. package/dist/components/ewt-littlefs-manager.d.ts +43 -0
  2. package/dist/components/ewt-littlefs-manager.js +819 -0
  3. package/dist/flash.js +4 -30
  4. package/dist/install-dialog.d.ts +11 -0
  5. package/dist/install-dialog.js +336 -0
  6. package/dist/partition.d.ts +26 -0
  7. package/dist/partition.js +129 -0
  8. package/dist/util/esp32s2-reconnect.d.ts +22 -0
  9. package/dist/util/esp32s2-reconnect.js +45 -0
  10. package/dist/util/partition.d.ts +4 -0
  11. package/dist/util/partition.js +59 -0
  12. package/{js/modules/index-BK6drzlv.js → dist/web/index-t2Vsxqjj.js} +1 -1
  13. package/dist/web/install-button.js +1 -1
  14. package/dist/web/{install-dialog-Xo7JovVA.js → install-dialog-CVebVk1R.js} +515 -36
  15. package/dist/web/{styles-ntwzvT31.js → styles-CWxbqh-J.js} +1 -1
  16. package/dist/web/wasm/littlefs/index.d.ts +91 -0
  17. package/dist/web/wasm/littlefs/index.js +611 -0
  18. package/dist/web/wasm/littlefs/littlefs.js +505 -0
  19. package/dist/web/wasm/littlefs/littlefs.wasm +0 -0
  20. package/{dist/web/index-BK6drzlv.js → js/modules/index-t2Vsxqjj.js} +1 -1
  21. package/js/modules/install-button.js +1 -1
  22. package/js/modules/{install-dialog-Xo7JovVA.js → install-dialog-CVebVk1R.js} +515 -36
  23. package/js/modules/{styles-ntwzvT31.js → styles-CWxbqh-J.js} +1 -1
  24. package/js/modules/wasm/littlefs/index.d.ts +91 -0
  25. package/js/modules/wasm/littlefs/index.js +611 -0
  26. package/js/modules/wasm/littlefs/littlefs.js +505 -0
  27. package/js/modules/wasm/littlefs/littlefs.wasm +0 -0
  28. package/package.json +3 -2
  29. package/.devcontainer/Dockerfile +0 -16
  30. package/.devcontainer/devcontainer.json +0 -21
  31. package/.github/dependabot.yml +0 -10
  32. package/.github/workflows/build_upload.yml +0 -78
  33. package/.github/workflows/ci.yml +0 -24
  34. package/.prettierignore +0 -1
  35. package/BAUD_RATE_CONFIGURATION.md +0 -291
  36. package/BAUD_RATE_IMPROVEMENT.md +0 -195
  37. package/CHANGELOG_IMPROVEMENTS.md +0 -246
  38. package/ESP32_S2_USB_RECONNECT.md +0 -108
  39. package/FINAL_SUMMARY.md +0 -153
  40. package/example-baud-rate.html +0 -276
  41. package/manifest-example-p4-variants.json +0 -61
  42. package/rollup.config.mjs +0 -36
  43. package/script/build +0 -8
  44. package/script/develop +0 -17
  45. package/src/components/ewt-button.ts +0 -25
  46. package/src/components/ewt-checkbox.ts +0 -14
  47. package/src/components/ewt-circular-progress.ts +0 -14
  48. package/src/components/ewt-console.ts +0 -166
  49. package/src/components/ewt-dialog.ts +0 -22
  50. package/src/components/ewt-formfield.ts +0 -14
  51. package/src/components/ewt-icon-button.ts +0 -14
  52. package/src/components/ewt-list-item.ts +0 -14
  53. package/src/components/ewt-select.ts +0 -23
  54. package/src/components/ewt-textfield.ts +0 -23
  55. package/src/components/svg.ts +0 -27
  56. package/src/connect.ts +0 -55
  57. package/src/const.ts +0 -122
  58. package/src/flash.ts +0 -361
  59. package/src/install-button.ts +0 -126
  60. package/src/install-dialog.ts +0 -1114
  61. package/src/no-port-picked/index.ts +0 -10
  62. package/src/no-port-picked/no-port-picked-dialog.ts +0 -167
  63. package/src/pages/ewt-page-message.ts +0 -39
  64. package/src/pages/ewt-page-progress.ts +0 -44
  65. package/src/styles.ts +0 -34
  66. package/src/util/chip-family-name.ts +0 -46
  67. package/src/util/console-color.ts +0 -284
  68. package/src/util/file-download.ts +0 -17
  69. package/src/util/fire-event.ts +0 -20
  70. package/src/util/line-break-transformer.ts +0 -20
  71. package/src/util/manifest.ts +0 -18
  72. package/src/util/sleep.ts +0 -2
  73. package/static/logos/2smart.png +0 -0
  74. package/static/logos/canairio.png +0 -0
  75. package/static/logos/clockwise.png +0 -0
  76. package/static/logos/espeasy.png +0 -0
  77. package/static/logos/esphome.svg +0 -1
  78. package/static/logos/luciferin_logo.png +0 -0
  79. package/static/logos/squeezelite-esp32.png +0 -0
  80. package/static/logos/tasmota.svg +0 -1
  81. package/static/logos/wled.png +0 -0
  82. package/static/screenshots/dashboard.png +0 -0
  83. package/static/screenshots/logs.png +0 -0
  84. package/static/social.png +0 -0
  85. package/tsconfig.json +0 -20
@@ -98,4 +98,4 @@ var t=function(e,i){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
98
98
  a.button {
99
99
  text-decoration: none;
100
100
  }
101
- `;export{K as A,It as B,j as C,ve as D,Nt as M,Jt as R,G as T,o as _,Zt as a,vt as b,jt as c,Qt as d,yt as e,l as f,e as g,i as h,bt as i,ue as j,ut as k,Et as l,Bt as m,ht as n,gt as o,Vt as p,Ut as q,r,lt as s,ft as t,Tt as u,Ct as v,Ot as w,V as x,$t as y,qt as z};
101
+ `;export{K as A,It as B,$t as C,qt as D,Nt as M,Jt as R,G as T,o as _,bt as a,j as b,Zt as c,ve as d,ut as e,yt as f,vt as g,jt as h,l as i,Qt as j,e as k,Et as l,i as m,ht as n,gt as o,ue as p,Bt as q,Vt as r,lt as s,ft as t,Ut as u,r as v,Tt as w,V as x,Ct as y,Ot as z};
@@ -0,0 +1,91 @@
1
+ import type { FileSource, BinarySource } from "../shared/types";
2
+
3
+ /**
4
+ * Maximum filename length (ESP-IDF default: 64)
5
+ */
6
+ export declare const LFS_NAME_MAX: number;
7
+
8
+ /**
9
+ * LittleFS disk version 2.0 (0x00020000)
10
+ * Use this for maximum compatibility with older implementations.
11
+ */
12
+ export declare const DISK_VERSION_2_0: number;
13
+
14
+ /**
15
+ * LittleFS disk version 2.1 (0x00020001)
16
+ * Latest version with additional features.
17
+ */
18
+ export declare const DISK_VERSION_2_1: number;
19
+
20
+ /**
21
+ * Format disk version as human-readable string (e.g., "2.0", "2.1")
22
+ */
23
+ export declare function formatDiskVersion(version: number): string;
24
+
25
+ export interface LittleFSEntry {
26
+ path: string;
27
+ size: number;
28
+ type: "file" | "dir";
29
+ }
30
+
31
+ export interface LittleFSOptions {
32
+ blockSize?: number;
33
+ blockCount?: number;
34
+ lookaheadSize?: number;
35
+ /**
36
+ * Optional override for the wasm asset location. Useful when bundlers move files.
37
+ */
38
+ wasmURL?: string | URL;
39
+ /**
40
+ * Formats the filesystem immediately after initialization.
41
+ */
42
+ formatOnInit?: boolean;
43
+ /**
44
+ * Disk version to use when formatting new filesystems.
45
+ * Use DISK_VERSION_2_0 for compatibility with older ESP implementations.
46
+ * Use DISK_VERSION_2_1 for latest features.
47
+ *
48
+ * IMPORTANT: Setting this prevents automatic migration of older filesystems.
49
+ */
50
+ diskVersion?: number;
51
+ }
52
+
53
+ export interface LittleFS {
54
+ format(): void;
55
+ list(path?: string): LittleFSEntry[];
56
+ addFile(path: string, data: FileSource): void;
57
+ writeFile(path: string, data: FileSource): void;
58
+ deleteFile(path: string): void;
59
+ delete(
60
+ path: string,
61
+ options?: {
62
+ recursive?: boolean;
63
+ },
64
+ ): void;
65
+ mkdir(path: string): void;
66
+ rename(oldPath: string, newPath: string): void;
67
+ toImage(): Uint8Array;
68
+ readFile(path: string): Uint8Array;
69
+ /**
70
+ * Get the disk version of the mounted filesystem.
71
+ * @returns Version as 32-bit number (e.g., 0x00020000 for v2.0)
72
+ */
73
+ getDiskVersion(): number;
74
+ /**
75
+ * Get filesystem usage statistics.
76
+ */
77
+ getUsage(): { capacityBytes: number; usedBytes: number; freeBytes: number };
78
+ }
79
+
80
+ export declare class LittleFSError extends Error {
81
+ readonly code: number;
82
+ constructor(message: string, code: number);
83
+ }
84
+
85
+ export declare function createLittleFS(
86
+ options?: LittleFSOptions,
87
+ ): Promise<LittleFS>;
88
+ export declare function createLittleFSFromImage(
89
+ image: BinarySource,
90
+ options?: LittleFSOptions,
91
+ ): Promise<LittleFS>;
@@ -0,0 +1,611 @@
1
+ /**
2
+ * LittleFS WebAssembly Bindings for ESPConnect
3
+ *
4
+ * Provides TypeScript-first API for LittleFS with disk version control.
5
+ * Supports DISK_VERSION_2_0 to prevent automatic migration of older filesystems.
6
+ *
7
+ * ESP-IDF Compatible Configuration:
8
+ * - LFS_NAME_MAX=64 (filename length)
9
+ * - LFS_ATTR_MAX=4 (metadata for timestamps)
10
+ * - LFS_MULTIVERSION enabled
11
+ */
12
+
13
+ // Import Emscripten-generated module loader (converted to ES module)
14
+ import createLittleFSModule from "./littlefs.js";
15
+
16
+ const DEFAULT_BLOCK_SIZE = 4096;
17
+ const DEFAULT_BLOCK_COUNT = 256;
18
+ const DEFAULT_LOOKAHEAD_SIZE = 32;
19
+ const LFS_ERR_NOSPC = -28;
20
+
21
+ /**
22
+ * Maximum filename length (ESP-IDF default)
23
+ */
24
+ export const LFS_NAME_MAX = 64;
25
+
26
+ /**
27
+ * LittleFS disk version 2.0 (0x00020000)
28
+ * Use this for maximum compatibility with older implementations.
29
+ */
30
+ export const DISK_VERSION_2_0 = 0x00020000;
31
+
32
+ /**
33
+ * LittleFS disk version 2.1 (0x00020001)
34
+ * Latest version with additional features.
35
+ */
36
+ export const DISK_VERSION_2_1 = 0x00020001;
37
+
38
+ /**
39
+ * Format disk version as human-readable string (e.g., "2.0", "2.1")
40
+ */
41
+ export function formatDiskVersion(version) {
42
+ const major = (version >> 16) & 0xffff;
43
+ const minor = version & 0xffff;
44
+ return `${major}.${minor}`;
45
+ }
46
+
47
+ export class LittleFSError extends Error {
48
+ constructor(message, code) {
49
+ super(message);
50
+ this.code = code;
51
+ this.name = "LittleFSError";
52
+ }
53
+ }
54
+
55
+ function createModuleConfig(wasmURL) {
56
+ const wasmURLStr = wasmURL instanceof URL ? wasmURL.href : wasmURL;
57
+
58
+ return {
59
+ locateFile: (path) => {
60
+ if (path.endsWith(".wasm")) {
61
+ console.info("[littlefs-wasm] locateFile:", path, "->", wasmURLStr);
62
+ return wasmURLStr;
63
+ }
64
+ return path;
65
+ },
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Create a new LittleFS instance
71
+ * @param {LittleFSOptions} options
72
+ * @returns {Promise<LittleFS>}
73
+ */
74
+ export async function createLittleFS(options = {}) {
75
+ console.info("[littlefs-wasm] createLittleFS() starting", options);
76
+
77
+ const blockSize = options.blockSize ?? DEFAULT_BLOCK_SIZE;
78
+ const blockCount = options.blockCount ?? DEFAULT_BLOCK_COUNT;
79
+ const lookaheadSize = options.lookaheadSize ?? DEFAULT_LOOKAHEAD_SIZE;
80
+ const diskVersion = options.diskVersion ?? DISK_VERSION_2_0;
81
+
82
+ // Configure module with custom locateFile for WASM
83
+ // Always set locateFile to ensure correct WASM path resolution
84
+ const wasmURL =
85
+ options.wasmURL ?? new URL("./littlefs.wasm", import.meta.url).href;
86
+ const moduleConfig = createModuleConfig(wasmURL);
87
+
88
+ // Initialize Emscripten module
89
+ const Module = await createLittleFSModule(moduleConfig);
90
+ console.info("[littlefs-wasm] Emscripten module loaded");
91
+ try {
92
+ // Set disk version before init to ensure new filesystems use the specified version
93
+ // and to prevent automatic migration from older versions
94
+ if (Module._lfs_wasm_set_disk_version) {
95
+ Module._lfs_wasm_set_disk_version(diskVersion);
96
+ console.info(
97
+ "[littlefs-wasm] Disk version set to:",
98
+ formatDiskVersion(diskVersion),
99
+ );
100
+ }
101
+
102
+ // Initialize LittleFS
103
+ const initResult = Module._lfs_wasm_init(
104
+ blockSize,
105
+ blockCount,
106
+ lookaheadSize,
107
+ );
108
+ if (initResult !== 0) {
109
+ throw new LittleFSError(
110
+ `Failed to initialize LittleFS: ${initResult}`,
111
+ initResult,
112
+ );
113
+ }
114
+
115
+ // Format if requested
116
+ if (options.formatOnInit) {
117
+ const formatResult = Module._lfs_wasm_format();
118
+ if (formatResult !== 0) {
119
+ throw new LittleFSError(
120
+ `Failed to format LittleFS: ${formatResult}`,
121
+ formatResult,
122
+ );
123
+ }
124
+ }
125
+
126
+ // Mount (with optional auto-format on failure)
127
+ const mountResult = Module._lfs_wasm_mount();
128
+ if (mountResult !== 0) {
129
+ if (options.autoFormatOnMountFailure !== true) {
130
+ throw new LittleFSError(
131
+ `Failed to mount LittleFS: ${mountResult}`,
132
+ mountResult,
133
+ );
134
+ }
135
+ console.warn(
136
+ "[littlefs-wasm] Mount failed, attempting format and remount...",
137
+ );
138
+ const formatResult = Module._lfs_wasm_format();
139
+ if (formatResult !== 0) {
140
+ throw new LittleFSError(
141
+ `Failed to format LittleFS: ${formatResult}`,
142
+ formatResult,
143
+ );
144
+ }
145
+ const retryMount = Module._lfs_wasm_mount();
146
+ if (retryMount !== 0) {
147
+ throw new LittleFSError(
148
+ `Failed to mount LittleFS: ${retryMount}`,
149
+ retryMount,
150
+ );
151
+ }
152
+ }
153
+ } catch (error) {
154
+ // Clean up Module resources before rethrowing
155
+ if (Module._lfs_wasm_cleanup) {
156
+ try {
157
+ Module._lfs_wasm_cleanup();
158
+ } catch (cleanupError) {
159
+ console.error(
160
+ "[littlefs-wasm] Cleanup during error handling failed:",
161
+ cleanupError,
162
+ );
163
+ }
164
+ }
165
+ throw error;
166
+ }
167
+
168
+ console.info("[littlefs-wasm] LittleFS mounted successfully");
169
+ return createClient(Module, blockSize, blockCount);
170
+ }
171
+
172
+ /**
173
+ * Create a LittleFS instance from an existing image
174
+ * @param {Uint8Array|ArrayBuffer} image
175
+ * @param {LittleFSOptions} options
176
+ * @returns {Promise<LittleFS>}
177
+ */
178
+ export async function createLittleFSFromImage(image, options = {}) {
179
+ console.info("[littlefs-wasm] createLittleFSFromImage() starting");
180
+
181
+ const imageData =
182
+ image instanceof ArrayBuffer ? new Uint8Array(image) : image;
183
+ const blockSize = options.blockSize ?? DEFAULT_BLOCK_SIZE;
184
+ const blockCount =
185
+ options.blockCount ?? Math.ceil(imageData.length / blockSize);
186
+ const lookaheadSize = options.lookaheadSize ?? DEFAULT_LOOKAHEAD_SIZE;
187
+
188
+ // Configure module with custom locateFile for WASM
189
+ // Always set locateFile to ensure correct WASM path resolution
190
+ const wasmURL =
191
+ options.wasmURL ?? new URL("./littlefs.wasm", import.meta.url).href;
192
+ const moduleConfig = createModuleConfig(wasmURL);
193
+
194
+ // Initialize Emscripten module
195
+ const Module = await createLittleFSModule(moduleConfig);
196
+ console.info("[littlefs-wasm] Emscripten module loaded");
197
+
198
+ // When loading from image, don't set disk version (preserve existing)
199
+ // This is important to not trigger migration
200
+
201
+ try {
202
+ // Allocate memory for image
203
+ const imagePtr = Module._malloc(imageData.length);
204
+ if (!imagePtr) {
205
+ throw new LittleFSError("Failed to allocate memory for image", -1);
206
+ }
207
+
208
+ try {
209
+ // Copy image to WASM memory
210
+ Module.HEAPU8.set(imageData, imagePtr);
211
+
212
+ // Initialize from image
213
+ const initResult = Module._lfs_wasm_init_from_image(
214
+ imagePtr,
215
+ imageData.length,
216
+ blockSize,
217
+ blockCount,
218
+ lookaheadSize,
219
+ );
220
+
221
+ if (initResult !== 0) {
222
+ throw new LittleFSError(
223
+ `Failed to initialize LittleFS from image: ${initResult}`,
224
+ initResult,
225
+ );
226
+ }
227
+ } finally {
228
+ Module._free(imagePtr);
229
+ }
230
+
231
+ // Mount
232
+ const mountResult = Module._lfs_wasm_mount();
233
+ if (mountResult !== 0) {
234
+ throw new LittleFSError(
235
+ `Failed to mount LittleFS: ${mountResult}`,
236
+ mountResult,
237
+ );
238
+ }
239
+ } catch (error) {
240
+ // Clean up Module resources before rethrowing
241
+ if (Module._lfs_wasm_cleanup) {
242
+ try {
243
+ Module._lfs_wasm_cleanup();
244
+ } catch (cleanupError) {
245
+ console.error(
246
+ "[littlefs-wasm] Cleanup during error handling failed:",
247
+ cleanupError,
248
+ );
249
+ }
250
+ }
251
+ throw error;
252
+ }
253
+
254
+ // Get disk version after mounting
255
+ const version = Module._lfs_wasm_get_disk_version
256
+ ? Module._lfs_wasm_get_disk_version()
257
+ : 0;
258
+ console.info(
259
+ "[littlefs-wasm] LittleFS mounted from image, disk version:",
260
+ formatDiskVersion(version),
261
+ );
262
+
263
+ return createClient(Module, blockSize, blockCount);
264
+ }
265
+
266
+ /**
267
+ * Create the client API wrapper
268
+ */
269
+ function createClient(Module, blockSize, blockCount) {
270
+ const encoder = new TextEncoder();
271
+ const decoder = new TextDecoder();
272
+
273
+ function allocString(str) {
274
+ const bytes = encoder.encode(str + "\0");
275
+ const ptr = Module._malloc(bytes.length);
276
+ if (!ptr) throw new LittleFSError("Failed to allocate string", -1);
277
+ Module.HEAPU8.set(bytes, ptr);
278
+ return ptr;
279
+ }
280
+
281
+ function readString(ptr, maxLength = 4096) {
282
+ if (!ptr) return "";
283
+ let end = ptr;
284
+ const limit = ptr + maxLength;
285
+ while (end < limit && Module.HEAPU8[end] !== 0) end++;
286
+ if (end >= limit) {
287
+ console.warn("[littlefs-wasm] String read truncated at maxLength");
288
+ }
289
+ return decoder.decode(Module.HEAPU8.subarray(ptr, end));
290
+ }
291
+
292
+ const client = {
293
+ format() {
294
+ const result = Module._lfs_wasm_format();
295
+ if (result !== 0) {
296
+ throw new LittleFSError(`Format failed: ${result}`, result);
297
+ }
298
+ // Remount after format
299
+ const mountResult = Module._lfs_wasm_mount();
300
+ if (mountResult !== 0) {
301
+ throw new LittleFSError(
302
+ `Mount after format failed: ${mountResult}`,
303
+ mountResult,
304
+ );
305
+ }
306
+ },
307
+
308
+ list(path = "/") {
309
+ const entries = [];
310
+ const pathPtr = allocString(path);
311
+ let dirHandle = -1;
312
+
313
+ try {
314
+ // dir_open returns handle >= 0 on success, negative on error
315
+ dirHandle = Module._lfs_wasm_dir_open(pathPtr);
316
+ if (dirHandle < 0) {
317
+ throw new LittleFSError(
318
+ `Failed to open directory: ${dirHandle}`,
319
+ dirHandle,
320
+ );
321
+ }
322
+
323
+ const nameBuffer = Module._malloc(LFS_NAME_MAX + 1);
324
+ const typePtr = Module._malloc(4);
325
+ const sizePtr = Module._malloc(4);
326
+
327
+ try {
328
+ while (true) {
329
+ // dir_read takes handle as first parameter
330
+ const readResult = Module._lfs_wasm_dir_read(
331
+ dirHandle,
332
+ nameBuffer,
333
+ LFS_NAME_MAX,
334
+ typePtr,
335
+ sizePtr,
336
+ );
337
+ if (readResult === 0) break; // No more entries
338
+ if (readResult < 0) {
339
+ throw new LittleFSError(
340
+ `Failed to read directory: ${readResult}`,
341
+ readResult,
342
+ );
343
+ }
344
+
345
+ const name = readString(nameBuffer);
346
+ // . and .. are already filtered in C code, but double-check
347
+ if (name === "." || name === "..") continue;
348
+
349
+ const type = Module.HEAP32[typePtr >> 2];
350
+ const size = Module.HEAPU32[sizePtr >> 2];
351
+
352
+ const entryPath = path === "/" ? `/${name}` : `${path}/${name}`;
353
+ // type: 1 = file (LFS_TYPE_REG), 2 = directory (LFS_TYPE_DIR)
354
+ const isDir = type === 2;
355
+ entries.push({
356
+ path: entryPath,
357
+ name,
358
+ size: isDir ? 0 : size, // Files have size, directories don't
359
+ type: isDir ? "dir" : "file",
360
+ });
361
+ }
362
+ } finally {
363
+ Module._free(nameBuffer);
364
+ Module._free(typePtr);
365
+ Module._free(sizePtr);
366
+ }
367
+
368
+ // dir_close takes handle as parameter
369
+ Module._lfs_wasm_dir_close(dirHandle);
370
+ dirHandle = -1; // Mark as closed
371
+ } finally {
372
+ Module._free(pathPtr);
373
+ // Close handle if still open (in case of exception)
374
+ if (dirHandle >= 0) {
375
+ Module._lfs_wasm_dir_close(dirHandle);
376
+ }
377
+ }
378
+
379
+ return entries;
380
+ },
381
+
382
+ readFile(path) {
383
+ const pathPtr = allocString(path);
384
+ try {
385
+ // Get file size first
386
+ const size = Module._lfs_wasm_file_size(pathPtr);
387
+ if (size < 0) {
388
+ throw new LittleFSError(`Failed to get file size: ${size}`, size);
389
+ }
390
+
391
+ const dataPtr = Module._malloc(size);
392
+ if (!dataPtr && size > 0) {
393
+ throw new LittleFSError("Failed to allocate read buffer", -1);
394
+ }
395
+
396
+ try {
397
+ const readResult = Module._lfs_wasm_read_file(pathPtr, dataPtr, size);
398
+ if (readResult < 0) {
399
+ throw new LittleFSError(
400
+ `Failed to read file: ${readResult}`,
401
+ readResult,
402
+ );
403
+ }
404
+ return new Uint8Array(
405
+ Module.HEAPU8.buffer,
406
+ dataPtr,
407
+ readResult,
408
+ ).slice();
409
+ } finally {
410
+ Module._free(dataPtr);
411
+ }
412
+ } finally {
413
+ Module._free(pathPtr);
414
+ }
415
+ },
416
+
417
+ writeFile(path, data) {
418
+ const pathPtr = allocString(path);
419
+ const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
420
+ const dataPtr = Module._malloc(bytes.length);
421
+
422
+ if (!dataPtr && bytes.length > 0) {
423
+ Module._free(pathPtr);
424
+ throw new LittleFSError("Failed to allocate write buffer", -1);
425
+ }
426
+
427
+ try {
428
+ Module.HEAPU8.set(bytes, dataPtr);
429
+ const result = Module._lfs_wasm_write_file(
430
+ pathPtr,
431
+ dataPtr,
432
+ bytes.length,
433
+ );
434
+ if (result < 0) {
435
+ if (result === LFS_ERR_NOSPC) {
436
+ throw new LittleFSError("No space left on device", result);
437
+ }
438
+ throw new LittleFSError(`Failed to write file: ${result}`, result);
439
+ }
440
+ } finally {
441
+ Module._free(pathPtr);
442
+ Module._free(dataPtr);
443
+ }
444
+ },
445
+
446
+ addFile(path, data) {
447
+ return this.writeFile(path, data);
448
+ },
449
+
450
+ deleteFile(path) {
451
+ const pathPtr = allocString(path);
452
+ try {
453
+ const result = Module._lfs_wasm_remove(pathPtr);
454
+ if (result !== 0) {
455
+ throw new LittleFSError(`Failed to delete: ${result}`, result);
456
+ }
457
+ } finally {
458
+ Module._free(pathPtr);
459
+ }
460
+ },
461
+
462
+ delete(path, options = {}) {
463
+ if (options.recursive) {
464
+ // List contents and delete recursively
465
+ try {
466
+ const entries = this.list(path);
467
+ for (const entry of entries) {
468
+ if (entry.type === "dir") {
469
+ this.delete(entry.path, { recursive: true });
470
+ } else {
471
+ this.deleteFile(entry.path);
472
+ }
473
+ }
474
+ } catch (e) {
475
+ // Directory might be empty or not exist, log other errors
476
+ if (e.code !== -2) {
477
+ // -2 is ENOENT
478
+ console.warn("[littlefs-wasm] Error during recursive delete:", e);
479
+ }
480
+ }
481
+ }
482
+ this.deleteFile(path);
483
+ },
484
+
485
+ mkdir(path) {
486
+ const pathPtr = allocString(path);
487
+ try {
488
+ const result = Module._lfs_wasm_mkdir(pathPtr);
489
+ // Ignore "already exists" error
490
+ if (result !== 0 && result !== -17) {
491
+ throw new LittleFSError(
492
+ `Failed to create directory: ${result}`,
493
+ result,
494
+ );
495
+ }
496
+ } finally {
497
+ Module._free(pathPtr);
498
+ }
499
+ },
500
+
501
+ rename(oldPath, newPath) {
502
+ const oldPtr = allocString(oldPath);
503
+ const newPtr = allocString(newPath);
504
+ try {
505
+ const result = Module._lfs_wasm_rename(oldPtr, newPtr);
506
+ if (result !== 0) {
507
+ throw new LittleFSError(`Failed to rename: ${result}`, result);
508
+ }
509
+ } finally {
510
+ Module._free(oldPtr);
511
+ Module._free(newPtr);
512
+ }
513
+ },
514
+
515
+ toImage() {
516
+ const size = Module._lfs_wasm_get_image_size();
517
+ if (size <= 0) {
518
+ throw new LittleFSError(`Invalid image size: ${size}`, size);
519
+ }
520
+
521
+ const ptr = Module._lfs_wasm_get_image();
522
+ if (!ptr) {
523
+ throw new LittleFSError("Failed to get image pointer", -1);
524
+ }
525
+
526
+ // Note: ptr points to internal ram_storage buffer, not allocated memory
527
+ // slice() already copies the data, so we must NOT free this pointer
528
+ return new Uint8Array(Module.HEAPU8.buffer, ptr, size).slice();
529
+ },
530
+
531
+ getDiskVersion() {
532
+ if (Module._lfs_wasm_get_fs_info) {
533
+ const versionPtr = Module._malloc(4);
534
+ try {
535
+ const result = Module._lfs_wasm_get_fs_info(versionPtr);
536
+ if (result === 0) {
537
+ return Module.HEAPU32[versionPtr >> 2];
538
+ }
539
+ } finally {
540
+ Module._free(versionPtr);
541
+ }
542
+ }
543
+ console.warn(
544
+ "[littlefs-wasm] getDiskVersion not available or filesystem not mounted",
545
+ );
546
+ return 0;
547
+ },
548
+
549
+ setDiskVersion(version) {
550
+ if (Module._lfs_wasm_set_disk_version) {
551
+ Module._lfs_wasm_set_disk_version(version);
552
+ } else {
553
+ console.warn("[littlefs-wasm] setDiskVersion not available");
554
+ }
555
+ },
556
+
557
+ getUsage() {
558
+ const blockUsedPtr = Module._malloc(4);
559
+ const blockTotalPtr = Module._malloc(4);
560
+
561
+ try {
562
+ const result = Module._lfs_wasm_fs_stat(blockUsedPtr, blockTotalPtr);
563
+ if (result !== 0) {
564
+ // Fallback estimation
565
+ return {
566
+ capacityBytes: blockSize * blockCount,
567
+ usedBytes: 0,
568
+ freeBytes: blockSize * blockCount,
569
+ };
570
+ }
571
+
572
+ const blocksUsed = Module.HEAPU32[blockUsedPtr >> 2];
573
+ const blocksTotal = Module.HEAPU32[blockTotalPtr >> 2];
574
+
575
+ const capacityBytes = blocksTotal * blockSize;
576
+ const usedBytes = blocksUsed * blockSize;
577
+
578
+ return {
579
+ capacityBytes,
580
+ usedBytes,
581
+ freeBytes: Math.max(0, capacityBytes - usedBytes),
582
+ };
583
+ } finally {
584
+ Module._free(blockUsedPtr);
585
+ Module._free(blockTotalPtr);
586
+ }
587
+ },
588
+
589
+ canFit(path, size) {
590
+ const usage = this.getUsage();
591
+ return usage.freeBytes >= size;
592
+ },
593
+
594
+ cleanup() {
595
+ Module._lfs_wasm_unmount();
596
+ Module._lfs_wasm_cleanup();
597
+ },
598
+ };
599
+
600
+ return client;
601
+ }
602
+
603
+ // Default export for CommonJS compatibility
604
+ export default {
605
+ createLittleFS,
606
+ createLittleFSFromImage,
607
+ DISK_VERSION_2_0,
608
+ DISK_VERSION_2_1,
609
+ LFS_NAME_MAX,
610
+ formatDiskVersion,
611
+ };