@rljson/fs-agent 0.0.19 → 0.0.21
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/dist/fs-agent.js +105 -67
- package/dist/fs-scanner.d.ts +13 -0
- package/package.json +1 -1
package/dist/fs-agent.js
CHANGED
|
@@ -441,6 +441,8 @@ class FsScanner {
|
|
|
441
441
|
_nextBlobCache = /* @__PURE__ */ new Map();
|
|
442
442
|
/** Ensures the persisted cache is read from disk only once. */
|
|
443
443
|
_cacheLoaded = false;
|
|
444
|
+
/** Entries that vanished mid-scan, counted per {@link scan} for one summary. */
|
|
445
|
+
_vanishedDuringScan = 0;
|
|
444
446
|
constructor(rootPath, options = {}) {
|
|
445
447
|
this._rootPath = rootPath;
|
|
446
448
|
this._options = {
|
|
@@ -485,6 +487,7 @@ class FsScanner {
|
|
|
485
487
|
this._nextBlobCache = /* @__PURE__ */ new Map();
|
|
486
488
|
}
|
|
487
489
|
const trees = /* @__PURE__ */ new Map();
|
|
490
|
+
this._vanishedDuringScan = 0;
|
|
488
491
|
let rootTree;
|
|
489
492
|
try {
|
|
490
493
|
rootTree = await this._scanDirectory(this._rootPath, ".", 0, trees);
|
|
@@ -500,6 +503,17 @@ class FsScanner {
|
|
|
500
503
|
"Failed to generate hash for root tree. Tree structure may be invalid."
|
|
501
504
|
);
|
|
502
505
|
}
|
|
506
|
+
if (this._vanishedDuringScan > 0) {
|
|
507
|
+
console.warn(
|
|
508
|
+
`[fs-scanner] ${this._vanishedDuringScan} entr${this._vanishedDuringScan === 1 ? "y" : "ies"} vanished during the scan of ${this._rootPath} — this scan is a partial picture`
|
|
509
|
+
);
|
|
510
|
+
if (this._tree) {
|
|
511
|
+
return this._tree;
|
|
512
|
+
}
|
|
513
|
+
console.warn(
|
|
514
|
+
`[fs-scanner] no previous scan of ${this._rootPath} to fall back on — starting from the partial one`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
503
517
|
trees.set(rootHashStr, rootTree);
|
|
504
518
|
this._tree = {
|
|
505
519
|
rootHash: rootHashStr,
|
|
@@ -513,6 +527,7 @@ class FsScanner {
|
|
|
513
527
|
}
|
|
514
528
|
async _scanDirectory(absolutePath, relativePath, depth, trees) {
|
|
515
529
|
const entries = await readdir(absolutePath, { withFileTypes: true });
|
|
530
|
+
const childTrees = [];
|
|
516
531
|
const childRefs = [];
|
|
517
532
|
for (const entry of entries) {
|
|
518
533
|
if (this._shouldIgnore(entry.name)) {
|
|
@@ -526,78 +541,87 @@ class FsScanner {
|
|
|
526
541
|
if (this._options.maxDepth !== void 0 && depth >= this._options.maxDepth) {
|
|
527
542
|
continue;
|
|
528
543
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
fileContent
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
)
|
|
544
|
+
try {
|
|
545
|
+
const childStats = await stat(childPath);
|
|
546
|
+
if (entry.isDirectory()) {
|
|
547
|
+
const childTree = await this._scanDirectory(
|
|
548
|
+
childPath,
|
|
549
|
+
childRelPath,
|
|
550
|
+
depth + 1,
|
|
551
|
+
trees
|
|
552
|
+
);
|
|
553
|
+
childTrees.push(childTree);
|
|
554
|
+
hip(childTree);
|
|
555
|
+
const childHashStr = childTree._hash;
|
|
556
|
+
trees.set(childHashStr, childTree);
|
|
557
|
+
childRefs.push(childHashStr);
|
|
558
|
+
} else if (entry.isFile()) {
|
|
559
|
+
const mtimeMs = childStats.mtime.getTime();
|
|
560
|
+
const cached = this._scanCachePath ? this._blobCache.get(childRelPath) : void 0;
|
|
561
|
+
let blobId;
|
|
562
|
+
if (cached && cached.mtime === mtimeMs && cached.size === childStats.size) {
|
|
563
|
+
blobId = cached.blobId;
|
|
564
|
+
} else {
|
|
565
|
+
let fileContent;
|
|
566
|
+
try {
|
|
567
|
+
fileContent = await readFile(childPath);
|
|
568
|
+
} catch (error) {
|
|
569
|
+
if (FsScanner._isVanished(error)) throw error;
|
|
570
|
+
throw new Error(
|
|
571
|
+
`Failed to read file "${childRelPath}": ${error instanceof Error ? error.message : String(error)}`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
let blobProps;
|
|
575
|
+
try {
|
|
576
|
+
blobProps = await this._bs.setBlob(fileContent);
|
|
577
|
+
} catch (error) {
|
|
578
|
+
throw new Error(
|
|
579
|
+
`Failed to store blob for file "${childRelPath}": ${error instanceof Error ? error.message : String(error)}`
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
if (!blobProps || !blobProps.blobId) {
|
|
583
|
+
throw new Error(
|
|
584
|
+
`Blob storage returned invalid blobId for file "${childRelPath}"`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
blobId = blobProps.blobId;
|
|
563
588
|
}
|
|
564
|
-
if (
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
589
|
+
if (this._scanCachePath) {
|
|
590
|
+
this._nextBlobCache.set(childRelPath, {
|
|
591
|
+
mtime: mtimeMs,
|
|
592
|
+
size: childStats.size,
|
|
593
|
+
blobId
|
|
594
|
+
});
|
|
568
595
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
mtime: mtimeMs,
|
|
596
|
+
const fileMeta = {
|
|
597
|
+
name: entry.name,
|
|
598
|
+
type: "file",
|
|
599
|
+
relativePath: childRelPath,
|
|
574
600
|
size: childStats.size,
|
|
601
|
+
// mtime is kept for files (restore preserves it, so it round-trips to
|
|
602
|
+
// the same ref on every client) but NOT for directories (a folder's
|
|
603
|
+
// mtime is per-machine and does not round-trip). The absolute `path`
|
|
604
|
+
// is excluded everywhere — it is folder-specific.
|
|
605
|
+
mtime: mtimeMs,
|
|
575
606
|
blobId
|
|
576
|
-
|
|
607
|
+
// Link to content in Bs
|
|
608
|
+
};
|
|
609
|
+
const fileTree = {
|
|
610
|
+
id: entry.name,
|
|
611
|
+
isParent: false,
|
|
612
|
+
meta: fileMeta,
|
|
613
|
+
children: null
|
|
614
|
+
};
|
|
615
|
+
childTrees.push(fileTree);
|
|
616
|
+
hip(fileTree);
|
|
617
|
+
const fileTreeHashStr = fileTree._hash;
|
|
618
|
+
trees.set(fileTreeHashStr, fileTree);
|
|
619
|
+
childRefs.push(fileTreeHashStr);
|
|
577
620
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
size: childStats.size,
|
|
583
|
-
// mtime is kept for files (restore preserves it, so it round-trips to
|
|
584
|
-
// the same ref on every client) but NOT for directories (a folder's
|
|
585
|
-
// mtime is per-machine and does not round-trip). The absolute `path`
|
|
586
|
-
// is excluded everywhere — it is folder-specific.
|
|
587
|
-
mtime: mtimeMs,
|
|
588
|
-
blobId
|
|
589
|
-
// Link to content in Bs
|
|
590
|
-
};
|
|
591
|
-
const fileTree = {
|
|
592
|
-
id: entry.name,
|
|
593
|
-
isParent: false,
|
|
594
|
-
meta: fileMeta,
|
|
595
|
-
children: null
|
|
596
|
-
};
|
|
597
|
-
hip(fileTree);
|
|
598
|
-
const fileTreeHashStr = fileTree._hash;
|
|
599
|
-
trees.set(fileTreeHashStr, fileTree);
|
|
600
|
-
childRefs.push(fileTreeHashStr);
|
|
621
|
+
} catch (error) {
|
|
622
|
+
if (!FsScanner._isVanished(error)) throw error;
|
|
623
|
+
this._vanishedDuringScan++;
|
|
624
|
+
continue;
|
|
601
625
|
}
|
|
602
626
|
}
|
|
603
627
|
const dirName = relativePath === "." ? "." : (
|
|
@@ -766,6 +790,20 @@ class FsScanner {
|
|
|
766
790
|
static _errMessage(err) {
|
|
767
791
|
return err instanceof Error ? err.message : String(err);
|
|
768
792
|
}
|
|
793
|
+
/**
|
|
794
|
+
* Whether a caught value is a "no longer there" filesystem error.
|
|
795
|
+
*
|
|
796
|
+
* A scan walks a directory that is still being written to. `readdir` returns
|
|
797
|
+
* a name, and by the time the entry is `stat`ed, read, or descended into it
|
|
798
|
+
* can be gone — a save-and-rename editor, a build tool, a peer applying a
|
|
799
|
+
* deletion. That is ordinary, not exceptional.
|
|
800
|
+
* @param err - The caught value.
|
|
801
|
+
* @returns `true` for ENOENT / ENOTDIR.
|
|
802
|
+
*/
|
|
803
|
+
static _isVanished(err) {
|
|
804
|
+
const code = err?.code;
|
|
805
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
806
|
+
}
|
|
769
807
|
/** Whether the host is Windows — gates Windows-specific watcher hardening. */
|
|
770
808
|
static get _isWindows() {
|
|
771
809
|
return process.platform === "win32";
|
package/dist/fs-scanner.d.ts
CHANGED
|
@@ -117,6 +117,8 @@ export declare class FsScanner {
|
|
|
117
117
|
private _nextBlobCache;
|
|
118
118
|
/** Ensures the persisted cache is read from disk only once. */
|
|
119
119
|
private _cacheLoaded;
|
|
120
|
+
/** Entries that vanished mid-scan, counted per {@link scan} for one summary. */
|
|
121
|
+
private _vanishedDuringScan;
|
|
120
122
|
constructor(rootPath: string, options?: FsScanOptions);
|
|
121
123
|
get tree(): FsTree | null;
|
|
122
124
|
get rootPath(): string;
|
|
@@ -156,6 +158,17 @@ export declare class FsScanner {
|
|
|
156
158
|
* @returns A message string
|
|
157
159
|
*/
|
|
158
160
|
private static _errMessage;
|
|
161
|
+
/**
|
|
162
|
+
* Whether a caught value is a "no longer there" filesystem error.
|
|
163
|
+
*
|
|
164
|
+
* A scan walks a directory that is still being written to. `readdir` returns
|
|
165
|
+
* a name, and by the time the entry is `stat`ed, read, or descended into it
|
|
166
|
+
* can be gone — a save-and-rename editor, a build tool, a peer applying a
|
|
167
|
+
* deletion. That is ordinary, not exceptional.
|
|
168
|
+
* @param err - The caught value.
|
|
169
|
+
* @returns `true` for ENOENT / ENOTDIR.
|
|
170
|
+
*/
|
|
171
|
+
private static _isVanished;
|
|
159
172
|
/** Whether the host is Windows — gates Windows-specific watcher hardening. */
|
|
160
173
|
private static get _isWindows();
|
|
161
174
|
private _handleFileChange;
|