zen-fs-config 0.5.42 → 0.5.44
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/index.js +5 -1
- package/dist/index.mjs +5 -1
- package/dist/zen-fs-config.js +208 -112
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2901,7 +2901,11 @@ var AppDataGroupImpl = class {
|
|
|
2901
2901
|
_pollIntervalMs;
|
|
2902
2902
|
async init() {
|
|
2903
2903
|
try {
|
|
2904
|
-
const localPrimary = await resolveLocalPrimary(
|
|
2904
|
+
const localPrimary = await resolveLocalPrimary(
|
|
2905
|
+
this.appId,
|
|
2906
|
+
"data",
|
|
2907
|
+
this.groupId === "default" ? {} : { idbStoreName: `zen-fs-config-data-${this.appId}-${this.groupId}` }
|
|
2908
|
+
);
|
|
2905
2909
|
this.localFS = await createBackend(localPrimary);
|
|
2906
2910
|
} catch {
|
|
2907
2911
|
throw new Error(`Failed to create local primary for data group "${this.groupId}"`);
|
package/dist/index.mjs
CHANGED
|
@@ -2361,7 +2361,11 @@ var AppDataGroupImpl = class {
|
|
|
2361
2361
|
_pollIntervalMs;
|
|
2362
2362
|
async init() {
|
|
2363
2363
|
try {
|
|
2364
|
-
const localPrimary = await resolveLocalPrimary(
|
|
2364
|
+
const localPrimary = await resolveLocalPrimary(
|
|
2365
|
+
this.appId,
|
|
2366
|
+
"data",
|
|
2367
|
+
this.groupId === "default" ? {} : { idbStoreName: `zen-fs-config-data-${this.appId}-${this.groupId}` }
|
|
2368
|
+
);
|
|
2365
2369
|
this.localFS = await createBackend(localPrimary);
|
|
2366
2370
|
} catch {
|
|
2367
2371
|
throw new Error(`Failed to create local primary for data group "${this.groupId}"`);
|
package/dist/zen-fs-config.js
CHANGED
|
@@ -1163,7 +1163,7 @@ var ZenFSConfig = (() => {
|
|
|
1163
1163
|
fancy: () => fancy,
|
|
1164
1164
|
format: () => format,
|
|
1165
1165
|
info: () => info,
|
|
1166
|
-
isEnabled: () =>
|
|
1166
|
+
isEnabled: () => isEnabled2,
|
|
1167
1167
|
levelOf: () => levelOf,
|
|
1168
1168
|
levels: () => levels,
|
|
1169
1169
|
log: () => log4,
|
|
@@ -1174,7 +1174,7 @@ var ZenFSConfig = (() => {
|
|
|
1174
1174
|
return levels.indexOf(value);
|
|
1175
1175
|
}
|
|
1176
1176
|
function log4(level, message) {
|
|
1177
|
-
if (!
|
|
1177
|
+
if (!isEnabled2)
|
|
1178
1178
|
return;
|
|
1179
1179
|
const entry = {
|
|
1180
1180
|
level,
|
|
@@ -1232,7 +1232,7 @@ var ZenFSConfig = (() => {
|
|
|
1232
1232
|
_format = options.format ?? _format;
|
|
1233
1233
|
_output = options.output ?? _output;
|
|
1234
1234
|
minLevel = typeof options.level == "string" ? levelOf(options.level) : options.level ?? minLevel;
|
|
1235
|
-
|
|
1235
|
+
isEnabled2 = options.enabled ?? isEnabled2;
|
|
1236
1236
|
includeStack = options.stack ?? includeStack;
|
|
1237
1237
|
if (!options.dumpBacklog)
|
|
1238
1238
|
return;
|
|
@@ -1240,7 +1240,7 @@ var ZenFSConfig = (() => {
|
|
|
1240
1240
|
output(entry);
|
|
1241
1241
|
}
|
|
1242
1242
|
}
|
|
1243
|
-
var Level, levels, entries, emerg, alert, crit, err, warn, notice, info, debug, levelColor, messageColor, _format, _output, minLevel, includeStack,
|
|
1243
|
+
var Level, levels, entries, emerg, alert, crit, err, warn, notice, info, debug, levelColor, messageColor, _format, _output, minLevel, includeStack, isEnabled2;
|
|
1244
1244
|
var init_log = __esm({
|
|
1245
1245
|
"node_modules/kerium/dist/log.js"() {
|
|
1246
1246
|
"use strict";
|
|
@@ -1322,7 +1322,7 @@ var ZenFSConfig = (() => {
|
|
|
1322
1322
|
_output = console.error;
|
|
1323
1323
|
minLevel = Level.ALERT;
|
|
1324
1324
|
includeStack = false;
|
|
1325
|
-
|
|
1325
|
+
isEnabled2 = true;
|
|
1326
1326
|
}
|
|
1327
1327
|
});
|
|
1328
1328
|
|
|
@@ -25298,6 +25298,66 @@ var ZenFSConfig = (() => {
|
|
|
25298
25298
|
writeVersion: () => writeVersion
|
|
25299
25299
|
});
|
|
25300
25300
|
|
|
25301
|
+
// node_modules/@richard432/localstorage-logger/dist/index.mjs
|
|
25302
|
+
var PREFIX = "debug:";
|
|
25303
|
+
function hasLocalStorage() {
|
|
25304
|
+
try {
|
|
25305
|
+
return typeof localStorage !== "undefined" && localStorage !== null;
|
|
25306
|
+
} catch {
|
|
25307
|
+
return false;
|
|
25308
|
+
}
|
|
25309
|
+
}
|
|
25310
|
+
function getProcessEnv() {
|
|
25311
|
+
try {
|
|
25312
|
+
if (typeof process !== "undefined" && process.env !== void 0) {
|
|
25313
|
+
return process.env;
|
|
25314
|
+
}
|
|
25315
|
+
} catch {
|
|
25316
|
+
}
|
|
25317
|
+
return null;
|
|
25318
|
+
}
|
|
25319
|
+
function moduleToEnvKey(module) {
|
|
25320
|
+
return "DEBUG_" + module.replace(/[:-]/g, "_").toUpperCase();
|
|
25321
|
+
}
|
|
25322
|
+
function isEnabled(module) {
|
|
25323
|
+
const key = `${PREFIX}${module}`;
|
|
25324
|
+
if (hasLocalStorage()) {
|
|
25325
|
+
const val = localStorage.getItem(key);
|
|
25326
|
+
if (val === null) {
|
|
25327
|
+
localStorage.setItem(key, "1");
|
|
25328
|
+
return true;
|
|
25329
|
+
}
|
|
25330
|
+
return val === "1";
|
|
25331
|
+
}
|
|
25332
|
+
const env = getProcessEnv();
|
|
25333
|
+
if (env) {
|
|
25334
|
+
const envKey = moduleToEnvKey(module);
|
|
25335
|
+
const val = env[envKey];
|
|
25336
|
+
if (val === void 0) return true;
|
|
25337
|
+
return val === "1";
|
|
25338
|
+
}
|
|
25339
|
+
return true;
|
|
25340
|
+
}
|
|
25341
|
+
function createLogger(module) {
|
|
25342
|
+
return {
|
|
25343
|
+
log(...args) {
|
|
25344
|
+
if (isEnabled(module)) {
|
|
25345
|
+
console.log(`[${module}]`, ...args);
|
|
25346
|
+
}
|
|
25347
|
+
},
|
|
25348
|
+
warn(...args) {
|
|
25349
|
+
if (isEnabled(module)) {
|
|
25350
|
+
console.warn(`[${module}]`, ...args);
|
|
25351
|
+
}
|
|
25352
|
+
},
|
|
25353
|
+
error(...args) {
|
|
25354
|
+
if (isEnabled(module)) {
|
|
25355
|
+
console.error(`[${module}]`, ...args);
|
|
25356
|
+
}
|
|
25357
|
+
}
|
|
25358
|
+
};
|
|
25359
|
+
}
|
|
25360
|
+
|
|
25301
25361
|
// node_modules/zen-fs-sync/dist/index.mjs
|
|
25302
25362
|
function isDirectory(stat5) {
|
|
25303
25363
|
return stat5.mode !== void 0 && (stat5.mode & 16384) === 16384;
|
|
@@ -25310,16 +25370,12 @@ var ZenFSConfig = (() => {
|
|
|
25310
25370
|
SyncDirection2["BiDirectional"] = "bi-directional";
|
|
25311
25371
|
return SyncDirection2;
|
|
25312
25372
|
})(SyncDirection || {});
|
|
25313
|
-
var
|
|
25314
|
-
|
|
25315
|
-
|
|
25316
|
-
return (...args) =>
|
|
25317
|
-
if (!enabled) return;
|
|
25318
|
-
if (tagFilter.size > 0 && !tagFilter.has(tag)) return;
|
|
25319
|
-
console.log(`[zen-fs-sync:${tag}]`, ...args);
|
|
25320
|
-
};
|
|
25373
|
+
var MODULE_PREFIX = "zen-fs-sync";
|
|
25374
|
+
function createLogger2(tag) {
|
|
25375
|
+
const logger = createLogger(`${MODULE_PREFIX}:${tag}`);
|
|
25376
|
+
return (...args) => logger.log(...args);
|
|
25321
25377
|
}
|
|
25322
|
-
var log =
|
|
25378
|
+
var log = createLogger2("detector");
|
|
25323
25379
|
function normalizePath(p) {
|
|
25324
25380
|
let s = p.replace(/\\/g, "/");
|
|
25325
25381
|
if (s.length > 1 && s.endsWith("/")) {
|
|
@@ -25367,6 +25423,11 @@ var ZenFSConfig = (() => {
|
|
|
25367
25423
|
);
|
|
25368
25424
|
return regex.test(str);
|
|
25369
25425
|
}
|
|
25426
|
+
function describeFs(fs) {
|
|
25427
|
+
if (fs.backendName) return fs.backendName;
|
|
25428
|
+
const ctorName = fs.constructor?.name;
|
|
25429
|
+
return ctorName && ctorName !== "Object" ? ctorName : "unknown";
|
|
25430
|
+
}
|
|
25370
25431
|
async function walkFiles(fs, root, filter) {
|
|
25371
25432
|
const results = [];
|
|
25372
25433
|
const normalizedRoot = normalizePath(root);
|
|
@@ -25392,6 +25453,18 @@ var ZenFSConfig = (() => {
|
|
|
25392
25453
|
await visit(fullPath);
|
|
25393
25454
|
} else if (isFile(stat5)) {
|
|
25394
25455
|
if (!isPathAllowed(relPath, filter)) continue;
|
|
25456
|
+
if (relPath.endsWith(".mtime")) {
|
|
25457
|
+
console.warn(
|
|
25458
|
+
`[zen-fs-sync] mtime sidecar leaked from backend "${describeFs(fs)}" - its readdir()/createSnapshot() must filter sidecars; skipping ${fullPath} (rel ${relPath})`
|
|
25459
|
+
);
|
|
25460
|
+
continue;
|
|
25461
|
+
}
|
|
25462
|
+
if (relPath === "/.keep" || relPath.endsWith("/.keep")) {
|
|
25463
|
+
console.warn(
|
|
25464
|
+
`[zen-fs-sync] '.keep' placeholder leaked from backend "${describeFs(fs)}" - its readdir() must filter the internal placeholder; skipping ${fullPath} (rel ${relPath})`
|
|
25465
|
+
);
|
|
25466
|
+
continue;
|
|
25467
|
+
}
|
|
25395
25468
|
results.push(relPath);
|
|
25396
25469
|
}
|
|
25397
25470
|
}
|
|
@@ -25399,13 +25472,48 @@ var ZenFSConfig = (() => {
|
|
|
25399
25472
|
await visit(normalizedRoot);
|
|
25400
25473
|
return results;
|
|
25401
25474
|
}
|
|
25475
|
+
async function collectDirectories(fs, root, filter) {
|
|
25476
|
+
const results = [];
|
|
25477
|
+
const normalizedRoot = normalizePath(root);
|
|
25478
|
+
async function visit(dir) {
|
|
25479
|
+
let entries2;
|
|
25480
|
+
try {
|
|
25481
|
+
entries2 = await fs.readdir(dir);
|
|
25482
|
+
} catch {
|
|
25483
|
+
return;
|
|
25484
|
+
}
|
|
25485
|
+
for (const entry of entries2) {
|
|
25486
|
+
if (entry === ".zenfs-sync") continue;
|
|
25487
|
+
const fullPath = resolvePath(dir, entry);
|
|
25488
|
+
let relPath = fullPath.slice(normalizedRoot.length) || "/";
|
|
25489
|
+
if (!relPath.startsWith("/")) relPath = "/" + relPath;
|
|
25490
|
+
let stat5;
|
|
25491
|
+
try {
|
|
25492
|
+
stat5 = await fs.stat(fullPath);
|
|
25493
|
+
} catch {
|
|
25494
|
+
continue;
|
|
25495
|
+
}
|
|
25496
|
+
if (isDirectory(stat5)) {
|
|
25497
|
+
if (isPathAllowed(relPath, filter)) results.push(relPath);
|
|
25498
|
+
await visit(fullPath);
|
|
25499
|
+
}
|
|
25500
|
+
}
|
|
25501
|
+
}
|
|
25502
|
+
await visit(normalizedRoot);
|
|
25503
|
+
return results;
|
|
25504
|
+
}
|
|
25402
25505
|
async function buildSnapshot(fs, root, filter) {
|
|
25403
25506
|
const normalizedRoot = normalizePath(root);
|
|
25404
25507
|
const fsName = fs.backendName || "unknown";
|
|
25405
25508
|
try {
|
|
25406
25509
|
await fs.readdir(normalizedRoot);
|
|
25407
|
-
} catch {
|
|
25408
|
-
|
|
25510
|
+
} catch (err2) {
|
|
25511
|
+
const e = err2;
|
|
25512
|
+
if (e && (e.name === "DirectoryNotFoundError" || e.code === "ENOENT")) {
|
|
25513
|
+
log(`buildSnapshot(${fsName}): root missing, treating as empty`);
|
|
25514
|
+
return /* @__PURE__ */ new Map();
|
|
25515
|
+
}
|
|
25516
|
+
log(`buildSnapshot(${fsName}): unreachable, returning null`);
|
|
25409
25517
|
return null;
|
|
25410
25518
|
}
|
|
25411
25519
|
const files = await walkFiles(fs, root, filter);
|
|
@@ -25466,7 +25574,7 @@ var ZenFSConfig = (() => {
|
|
|
25466
25574
|
}
|
|
25467
25575
|
function diffSnapshots(source, target) {
|
|
25468
25576
|
if (source === null || target === null) {
|
|
25469
|
-
|
|
25577
|
+
log(`diffSnapshots: one side is null (unreachable) \u2014 skipping to prevent false deletions`);
|
|
25470
25578
|
return [];
|
|
25471
25579
|
}
|
|
25472
25580
|
const changes = [];
|
|
@@ -25492,7 +25600,7 @@ var ZenFSConfig = (() => {
|
|
|
25492
25600
|
}
|
|
25493
25601
|
return changes;
|
|
25494
25602
|
}
|
|
25495
|
-
var log2 =
|
|
25603
|
+
var log2 = createLogger2("detector");
|
|
25496
25604
|
var IncrementalDetector = class {
|
|
25497
25605
|
async detect(source, target, root, prevSnapshots, filter) {
|
|
25498
25606
|
if (!prevSnapshots || prevSnapshots.size === 0) {
|
|
@@ -25559,7 +25667,7 @@ var ZenFSConfig = (() => {
|
|
|
25559
25667
|
}
|
|
25560
25668
|
}
|
|
25561
25669
|
};
|
|
25562
|
-
var log3 =
|
|
25670
|
+
var log3 = createLogger2("sync");
|
|
25563
25671
|
var SyncPair = class {
|
|
25564
25672
|
constructor(source, target, options = {}, syncRoot = "/") {
|
|
25565
25673
|
__publicField(this, "syncRoot");
|
|
@@ -25572,6 +25680,7 @@ var ZenFSConfig = (() => {
|
|
|
25572
25680
|
__publicField(this, "resolver");
|
|
25573
25681
|
__publicField(this, "state", "idle");
|
|
25574
25682
|
__publicField(this, "lastResult");
|
|
25683
|
+
__publicField(this, "lastError");
|
|
25575
25684
|
__publicField(this, "lastCheckTime");
|
|
25576
25685
|
__publicField(this, "totalSyncs", 0);
|
|
25577
25686
|
// 轮询定时器列表(远程 shouldSync 轮询,或两端都不支持变更检测时的兜底轮询)
|
|
@@ -25632,7 +25741,7 @@ var ZenFSConfig = (() => {
|
|
|
25632
25741
|
try {
|
|
25633
25742
|
await this.options.preSyncHook();
|
|
25634
25743
|
} catch (err2) {
|
|
25635
|
-
|
|
25744
|
+
log3(`[zen-fs-sync] preSyncHook error pairId=${this.pairId}`, err2);
|
|
25636
25745
|
} finally {
|
|
25637
25746
|
this.hookInProgress = false;
|
|
25638
25747
|
}
|
|
@@ -25647,18 +25756,21 @@ var ZenFSConfig = (() => {
|
|
|
25647
25756
|
try {
|
|
25648
25757
|
await this.options.postSyncHook();
|
|
25649
25758
|
} catch (err2) {
|
|
25650
|
-
|
|
25759
|
+
log3(`[zen-fs-sync] postSyncHook error pairId=${this.pairId}`, err2);
|
|
25651
25760
|
} finally {
|
|
25652
25761
|
this.hookInProgress = false;
|
|
25653
25762
|
}
|
|
25654
25763
|
}
|
|
25655
25764
|
result.durationMs = Date.now() - startTime;
|
|
25656
25765
|
this.lastResult = result;
|
|
25766
|
+
if (result.errors && result.errors.length > 0) {
|
|
25767
|
+
this.lastError = { message: result.errors[0].message, timestamp: result.timestamp };
|
|
25768
|
+
}
|
|
25657
25769
|
this.totalSyncs++;
|
|
25658
25770
|
this.state = this.pollTimers.length > 0 ? "watching" : "idle";
|
|
25659
25771
|
const hasActivity = result.filesCreated > 0 || result.filesUpdated > 0 || result.filesDeleted > 0 || result.conflicts.length > 0;
|
|
25660
25772
|
if (hasActivity) {
|
|
25661
|
-
|
|
25773
|
+
log3(`[zen-fs-sync] sync pairId=${this.pairId} +${result.filesCreated}/~${result.filesUpdated}/-${result.filesDeleted} skip:${result.filesSkipped} conflicts:${result.conflicts.length} ${result.durationMs}ms`);
|
|
25662
25774
|
}
|
|
25663
25775
|
this.emit({
|
|
25664
25776
|
type: "sync:end",
|
|
@@ -25669,7 +25781,8 @@ var ZenFSConfig = (() => {
|
|
|
25669
25781
|
return result;
|
|
25670
25782
|
} catch (error) {
|
|
25671
25783
|
this.state = this.pollTimers.length > 0 ? "watching" : "idle";
|
|
25672
|
-
|
|
25784
|
+
this.lastError = { message: error instanceof Error ? error.message : String(error), timestamp: Date.now() };
|
|
25785
|
+
log3(`[zen-fs-sync] sync ERROR pairId=${this.pairId}`, error);
|
|
25673
25786
|
this.emit({
|
|
25674
25787
|
type: "sync:error",
|
|
25675
25788
|
pairId: this.pairId,
|
|
@@ -25760,6 +25873,7 @@ var ZenFSConfig = (() => {
|
|
|
25760
25873
|
state: this.state,
|
|
25761
25874
|
lastResult: this.lastResult,
|
|
25762
25875
|
lastCheckTime: this.lastCheckTime,
|
|
25876
|
+
lastError: this.lastError,
|
|
25763
25877
|
// 是否正在 watch:有轮询定时器或处于 Watching 状态
|
|
25764
25878
|
watching: this.pollTimers.length > 0 || this.state === "watching",
|
|
25765
25879
|
totalSyncs: this.totalSyncs
|
|
@@ -25814,7 +25928,7 @@ var ZenFSConfig = (() => {
|
|
|
25814
25928
|
this.options.filter
|
|
25815
25929
|
);
|
|
25816
25930
|
if (changes.length > 0) {
|
|
25817
|
-
|
|
25931
|
+
log3(`[zen-fs-sync] syncOneWay START direction=${directionLabel} changes=${changes.length}`);
|
|
25818
25932
|
}
|
|
25819
25933
|
const newSnap = await this.getSnapshot(src);
|
|
25820
25934
|
if (newSnap !== null) {
|
|
@@ -25825,6 +25939,7 @@ var ZenFSConfig = (() => {
|
|
|
25825
25939
|
let filesDeleted = 0;
|
|
25826
25940
|
let filesSkipped = 0;
|
|
25827
25941
|
const conflicts = [];
|
|
25942
|
+
const errors = [];
|
|
25828
25943
|
for (const change of changes) {
|
|
25829
25944
|
const srcPath = resolvePath(this.root, change.path);
|
|
25830
25945
|
const tgtPath = resolvePath(this.root, change.path);
|
|
@@ -25868,37 +25983,40 @@ var ZenFSConfig = (() => {
|
|
|
25868
25983
|
try {
|
|
25869
25984
|
const tgtContent = await tgt.readFile(tgtPath, "utf-8");
|
|
25870
25985
|
if (srcContent === tgtContent) {
|
|
25871
|
-
|
|
25986
|
+
log3(`[zen-fs-sync] WRITE SKIP (content identical) ${change.path}`);
|
|
25872
25987
|
filesSkipped++;
|
|
25873
25988
|
break;
|
|
25874
25989
|
}
|
|
25875
25990
|
} catch {
|
|
25876
25991
|
}
|
|
25877
|
-
|
|
25992
|
+
log3(`[zen-fs-sync] WRITE ${change.type} [${directionLabel}] ${srcPath} \u2192 ${tgtPath} (${srcContent.length} chars)`);
|
|
25878
25993
|
await ensureDir(tgt, tgtPath.substring(0, tgtPath.lastIndexOf("/")));
|
|
25879
25994
|
await tgt.writeFile(tgtPath, srcContent);
|
|
25880
25995
|
if (isCreated) filesCreated++;
|
|
25881
25996
|
else filesUpdated++;
|
|
25882
25997
|
} catch (err2) {
|
|
25883
|
-
|
|
25998
|
+
log3(`[zen-fs-sync] WRITE FAIL ${change.type} [${directionLabel}] ${srcPath} \u2192 ${tgtPath}:`, err2);
|
|
25999
|
+
errors.push({ path: change.path, message: err2 instanceof Error ? err2.message : String(err2) });
|
|
25884
26000
|
filesSkipped++;
|
|
25885
26001
|
}
|
|
25886
26002
|
break;
|
|
25887
26003
|
}
|
|
25888
26004
|
case "deleted": {
|
|
25889
26005
|
try {
|
|
25890
|
-
|
|
25891
|
-
|
|
26006
|
+
log3(`[zen-fs-sync] DELETE [${directionLabel}] ${tgtPath}`);
|
|
26007
|
+
log3(`[SYNC-TRACE] oneway DELETE \u2192 tgt.unlink(${tgtPath}) \u2014 NO exists() check before unlink`);
|
|
25892
26008
|
await tgt.unlink(tgtPath);
|
|
25893
26009
|
filesDeleted++;
|
|
25894
26010
|
} catch (err2) {
|
|
25895
|
-
|
|
26011
|
+
log3(`[zen-fs-sync] DELETE SKIP [${directionLabel}] ${tgtPath}:`, err2);
|
|
26012
|
+
errors.push({ path: change.path, message: err2 instanceof Error ? err2.message : String(err2) });
|
|
25896
26013
|
filesSkipped++;
|
|
25897
26014
|
}
|
|
25898
26015
|
break;
|
|
25899
26016
|
}
|
|
25900
26017
|
}
|
|
25901
26018
|
}
|
|
26019
|
+
await this.preserveDirectories(src, tgt);
|
|
25902
26020
|
return {
|
|
25903
26021
|
pairId: this.pairId,
|
|
25904
26022
|
direction: this.options.direction,
|
|
@@ -25908,6 +26026,7 @@ var ZenFSConfig = (() => {
|
|
|
25908
26026
|
filesDeleted,
|
|
25909
26027
|
filesSkipped,
|
|
25910
26028
|
conflicts,
|
|
26029
|
+
errors,
|
|
25911
26030
|
changes,
|
|
25912
26031
|
durationMs: 0
|
|
25913
26032
|
// 由 sync() 方法填充
|
|
@@ -25920,7 +26039,7 @@ var ZenFSConfig = (() => {
|
|
|
25920
26039
|
this.getSnapshot(this.target)
|
|
25921
26040
|
]);
|
|
25922
26041
|
if (srcSnap === null || tgtSnap === null) {
|
|
25923
|
-
|
|
26042
|
+
log3(`[zen-fs-sync] syncBidirectional SKIP (one side unreachable)`);
|
|
25924
26043
|
return {
|
|
25925
26044
|
pairId: this.pairId,
|
|
25926
26045
|
direction: "bi-directional",
|
|
@@ -25957,13 +26076,14 @@ var ZenFSConfig = (() => {
|
|
|
25957
26076
|
this.prevTgtSnap = tgtSnap;
|
|
25958
26077
|
const srcPaths = Array.from(srcSnap.keys()).sort();
|
|
25959
26078
|
const tgtPaths = Array.from(tgtSnap.keys()).sort();
|
|
25960
|
-
|
|
26079
|
+
log3(`[zen-fs-sync] syncBidirectional comparing source=${srcPaths.length} target=${tgtPaths.length}`);
|
|
25961
26080
|
let filesCreated = 0;
|
|
25962
26081
|
let filesUpdated = 0;
|
|
25963
26082
|
let filesDeleted = 0;
|
|
25964
26083
|
let filesSkipped = 0;
|
|
25965
26084
|
const conflicts = [];
|
|
25966
26085
|
const changes = [];
|
|
26086
|
+
const errors = [];
|
|
25967
26087
|
const allPaths = /* @__PURE__ */ new Set([...srcSnap.keys(), ...tgtSnap.keys()]);
|
|
25968
26088
|
for (const path of allPaths) {
|
|
25969
26089
|
const srcEntry = srcSnap.get(path);
|
|
@@ -25972,13 +26092,14 @@ var ZenFSConfig = (() => {
|
|
|
25972
26092
|
if (oldPrevSrcSnap?.has(path)) {
|
|
25973
26093
|
try {
|
|
25974
26094
|
const fullPath = resolvePath(this.root, path);
|
|
25975
|
-
|
|
25976
|
-
|
|
26095
|
+
log3(`[zen-fs-sync] DELETE (src deleted) target ${path}`);
|
|
26096
|
+
log3(`[SYNC-TRACE] bidirectional DELETE (src deleted) \u2192 target.unlink(${fullPath}) \u2014 NO exists() check before unlink`);
|
|
25977
26097
|
await this.target.unlink(fullPath);
|
|
25978
26098
|
filesDeleted++;
|
|
25979
26099
|
changes.push({ path, type: "deleted", targetSnapshot: tgtEntry });
|
|
25980
26100
|
} catch (err2) {
|
|
25981
|
-
|
|
26101
|
+
log3(`[zen-fs-sync] DELETE SKIP (src deleted) target ${path}:`, err2);
|
|
26102
|
+
errors.push({ path, message: err2 instanceof Error ? err2.message : String(err2) });
|
|
25982
26103
|
filesSkipped++;
|
|
25983
26104
|
}
|
|
25984
26105
|
} else {
|
|
@@ -25987,12 +26108,13 @@ var ZenFSConfig = (() => {
|
|
|
25987
26108
|
if (wrote) {
|
|
25988
26109
|
filesCreated++;
|
|
25989
26110
|
changes.push({ path, type: "created", sourceSnapshot: tgtEntry });
|
|
25990
|
-
|
|
26111
|
+
log3(`[zen-fs-sync] COPY target\u2192source ${path}`);
|
|
25991
26112
|
} else {
|
|
25992
26113
|
filesSkipped++;
|
|
25993
26114
|
}
|
|
25994
26115
|
} catch (err2) {
|
|
25995
|
-
|
|
26116
|
+
log3(`[zen-fs-sync] COPY FAIL target\u2192source ${path}:`, err2);
|
|
26117
|
+
errors.push({ path, message: err2 instanceof Error ? err2.message : String(err2) });
|
|
25996
26118
|
filesSkipped++;
|
|
25997
26119
|
}
|
|
25998
26120
|
}
|
|
@@ -26000,13 +26122,14 @@ var ZenFSConfig = (() => {
|
|
|
26000
26122
|
if (oldPrevTgtSnap?.has(path)) {
|
|
26001
26123
|
try {
|
|
26002
26124
|
const fullPath = resolvePath(this.root, path);
|
|
26003
|
-
|
|
26004
|
-
|
|
26125
|
+
log3(`[zen-fs-sync] DELETE (tgt deleted) source ${path}`);
|
|
26126
|
+
log3(`[SYNC-TRACE] bidirectional DELETE (tgt deleted) \u2192 source.unlink(${fullPath}) \u2014 NO exists() check before unlink`);
|
|
26005
26127
|
await this.source.unlink(fullPath);
|
|
26006
26128
|
filesDeleted++;
|
|
26007
26129
|
changes.push({ path, type: "deleted", sourceSnapshot: srcEntry });
|
|
26008
26130
|
} catch (err2) {
|
|
26009
|
-
|
|
26131
|
+
log3(`[zen-fs-sync] DELETE SKIP (tgt deleted) source ${path}:`, err2);
|
|
26132
|
+
errors.push({ path, message: err2 instanceof Error ? err2.message : String(err2) });
|
|
26010
26133
|
filesSkipped++;
|
|
26011
26134
|
}
|
|
26012
26135
|
} else {
|
|
@@ -26015,12 +26138,13 @@ var ZenFSConfig = (() => {
|
|
|
26015
26138
|
if (wrote) {
|
|
26016
26139
|
filesCreated++;
|
|
26017
26140
|
changes.push({ path, type: "created", sourceSnapshot: srcEntry });
|
|
26018
|
-
|
|
26141
|
+
log3(`[zen-fs-sync] COPY source\u2192target ${path}`);
|
|
26019
26142
|
} else {
|
|
26020
26143
|
filesSkipped++;
|
|
26021
26144
|
}
|
|
26022
26145
|
} catch (err2) {
|
|
26023
|
-
|
|
26146
|
+
log3(`[zen-fs-sync] COPY FAIL source\u2192target ${path}:`, err2);
|
|
26147
|
+
errors.push({ path, message: err2 instanceof Error ? err2.message : String(err2) });
|
|
26024
26148
|
filesSkipped++;
|
|
26025
26149
|
}
|
|
26026
26150
|
}
|
|
@@ -26036,15 +26160,17 @@ var ZenFSConfig = (() => {
|
|
|
26036
26160
|
srcContent = await this.source.readFile(fullPath, "utf-8");
|
|
26037
26161
|
tgtContent = await this.target.readFile(fullPath, "utf-8");
|
|
26038
26162
|
} catch (err2) {
|
|
26039
|
-
|
|
26163
|
+
log3(`[zen-fs-sync] CONTENT READ FAIL ${path}:`, err2);
|
|
26164
|
+
errors.push({ path, message: err2 instanceof Error ? err2.message : String(err2) });
|
|
26040
26165
|
filesSkipped++;
|
|
26041
26166
|
continue;
|
|
26042
26167
|
}
|
|
26043
26168
|
if (srcContent === tgtContent) {
|
|
26044
26169
|
const oldestMtime = Math.min(srcEntry.mtimeMs, tgtEntry.mtimeMs);
|
|
26170
|
+
const keptOn = srcEntry.mtimeMs <= tgtEntry.mtimeMs ? "src" : "tgt";
|
|
26045
26171
|
await this.normalizeMtimeBoth(path, srcContent, oldestMtime);
|
|
26046
26172
|
filesSkipped++;
|
|
26047
|
-
|
|
26173
|
+
log3(`[zen-fs-sync] MTIME NORMALIZE ${path} \u2192 mtime=${oldestMtime} (content identical; src=${srcEntry.mtimeMs} tgt=${tgtEntry.mtimeMs}; kept=oldest(${keptOn}); real mtime stored in sidecar .${path}.mtime)`);
|
|
26048
26174
|
} else {
|
|
26049
26175
|
const newerIsSource = srcEntry.mtimeMs > tgtEntry.mtimeMs;
|
|
26050
26176
|
const fromFs = newerIsSource ? this.source : this.target;
|
|
@@ -26055,12 +26181,13 @@ var ZenFSConfig = (() => {
|
|
|
26055
26181
|
if (wrote) {
|
|
26056
26182
|
filesUpdated++;
|
|
26057
26183
|
changes.push({ path, type: "modified", sourceSnapshot: newerIsSource ? srcEntry : tgtEntry, targetSnapshot: newerIsSource ? tgtEntry : srcEntry });
|
|
26058
|
-
|
|
26184
|
+
log3(`[zen-fs-sync] UPDATE ${fromLabel} ${path} (mtime=${newerIsSource ? srcEntry.mtimeMs : tgtEntry.mtimeMs} > ${newerIsSource ? tgtEntry.mtimeMs : srcEntry.mtimeMs})`);
|
|
26059
26185
|
} else {
|
|
26060
26186
|
filesSkipped++;
|
|
26061
26187
|
}
|
|
26062
26188
|
} catch (err2) {
|
|
26063
|
-
|
|
26189
|
+
log3(`[zen-fs-sync] UPDATE FAIL ${fromLabel} ${path}:`, err2);
|
|
26190
|
+
errors.push({ path, message: err2 instanceof Error ? err2.message : String(err2) });
|
|
26064
26191
|
filesSkipped++;
|
|
26065
26192
|
}
|
|
26066
26193
|
}
|
|
@@ -26086,12 +26213,14 @@ var ZenFSConfig = (() => {
|
|
|
26086
26213
|
await this.writeFileBoth(path, resolved.content);
|
|
26087
26214
|
filesUpdated++;
|
|
26088
26215
|
changes.push({ path, type: "modified", sourceSnapshot: { ...srcEntry, mtimeMs: Date.now() }, targetSnapshot: srcEntry });
|
|
26089
|
-
|
|
26216
|
+
log3(`[zen-fs-sync] CONFLICT ${path} resolved=${resolved.strategy}`);
|
|
26090
26217
|
}
|
|
26091
26218
|
}
|
|
26092
26219
|
}
|
|
26220
|
+
await this.preserveDirectories(this.source, this.target);
|
|
26221
|
+
await this.preserveDirectories(this.target, this.source);
|
|
26093
26222
|
const durationMs = Date.now() - startTime;
|
|
26094
|
-
|
|
26223
|
+
log3(`[zen-fs-sync] syncBidirectional END pairId=${this.pairId} +${filesCreated}/~${filesUpdated}/-${filesDeleted} ${durationMs}ms`);
|
|
26095
26224
|
return {
|
|
26096
26225
|
pairId: this.pairId,
|
|
26097
26226
|
direction: "bi-directional",
|
|
@@ -26101,6 +26230,7 @@ var ZenFSConfig = (() => {
|
|
|
26101
26230
|
filesDeleted,
|
|
26102
26231
|
filesSkipped,
|
|
26103
26232
|
conflicts,
|
|
26233
|
+
errors,
|
|
26104
26234
|
changes,
|
|
26105
26235
|
durationMs
|
|
26106
26236
|
};
|
|
@@ -26118,6 +26248,28 @@ var ZenFSConfig = (() => {
|
|
|
26118
26248
|
}
|
|
26119
26249
|
return buildSnapshot(fs, this.root, this.options.filter);
|
|
26120
26250
|
}
|
|
26251
|
+
/**
|
|
26252
|
+
* Preserve empty directories across backends (zen-fs-sync/docs/SyncableFS.md §2).
|
|
26253
|
+
*
|
|
26254
|
+
* A backend that cannot store empty directories (Git, RemoteStorage) keeps a
|
|
26255
|
+
* directory alive with an internal `.keep` placeholder that is hidden from
|
|
26256
|
+
* callers. Because the sync snapshot is file-based, empty directories are not
|
|
26257
|
+
* represented, so the sync engine must explicitly `mkdir` each source
|
|
26258
|
+
* directory on the target. The target's own `mkdir` then produces its own
|
|
26259
|
+
* (hidden) placeholder. Best-effort: failures are logged, never thrown.
|
|
26260
|
+
*/
|
|
26261
|
+
async preserveDirectories(source, target) {
|
|
26262
|
+
try {
|
|
26263
|
+
const dirs = await collectDirectories(source, this.root, this.options.filter);
|
|
26264
|
+
for (const relDir of dirs) {
|
|
26265
|
+
if (relDir === "/") continue;
|
|
26266
|
+
const fullDir = resolvePath(this.root, relDir);
|
|
26267
|
+
await ensureDir(target, fullDir);
|
|
26268
|
+
}
|
|
26269
|
+
} catch (err2) {
|
|
26270
|
+
log3(`[zen-fs-sync] preserveDirectories FAIL: ${err2}`);
|
|
26271
|
+
}
|
|
26272
|
+
}
|
|
26121
26273
|
/**
|
|
26122
26274
|
* Compare two snapshots for equality (same paths, same size, same mtime).
|
|
26123
26275
|
* Returns true if they are identical, false otherwise.
|
|
@@ -26682,70 +26834,10 @@ var ZenFSConfig = (() => {
|
|
|
26682
26834
|
init_backend_registry();
|
|
26683
26835
|
init_dist4();
|
|
26684
26836
|
|
|
26685
|
-
// node_modules/@richard432/localstorage-logger/dist/index.mjs
|
|
26686
|
-
var PREFIX = "debug:";
|
|
26687
|
-
function hasLocalStorage() {
|
|
26688
|
-
try {
|
|
26689
|
-
return typeof localStorage !== "undefined" && localStorage !== null;
|
|
26690
|
-
} catch {
|
|
26691
|
-
return false;
|
|
26692
|
-
}
|
|
26693
|
-
}
|
|
26694
|
-
function getProcessEnv() {
|
|
26695
|
-
try {
|
|
26696
|
-
if (typeof process !== "undefined" && process.env !== void 0) {
|
|
26697
|
-
return process.env;
|
|
26698
|
-
}
|
|
26699
|
-
} catch {
|
|
26700
|
-
}
|
|
26701
|
-
return null;
|
|
26702
|
-
}
|
|
26703
|
-
function moduleToEnvKey(module) {
|
|
26704
|
-
return "DEBUG_" + module.replace(/[:-]/g, "_").toUpperCase();
|
|
26705
|
-
}
|
|
26706
|
-
function isEnabled2(module) {
|
|
26707
|
-
const key = `${PREFIX}${module}`;
|
|
26708
|
-
if (hasLocalStorage()) {
|
|
26709
|
-
const val = localStorage.getItem(key);
|
|
26710
|
-
if (val === null) {
|
|
26711
|
-
localStorage.setItem(key, "1");
|
|
26712
|
-
return true;
|
|
26713
|
-
}
|
|
26714
|
-
return val === "1";
|
|
26715
|
-
}
|
|
26716
|
-
const env = getProcessEnv();
|
|
26717
|
-
if (env) {
|
|
26718
|
-
const envKey = moduleToEnvKey(module);
|
|
26719
|
-
const val = env[envKey];
|
|
26720
|
-
if (val === void 0) return true;
|
|
26721
|
-
return val === "1";
|
|
26722
|
-
}
|
|
26723
|
-
return true;
|
|
26724
|
-
}
|
|
26725
|
-
function createLogger2(module) {
|
|
26726
|
-
return {
|
|
26727
|
-
log(...args) {
|
|
26728
|
-
if (isEnabled2(module)) {
|
|
26729
|
-
console.log(`[${module}]`, ...args);
|
|
26730
|
-
}
|
|
26731
|
-
},
|
|
26732
|
-
warn(...args) {
|
|
26733
|
-
if (isEnabled2(module)) {
|
|
26734
|
-
console.warn(`[${module}]`, ...args);
|
|
26735
|
-
}
|
|
26736
|
-
},
|
|
26737
|
-
error(...args) {
|
|
26738
|
-
if (isEnabled2(module)) {
|
|
26739
|
-
console.error(`[${module}]`, ...args);
|
|
26740
|
-
}
|
|
26741
|
-
}
|
|
26742
|
-
};
|
|
26743
|
-
}
|
|
26744
|
-
|
|
26745
26837
|
// src/logger.ts
|
|
26746
|
-
var
|
|
26838
|
+
var MODULE_PREFIX2 = "zen-fs-config";
|
|
26747
26839
|
function createLogger3(prefix) {
|
|
26748
|
-
const logger =
|
|
26840
|
+
const logger = createLogger(`${MODULE_PREFIX2}:${prefix}`);
|
|
26749
26841
|
return (...args) => logger.log(...args);
|
|
26750
26842
|
}
|
|
26751
26843
|
|
|
@@ -27351,7 +27443,7 @@ var ZenFSConfig = (() => {
|
|
|
27351
27443
|
}
|
|
27352
27444
|
|
|
27353
27445
|
// src/config-repo.ts
|
|
27354
|
-
var log6 =
|
|
27446
|
+
var log6 = createLogger("zen-fs-config:config-repo");
|
|
27355
27447
|
var META_DIR = "/.meta";
|
|
27356
27448
|
var GROUP_TYPE_FILE = `${META_DIR}/group-type`;
|
|
27357
27449
|
var BACKENDS_FILE = `${META_DIR}/backends.json`;
|
|
@@ -28834,7 +28926,11 @@ var ZenFSConfig = (() => {
|
|
|
28834
28926
|
}
|
|
28835
28927
|
async init() {
|
|
28836
28928
|
try {
|
|
28837
|
-
const localPrimary = await resolveLocalPrimary(
|
|
28929
|
+
const localPrimary = await resolveLocalPrimary(
|
|
28930
|
+
this.appId,
|
|
28931
|
+
"data",
|
|
28932
|
+
this.groupId === "default" ? {} : { idbStoreName: `zen-fs-config-data-${this.appId}-${this.groupId}` }
|
|
28933
|
+
);
|
|
28838
28934
|
this.localFS = await createBackend(localPrimary);
|
|
28839
28935
|
} catch {
|
|
28840
28936
|
throw new Error(`Failed to create local primary for data group "${this.groupId}"`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zen-fs-config",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.44",
|
|
4
4
|
"description": "Distributed config management library built on ZenFS, zen-fs-cache, and zen-fs-sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -85,6 +85,6 @@
|
|
|
85
85
|
"typescript": "^5.9.3",
|
|
86
86
|
"vitest": "^1.6.1",
|
|
87
87
|
"zen-fs-cache": "^1.0.6",
|
|
88
|
-
"zen-fs-sync": "^0.4.
|
|
88
|
+
"zen-fs-sync": "^0.4.17"
|
|
89
89
|
}
|
|
90
90
|
}
|