@componentor/fs 3.0.7 → 3.0.9
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 +14 -0
- package/dist/index.js +203 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -509,6 +509,20 @@ Make sure `opfsSync` is enabled (it's `true` by default). Files are mirrored to
|
|
|
509
509
|
|
|
510
510
|
## Changelog
|
|
511
511
|
|
|
512
|
+
### v3.0.9 (2026)
|
|
513
|
+
|
|
514
|
+
**Improvements:**
|
|
515
|
+
- `unpackToOPFS`, `loadFromOPFS`, and `repairVFS` now accept an optional `fs` parameter (a running `VFSFileSystem` instance) so they work from any tab — leader or follower — without stopping the VFS
|
|
516
|
+
- When `fs` is not provided, falls back to direct `.vfs.bin` access via VFSEngine (requires VFS to be stopped or a Worker context)
|
|
517
|
+
- `repairVFS` with a running instance uses OPFS as source of truth: rebuilds VFS from OPFS, then syncs back for full consistency
|
|
518
|
+
|
|
519
|
+
### v3.0.8 (2026)
|
|
520
|
+
|
|
521
|
+
**Improvements:**
|
|
522
|
+
- Add VFS helper functions: `unpackToOPFS`, `loadFromOPFS`, and `repairVFS` for VFS maintenance, migration, and recovery
|
|
523
|
+
- Helpers work in both Worker (sync access handle) and main thread (in-memory buffer + async writable) contexts
|
|
524
|
+
- Remove redundant I/O call in `unpackToOPFS` directory creation
|
|
525
|
+
|
|
512
526
|
### v3.0.7 (2026)
|
|
513
527
|
|
|
514
528
|
**Fixes:**
|
package/dist/index.js
CHANGED
|
@@ -3236,6 +3236,86 @@ var VFSEngine = class {
|
|
|
3236
3236
|
};
|
|
3237
3237
|
|
|
3238
3238
|
// src/helpers.ts
|
|
3239
|
+
var MemoryHandle = class {
|
|
3240
|
+
buf;
|
|
3241
|
+
len;
|
|
3242
|
+
constructor(initialData) {
|
|
3243
|
+
if (initialData && initialData.byteLength > 0) {
|
|
3244
|
+
this.buf = new Uint8Array(initialData);
|
|
3245
|
+
this.len = initialData.byteLength;
|
|
3246
|
+
} else {
|
|
3247
|
+
this.buf = new Uint8Array(1024 * 1024);
|
|
3248
|
+
this.len = 0;
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
getSize() {
|
|
3252
|
+
return this.len;
|
|
3253
|
+
}
|
|
3254
|
+
read(target, opts) {
|
|
3255
|
+
const offset = opts?.at ?? 0;
|
|
3256
|
+
const dst = new Uint8Array(target.buffer, target.byteOffset, target.byteLength);
|
|
3257
|
+
const bytesToRead = Math.min(dst.length, this.len - offset);
|
|
3258
|
+
if (bytesToRead <= 0) return 0;
|
|
3259
|
+
dst.set(this.buf.subarray(offset, offset + bytesToRead));
|
|
3260
|
+
return bytesToRead;
|
|
3261
|
+
}
|
|
3262
|
+
write(data, opts) {
|
|
3263
|
+
const offset = opts?.at ?? 0;
|
|
3264
|
+
const src = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
3265
|
+
const needed = offset + src.length;
|
|
3266
|
+
if (needed > this.buf.length) {
|
|
3267
|
+
this.grow(needed);
|
|
3268
|
+
}
|
|
3269
|
+
this.buf.set(src, offset);
|
|
3270
|
+
if (needed > this.len) this.len = needed;
|
|
3271
|
+
return src.length;
|
|
3272
|
+
}
|
|
3273
|
+
truncate(size) {
|
|
3274
|
+
if (size > this.buf.length) {
|
|
3275
|
+
this.grow(size);
|
|
3276
|
+
}
|
|
3277
|
+
if (size > this.len) {
|
|
3278
|
+
this.buf.fill(0, this.len, size);
|
|
3279
|
+
}
|
|
3280
|
+
this.len = size;
|
|
3281
|
+
}
|
|
3282
|
+
flush() {
|
|
3283
|
+
}
|
|
3284
|
+
close() {
|
|
3285
|
+
}
|
|
3286
|
+
getBuffer() {
|
|
3287
|
+
return this.buf.buffer.slice(0, this.len);
|
|
3288
|
+
}
|
|
3289
|
+
grow(minSize) {
|
|
3290
|
+
const newSize = Math.max(minSize, this.buf.length * 2);
|
|
3291
|
+
const newBuf = new Uint8Array(newSize);
|
|
3292
|
+
newBuf.set(this.buf.subarray(0, this.len));
|
|
3293
|
+
this.buf = newBuf;
|
|
3294
|
+
}
|
|
3295
|
+
};
|
|
3296
|
+
async function openVFSHandle(fileHandle) {
|
|
3297
|
+
try {
|
|
3298
|
+
const handle = await fileHandle.createSyncAccessHandle();
|
|
3299
|
+
return { handle, isMemory: false };
|
|
3300
|
+
} catch {
|
|
3301
|
+
const file = await fileHandle.getFile();
|
|
3302
|
+
const data = await file.arrayBuffer();
|
|
3303
|
+
return { handle: new MemoryHandle(data), isMemory: true };
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
async function openFreshVFSHandle(fileHandle) {
|
|
3307
|
+
try {
|
|
3308
|
+
const handle = await fileHandle.createSyncAccessHandle();
|
|
3309
|
+
return { handle, isMemory: false };
|
|
3310
|
+
} catch {
|
|
3311
|
+
return { handle: new MemoryHandle(), isMemory: true };
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
async function saveMemoryHandle(fileHandle, memHandle) {
|
|
3315
|
+
const writable = await fileHandle.createWritable();
|
|
3316
|
+
await writable.write(memHandle.getBuffer());
|
|
3317
|
+
await writable.close();
|
|
3318
|
+
}
|
|
3239
3319
|
async function navigateToRoot(root) {
|
|
3240
3320
|
let dir = await navigator.storage.getDirectory();
|
|
3241
3321
|
if (root && root !== "/") {
|
|
@@ -3262,15 +3342,21 @@ async function writeOPFSFile(rootDir, path, data) {
|
|
|
3262
3342
|
const parentDir = await ensureParentDirs(rootDir, path);
|
|
3263
3343
|
const name = basename2(path);
|
|
3264
3344
|
const fileHandle = await parentDir.getFileHandle(name, { create: true });
|
|
3265
|
-
const syncHandle = await fileHandle.createSyncAccessHandle();
|
|
3266
3345
|
try {
|
|
3267
|
-
syncHandle.
|
|
3268
|
-
|
|
3269
|
-
syncHandle.
|
|
3346
|
+
const syncHandle = await fileHandle.createSyncAccessHandle();
|
|
3347
|
+
try {
|
|
3348
|
+
syncHandle.truncate(0);
|
|
3349
|
+
if (data.byteLength > 0) {
|
|
3350
|
+
syncHandle.write(data, { at: 0 });
|
|
3351
|
+
}
|
|
3352
|
+
syncHandle.flush();
|
|
3353
|
+
} finally {
|
|
3354
|
+
syncHandle.close();
|
|
3270
3355
|
}
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3356
|
+
} catch {
|
|
3357
|
+
const writable = await fileHandle.createWritable();
|
|
3358
|
+
await writable.write(data);
|
|
3359
|
+
await writable.close();
|
|
3274
3360
|
}
|
|
3275
3361
|
}
|
|
3276
3362
|
async function clearDirectory(dir, skip) {
|
|
@@ -3299,10 +3385,54 @@ async function readOPFSRecursive(dir, prefix, skip) {
|
|
|
3299
3385
|
}
|
|
3300
3386
|
return result;
|
|
3301
3387
|
}
|
|
3302
|
-
|
|
3388
|
+
function readVFSRecursive(fs, vfsPath) {
|
|
3389
|
+
const result = [];
|
|
3390
|
+
let entries;
|
|
3391
|
+
try {
|
|
3392
|
+
entries = fs.readdirSync(vfsPath, { withFileTypes: true });
|
|
3393
|
+
} catch {
|
|
3394
|
+
return result;
|
|
3395
|
+
}
|
|
3396
|
+
for (const entry of entries) {
|
|
3397
|
+
const fullPath = vfsPath === "/" ? `/${entry.name}` : `${vfsPath}/${entry.name}`;
|
|
3398
|
+
if (entry.isDirectory()) {
|
|
3399
|
+
result.push({ path: fullPath, type: "directory" });
|
|
3400
|
+
result.push(...readVFSRecursive(fs, fullPath));
|
|
3401
|
+
} else {
|
|
3402
|
+
try {
|
|
3403
|
+
const data = fs.readFileSync(fullPath);
|
|
3404
|
+
result.push({ path: fullPath, type: "file", data });
|
|
3405
|
+
} catch {
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
return result;
|
|
3410
|
+
}
|
|
3411
|
+
async function unpackToOPFS(root = "/", fs) {
|
|
3303
3412
|
const rootDir = await navigateToRoot(root);
|
|
3413
|
+
if (fs) {
|
|
3414
|
+
const vfsEntries = readVFSRecursive(fs, "/");
|
|
3415
|
+
let files2 = 0;
|
|
3416
|
+
let directories2 = 0;
|
|
3417
|
+
for (const entry of vfsEntries) {
|
|
3418
|
+
if (entry.type === "directory") {
|
|
3419
|
+
const name = basename2(entry.path);
|
|
3420
|
+
const parent = await ensureParentDirs(rootDir, entry.path);
|
|
3421
|
+
await parent.getDirectoryHandle(name, { create: true });
|
|
3422
|
+
directories2++;
|
|
3423
|
+
} else {
|
|
3424
|
+
try {
|
|
3425
|
+
await writeOPFSFile(rootDir, entry.path, entry.data ?? new Uint8Array(0));
|
|
3426
|
+
files2++;
|
|
3427
|
+
} catch (err) {
|
|
3428
|
+
console.warn(`[VFS] Failed to write OPFS file ${entry.path}: ${err.message}`);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
return { files: files2, directories: directories2 };
|
|
3433
|
+
}
|
|
3304
3434
|
const vfsFileHandle = await rootDir.getFileHandle(".vfs.bin");
|
|
3305
|
-
const handle = await vfsFileHandle
|
|
3435
|
+
const { handle } = await openVFSHandle(vfsFileHandle);
|
|
3306
3436
|
let entries;
|
|
3307
3437
|
try {
|
|
3308
3438
|
const engine = new VFSEngine();
|
|
@@ -3317,30 +3447,65 @@ async function unpackToOPFS(root = "/") {
|
|
|
3317
3447
|
for (const entry of entries) {
|
|
3318
3448
|
if (entry.path === "/") continue;
|
|
3319
3449
|
if (entry.type === INODE_TYPE.DIRECTORY) {
|
|
3320
|
-
await ensureParentDirs(rootDir, entry.path + "/dummy");
|
|
3321
3450
|
const name = basename2(entry.path);
|
|
3322
3451
|
const parent = await ensureParentDirs(rootDir, entry.path);
|
|
3323
3452
|
await parent.getDirectoryHandle(name, { create: true });
|
|
3324
3453
|
directories++;
|
|
3325
|
-
} else if (entry.type === INODE_TYPE.FILE) {
|
|
3326
|
-
await writeOPFSFile(rootDir, entry.path, entry.data ?? new Uint8Array(0));
|
|
3327
|
-
files++;
|
|
3328
|
-
} else if (entry.type === INODE_TYPE.SYMLINK) {
|
|
3454
|
+
} else if (entry.type === INODE_TYPE.FILE || entry.type === INODE_TYPE.SYMLINK) {
|
|
3329
3455
|
await writeOPFSFile(rootDir, entry.path, entry.data ?? new Uint8Array(0));
|
|
3330
3456
|
files++;
|
|
3331
3457
|
}
|
|
3332
3458
|
}
|
|
3333
3459
|
return { files, directories };
|
|
3334
3460
|
}
|
|
3335
|
-
async function loadFromOPFS(root = "/") {
|
|
3461
|
+
async function loadFromOPFS(root = "/", fs) {
|
|
3336
3462
|
const rootDir = await navigateToRoot(root);
|
|
3337
3463
|
const opfsEntries = await readOPFSRecursive(rootDir, "", /* @__PURE__ */ new Set([".vfs.bin"]));
|
|
3464
|
+
if (fs) {
|
|
3465
|
+
try {
|
|
3466
|
+
const rootEntries = fs.readdirSync("/");
|
|
3467
|
+
for (const entry of rootEntries) {
|
|
3468
|
+
try {
|
|
3469
|
+
fs.rmSync(`/${entry}`, { recursive: true, force: true });
|
|
3470
|
+
} catch {
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
} catch {
|
|
3474
|
+
}
|
|
3475
|
+
const dirs = opfsEntries.filter((e) => e.type === "directory").sort((a, b) => a.path.localeCompare(b.path));
|
|
3476
|
+
let files = 0;
|
|
3477
|
+
let directories = 0;
|
|
3478
|
+
for (const dir of dirs) {
|
|
3479
|
+
try {
|
|
3480
|
+
fs.mkdirSync(dir.path, { recursive: true, mode: 493 });
|
|
3481
|
+
directories++;
|
|
3482
|
+
} catch {
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
const fileEntries = opfsEntries.filter((e) => e.type === "file");
|
|
3486
|
+
for (const file of fileEntries) {
|
|
3487
|
+
try {
|
|
3488
|
+
const parentPath = file.path.substring(0, file.path.lastIndexOf("/")) || "/";
|
|
3489
|
+
if (parentPath !== "/") {
|
|
3490
|
+
try {
|
|
3491
|
+
fs.mkdirSync(parentPath, { recursive: true, mode: 493 });
|
|
3492
|
+
} catch {
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
fs.writeFileSync(file.path, new Uint8Array(file.data));
|
|
3496
|
+
files++;
|
|
3497
|
+
} catch (err) {
|
|
3498
|
+
console.warn(`[VFS] Failed to write ${file.path}: ${err.message}`);
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
return { files, directories };
|
|
3502
|
+
}
|
|
3338
3503
|
try {
|
|
3339
3504
|
await rootDir.removeEntry(".vfs.bin");
|
|
3340
3505
|
} catch (_) {
|
|
3341
3506
|
}
|
|
3342
3507
|
const vfsFileHandle = await rootDir.getFileHandle(".vfs.bin", { create: true });
|
|
3343
|
-
const handle = await vfsFileHandle
|
|
3508
|
+
const { handle, isMemory } = await openFreshVFSHandle(vfsFileHandle);
|
|
3344
3509
|
try {
|
|
3345
3510
|
const engine = new VFSEngine();
|
|
3346
3511
|
engine.init(handle);
|
|
@@ -3357,12 +3522,29 @@ async function loadFromOPFS(root = "/") {
|
|
|
3357
3522
|
files++;
|
|
3358
3523
|
}
|
|
3359
3524
|
engine.flush();
|
|
3525
|
+
if (isMemory) {
|
|
3526
|
+
await saveMemoryHandle(vfsFileHandle, handle);
|
|
3527
|
+
}
|
|
3360
3528
|
return { files, directories };
|
|
3361
3529
|
} finally {
|
|
3362
3530
|
handle.close();
|
|
3363
3531
|
}
|
|
3364
3532
|
}
|
|
3365
|
-
async function repairVFS(root = "/") {
|
|
3533
|
+
async function repairVFS(root = "/", fs) {
|
|
3534
|
+
if (fs) {
|
|
3535
|
+
const loadResult = await loadFromOPFS(root, fs);
|
|
3536
|
+
await unpackToOPFS(root, fs);
|
|
3537
|
+
const total = loadResult.files + loadResult.directories;
|
|
3538
|
+
return {
|
|
3539
|
+
recovered: total,
|
|
3540
|
+
lost: 0,
|
|
3541
|
+
entries: []
|
|
3542
|
+
// Detailed entries not available in fs-based path
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
return repairVFSRaw(root);
|
|
3546
|
+
}
|
|
3547
|
+
async function repairVFSRaw(root) {
|
|
3366
3548
|
const rootDir = await navigateToRoot(root);
|
|
3367
3549
|
const vfsFileHandle = await rootDir.getFileHandle(".vfs.bin");
|
|
3368
3550
|
const file = await vfsFileHandle.getFile();
|
|
@@ -3458,7 +3640,7 @@ async function repairVFS(root = "/") {
|
|
|
3458
3640
|
}
|
|
3459
3641
|
await rootDir.removeEntry(".vfs.bin");
|
|
3460
3642
|
const newFileHandle = await rootDir.getFileHandle(".vfs.bin", { create: true });
|
|
3461
|
-
const handle = await newFileHandle
|
|
3643
|
+
const { handle, isMemory } = await openFreshVFSHandle(newFileHandle);
|
|
3462
3644
|
try {
|
|
3463
3645
|
const engine = new VFSEngine();
|
|
3464
3646
|
engine.init(handle);
|
|
@@ -3479,6 +3661,9 @@ async function repairVFS(root = "/") {
|
|
|
3479
3661
|
if (result.status !== 0) lost++;
|
|
3480
3662
|
}
|
|
3481
3663
|
engine.flush();
|
|
3664
|
+
if (isMemory) {
|
|
3665
|
+
await saveMemoryHandle(newFileHandle, handle);
|
|
3666
|
+
}
|
|
3482
3667
|
} finally {
|
|
3483
3668
|
handle.close();
|
|
3484
3669
|
}
|