zen-fs-config 0.3.16 → 0.3.18
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.d.mts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.js +7 -220
- package/dist/index.mjs +5 -220
- package/package.json +5 -8
package/dist/index.d.mts
CHANGED
|
@@ -279,10 +279,17 @@ declare function createConfigRepo(appId: string, options: ConfigRepoOptions): Pr
|
|
|
279
279
|
* zen-fs-config — Backend Registry
|
|
280
280
|
*
|
|
281
281
|
* A pluggable registry that maps backend type names to factory functions.
|
|
282
|
-
* Built-in support for ZenFS backends (InMemory, IndexedDB, etc.)
|
|
283
|
-
* loaded from @zenfs/core.
|
|
284
282
|
*
|
|
285
|
-
*
|
|
283
|
+
* Core principle: zen-fs-config does NOT hardcode every ZenFS backend.
|
|
284
|
+
* Instead, it provides:
|
|
285
|
+
* 1. A simple registry API (registerBackend, createBackend, etc.)
|
|
286
|
+
* 2. One built-in backend (InMemory) — zero extra dependencies
|
|
287
|
+
* 3. A wrapZenFSFileSystem() helper to adapt any ZenFS FileSystem
|
|
288
|
+
* implementation into the BackendInstance interface
|
|
289
|
+
*
|
|
290
|
+
* Applications (like zen-fs-config-admin) register whatever backends
|
|
291
|
+
* they need at startup. Adding a new backend never requires changing
|
|
292
|
+
* zen-fs-config itself.
|
|
286
293
|
*/
|
|
287
294
|
|
|
288
295
|
type BackendFactory = (options: Record<string, unknown>) => Promise<BackendInstance>;
|
|
@@ -304,9 +311,11 @@ interface BackendInstance {
|
|
|
304
311
|
getRevision?(path: string): Promise<string | number | undefined>;
|
|
305
312
|
}
|
|
306
313
|
declare function registerBackend(type: string, factory: BackendFactory): void;
|
|
314
|
+
declare function unregisterBackend(type: string): boolean;
|
|
307
315
|
declare function createBackend(descriptor: Pick<BackendDescriptor, 'type' | 'options'>): Promise<BackendInstance>;
|
|
308
316
|
declare function hasBackend(type: string): boolean;
|
|
309
317
|
declare function listBackends(): string[];
|
|
318
|
+
declare function wrapZenFSFileSystem(config: any): Promise<BackendInstance>;
|
|
310
319
|
|
|
311
320
|
/**
|
|
312
321
|
* zen-fs-config — Sidecar Version File Management
|
|
@@ -350,4 +359,4 @@ declare function incrementVersion(fs: SyncableFS, configFilePath: string, newCon
|
|
|
350
359
|
*/
|
|
351
360
|
declare function verifyOrRepairVersion(fs: SyncableFS, configFilePath: string, author: string): Promise<VersionMeta | null>;
|
|
352
361
|
|
|
353
|
-
export { type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendsMeta, type BootstrapData, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type IConfigRepo, type SyncRule, type SyncRulesMeta, type VersionMeta, configKeyToFilePath, createBackend, createConfigRepo, createSerializerChain, getExtension, hasBackend, incrementVersion, listBackends, readVersion, registerBackend, sha256, verifyOrRepairVersion, versionPathFor, writeVersion };
|
|
362
|
+
export { type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendsMeta, type BootstrapData, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type IConfigRepo, type SyncRule, type SyncRulesMeta, type VersionMeta, configKeyToFilePath, createBackend, createConfigRepo, createSerializerChain, getExtension, hasBackend, incrementVersion, listBackends, readVersion, registerBackend, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
|
package/dist/index.d.ts
CHANGED
|
@@ -279,10 +279,17 @@ declare function createConfigRepo(appId: string, options: ConfigRepoOptions): Pr
|
|
|
279
279
|
* zen-fs-config — Backend Registry
|
|
280
280
|
*
|
|
281
281
|
* A pluggable registry that maps backend type names to factory functions.
|
|
282
|
-
* Built-in support for ZenFS backends (InMemory, IndexedDB, etc.)
|
|
283
|
-
* loaded from @zenfs/core.
|
|
284
282
|
*
|
|
285
|
-
*
|
|
283
|
+
* Core principle: zen-fs-config does NOT hardcode every ZenFS backend.
|
|
284
|
+
* Instead, it provides:
|
|
285
|
+
* 1. A simple registry API (registerBackend, createBackend, etc.)
|
|
286
|
+
* 2. One built-in backend (InMemory) — zero extra dependencies
|
|
287
|
+
* 3. A wrapZenFSFileSystem() helper to adapt any ZenFS FileSystem
|
|
288
|
+
* implementation into the BackendInstance interface
|
|
289
|
+
*
|
|
290
|
+
* Applications (like zen-fs-config-admin) register whatever backends
|
|
291
|
+
* they need at startup. Adding a new backend never requires changing
|
|
292
|
+
* zen-fs-config itself.
|
|
286
293
|
*/
|
|
287
294
|
|
|
288
295
|
type BackendFactory = (options: Record<string, unknown>) => Promise<BackendInstance>;
|
|
@@ -304,9 +311,11 @@ interface BackendInstance {
|
|
|
304
311
|
getRevision?(path: string): Promise<string | number | undefined>;
|
|
305
312
|
}
|
|
306
313
|
declare function registerBackend(type: string, factory: BackendFactory): void;
|
|
314
|
+
declare function unregisterBackend(type: string): boolean;
|
|
307
315
|
declare function createBackend(descriptor: Pick<BackendDescriptor, 'type' | 'options'>): Promise<BackendInstance>;
|
|
308
316
|
declare function hasBackend(type: string): boolean;
|
|
309
317
|
declare function listBackends(): string[];
|
|
318
|
+
declare function wrapZenFSFileSystem(config: any): Promise<BackendInstance>;
|
|
310
319
|
|
|
311
320
|
/**
|
|
312
321
|
* zen-fs-config — Sidecar Version File Management
|
|
@@ -350,4 +359,4 @@ declare function incrementVersion(fs: SyncableFS, configFilePath: string, newCon
|
|
|
350
359
|
*/
|
|
351
360
|
declare function verifyOrRepairVersion(fs: SyncableFS, configFilePath: string, author: string): Promise<VersionMeta | null>;
|
|
352
361
|
|
|
353
|
-
export { type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendsMeta, type BootstrapData, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type IConfigRepo, type SyncRule, type SyncRulesMeta, type VersionMeta, configKeyToFilePath, createBackend, createConfigRepo, createSerializerChain, getExtension, hasBackend, incrementVersion, listBackends, readVersion, registerBackend, sha256, verifyOrRepairVersion, versionPathFor, writeVersion };
|
|
362
|
+
export { type BackendDescriptor, type BackendFactory, type BackendInstance, type BackendsMeta, type BootstrapData, type CacheOptions, ConfigRepo, type ConfigRepoOptions, type ConfigSerializer, type ConflictArchive, type ConflictInfo, type IConfigRepo, type SyncRule, type SyncRulesMeta, type VersionMeta, configKeyToFilePath, createBackend, createConfigRepo, createSerializerChain, getExtension, hasBackend, incrementVersion, listBackends, readVersion, registerBackend, sha256, unregisterBackend, verifyOrRepairVersion, versionPathFor, wrapZenFSFileSystem, writeVersion };
|
package/dist/index.js
CHANGED
|
@@ -42,8 +42,10 @@ __export(index_exports, {
|
|
|
42
42
|
readVersion: () => readVersion,
|
|
43
43
|
registerBackend: () => registerBackend,
|
|
44
44
|
sha256: () => sha256,
|
|
45
|
+
unregisterBackend: () => unregisterBackend,
|
|
45
46
|
verifyOrRepairVersion: () => verifyOrRepairVersion,
|
|
46
47
|
versionPathFor: () => versionPathFor,
|
|
48
|
+
wrapZenFSFileSystem: () => wrapZenFSFileSystem,
|
|
47
49
|
writeVersion: () => writeVersion
|
|
48
50
|
});
|
|
49
51
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -311,6 +313,9 @@ var registry = /* @__PURE__ */ new Map();
|
|
|
311
313
|
function registerBackend(type, factory) {
|
|
312
314
|
registry.set(type, factory);
|
|
313
315
|
}
|
|
316
|
+
function unregisterBackend(type) {
|
|
317
|
+
return registry.delete(type);
|
|
318
|
+
}
|
|
314
319
|
async function createBackend(descriptor) {
|
|
315
320
|
const factory = registry.get(descriptor.type);
|
|
316
321
|
if (!factory) {
|
|
@@ -392,226 +397,6 @@ registerBackend("InMemory", async (options) => {
|
|
|
392
397
|
const label = options.label ?? `zen-fs-config-${++inMemoryCounter}`;
|
|
393
398
|
return wrapZenFSFileSystem({ backend: InMemory, maxSize, label });
|
|
394
399
|
});
|
|
395
|
-
var idbCounter = 0;
|
|
396
|
-
registerBackend("IndexedDB", async (options) => {
|
|
397
|
-
const { IndexedDB } = await import("@zenfs/dom");
|
|
398
|
-
const storeName = options.storeName ?? `zen-fs-config-${++idbCounter}`;
|
|
399
|
-
return wrapZenFSFileSystem({ backend: IndexedDB, storeName });
|
|
400
|
-
});
|
|
401
|
-
registerBackend("WebStorage", async (options) => {
|
|
402
|
-
const { WebStorage } = await import("@zenfs/dom");
|
|
403
|
-
const storageType = options.storageType ?? "localStorage";
|
|
404
|
-
let storage;
|
|
405
|
-
if (storageType === "sessionStorage" && typeof sessionStorage !== "undefined") {
|
|
406
|
-
storage = sessionStorage;
|
|
407
|
-
} else {
|
|
408
|
-
storage = localStorage;
|
|
409
|
-
}
|
|
410
|
-
return wrapZenFSFileSystem({ backend: WebStorage, storage });
|
|
411
|
-
});
|
|
412
|
-
registerBackend("GitHub", async (options) => {
|
|
413
|
-
const { Github } = await import("zen-fs-github");
|
|
414
|
-
return wrapZenFSFileSystem({
|
|
415
|
-
backend: Github,
|
|
416
|
-
token: options.token,
|
|
417
|
-
owner: options.owner,
|
|
418
|
-
repo: options.repo,
|
|
419
|
-
branch: options.branch,
|
|
420
|
-
baseUrl: options.baseUrl && options.baseUrl.trim() || void 0
|
|
421
|
-
});
|
|
422
|
-
});
|
|
423
|
-
registerBackend("Gitee", async (options) => {
|
|
424
|
-
const zenGitee = await import("zen-fs-gitee");
|
|
425
|
-
const { GiteeFS } = zenGitee;
|
|
426
|
-
const origInit = GiteeFS.prototype.init;
|
|
427
|
-
const patched = /* @__PURE__ */ new WeakSet();
|
|
428
|
-
GiteeFS.prototype.init = async function() {
|
|
429
|
-
let firstErr;
|
|
430
|
-
try {
|
|
431
|
-
return await origInit.call(this);
|
|
432
|
-
} catch (err) {
|
|
433
|
-
if (!err.message?.includes("404") && !err.message?.includes("Tree not found")) {
|
|
434
|
-
throw err;
|
|
435
|
-
}
|
|
436
|
-
firstErr = err;
|
|
437
|
-
}
|
|
438
|
-
if (patched.has(this)) throw firstErr;
|
|
439
|
-
patched.add(this);
|
|
440
|
-
console.log(`[Gitee] init failed with branch="${this.api.branch}", resolving to SHA...`);
|
|
441
|
-
const baseUrl = this.api.baseUrl || "https://gitee.com/api/v5";
|
|
442
|
-
const auth = `access_token=${this.api.token}`;
|
|
443
|
-
const branchUrl = `${baseUrl}/repos/${this.api.owner}/${this.api.repo}/branches/${this.api.branch}?${auth}`;
|
|
444
|
-
let branchRes = await fetch(branchUrl);
|
|
445
|
-
let branchData;
|
|
446
|
-
if (branchRes.ok) {
|
|
447
|
-
branchData = await branchRes.json();
|
|
448
|
-
} else {
|
|
449
|
-
console.log(`[Gitee] Branch "${this.api.branch}" not found, creating...`);
|
|
450
|
-
const defaultBranch = this.api.branch === "master" ? "main" : "master";
|
|
451
|
-
let defaultSha;
|
|
452
|
-
for (const name of [defaultBranch, "main", "master"]) {
|
|
453
|
-
const dr = await fetch(`${baseUrl}/repos/${this.api.owner}/${this.api.repo}/branches/${name}?${auth}`);
|
|
454
|
-
if (dr.ok) {
|
|
455
|
-
const dd = await dr.json();
|
|
456
|
-
defaultSha = dd.commit?.sha;
|
|
457
|
-
if (defaultSha) break;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
if (!defaultSha) {
|
|
461
|
-
console.log(`[Gitee] No branches found in repo, skipping tree init`);
|
|
462
|
-
this.initialized = true;
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
const createRes = await fetch(`${baseUrl}/repos/${this.api.owner}/${this.api.repo}/branches?${auth}`, {
|
|
466
|
-
method: "POST",
|
|
467
|
-
headers: { "Content-Type": "application/json" },
|
|
468
|
-
body: JSON.stringify({
|
|
469
|
-
refs: defaultSha,
|
|
470
|
-
branch_name: this.api.branch
|
|
471
|
-
})
|
|
472
|
-
});
|
|
473
|
-
if (!createRes.ok) {
|
|
474
|
-
const errText = await createRes.text().catch(() => "");
|
|
475
|
-
throw new Error(`Gitee: failed to create branch "${this.api.branch}": ${createRes.status} ${errText}`);
|
|
476
|
-
}
|
|
477
|
-
branchData = await createRes.json();
|
|
478
|
-
console.log(`[Gitee] Branch "${this.api.branch}" created from ${defaultSha.slice(0, 8)}`);
|
|
479
|
-
}
|
|
480
|
-
const commitSha = branchData.commit?.sha;
|
|
481
|
-
if (!commitSha) throw new Error(`Gitee: could not get commit SHA for branch "${this.api.branch}"`);
|
|
482
|
-
const commitUrl = `${baseUrl}/repos/${this.api.owner}/${this.api.repo}/git/commits/${commitSha}?${auth}`;
|
|
483
|
-
const commitRes = await fetch(commitUrl);
|
|
484
|
-
if (!commitRes.ok) throw new Error(`Gitee: commit ${commitSha} not found (${commitRes.status})`);
|
|
485
|
-
const commitData = await commitRes.json();
|
|
486
|
-
const treeSha = commitData.tree?.sha;
|
|
487
|
-
if (!treeSha) throw new Error(`Gitee: could not get tree SHA from commit ${commitSha}`);
|
|
488
|
-
const realBranch = this.api.branch;
|
|
489
|
-
this.api.branch = treeSha;
|
|
490
|
-
console.log(`[Gitee] Resolved branch="${realBranch}" \u2192 commit=${commitSha.slice(0, 8)} \u2192 tree=${treeSha.slice(0, 8)}`);
|
|
491
|
-
try {
|
|
492
|
-
return await origInit.call(this);
|
|
493
|
-
} finally {
|
|
494
|
-
this.api.branch = realBranch;
|
|
495
|
-
}
|
|
496
|
-
};
|
|
497
|
-
return wrapZenFSFileSystem({
|
|
498
|
-
backend: zenGitee.Gitee,
|
|
499
|
-
token: options.token,
|
|
500
|
-
owner: options.owner,
|
|
501
|
-
repo: options.repo,
|
|
502
|
-
branch: options.branch,
|
|
503
|
-
baseUrl: options.baseUrl && options.baseUrl.trim() || void 0
|
|
504
|
-
});
|
|
505
|
-
});
|
|
506
|
-
registerBackend("WebDAV", async (options) => {
|
|
507
|
-
const url = options.url ?? "";
|
|
508
|
-
const username = options.username ?? "";
|
|
509
|
-
const password = options.password ?? "";
|
|
510
|
-
const rootPath = options.rootPath ?? "/";
|
|
511
|
-
if (!url) throw new Error('WebDAV backend requires "url" option');
|
|
512
|
-
const authHeader = username ? `Basic ${btoa(`${username}:${password}`)}` : "";
|
|
513
|
-
const davUrl = (path) => {
|
|
514
|
-
const cleanRoot = rootPath.replace(/\/$/, "");
|
|
515
|
-
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
516
|
-
return `${url.replace(/\/$/, "")}${cleanRoot}${cleanPath}`;
|
|
517
|
-
};
|
|
518
|
-
const davFetch = async (path, method, body) => {
|
|
519
|
-
const headers = {};
|
|
520
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
521
|
-
if (body) headers["Content-Type"] = "application/xml";
|
|
522
|
-
const res = await fetch(davUrl(path), { method, headers, body });
|
|
523
|
-
if (!res.ok && res.status !== 404) throw new Error(`WebDAV ${res.status} ${method} ${davUrl(path)}`);
|
|
524
|
-
return res;
|
|
525
|
-
};
|
|
526
|
-
const parseMultiStatus = async (res) => {
|
|
527
|
-
const text = await res.text();
|
|
528
|
-
const results = [];
|
|
529
|
-
const responses = text.match(/<D:response[^>]*>[\s\S]*?<\/D:response>/gi) || [];
|
|
530
|
-
for (const resp of responses) {
|
|
531
|
-
const href = (resp.match(/<D:href>([^<]+)<\/D:href>/i) || [])[1] || "";
|
|
532
|
-
const isDir = /<D:collection\s*\/>/i.test(resp) || /<D:resourcetype>.*<D:collection/.test(resp);
|
|
533
|
-
const sizeMatch = resp.match(/<D:getcontentlength>([^<]+)<\/D:getcontentlength>/i);
|
|
534
|
-
const size = sizeMatch ? parseInt(sizeMatch[1]) : 0;
|
|
535
|
-
const decoded = decodeURIComponent(href);
|
|
536
|
-
results.push({ path: decoded, isDir, size });
|
|
537
|
-
}
|
|
538
|
-
return results;
|
|
539
|
-
};
|
|
540
|
-
const exists = async (path) => {
|
|
541
|
-
const res = await davFetch(path, "PROPFIND");
|
|
542
|
-
return res.ok;
|
|
543
|
-
};
|
|
544
|
-
const backend = {
|
|
545
|
-
async readFile(path, ...args) {
|
|
546
|
-
const res = await davFetch(path, "GET");
|
|
547
|
-
if (!res.ok) throw new Error(`ENOENT: ${path}`);
|
|
548
|
-
if (args[0] === "utf-8") return res.text();
|
|
549
|
-
const buf = await res.arrayBuffer();
|
|
550
|
-
return new Uint8Array(buf);
|
|
551
|
-
},
|
|
552
|
-
async writeFile(path, data, _options) {
|
|
553
|
-
const headers = { "Content-Type": "application/octet-stream" };
|
|
554
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
555
|
-
await fetch(davUrl(path), {
|
|
556
|
-
method: "PUT",
|
|
557
|
-
headers,
|
|
558
|
-
body: data instanceof ArrayBuffer ? data : data instanceof Uint8Array ? new Uint8Array(data).buffer : new TextEncoder().encode(data)
|
|
559
|
-
});
|
|
560
|
-
},
|
|
561
|
-
async readdir(path) {
|
|
562
|
-
const headers = { Depth: "1" };
|
|
563
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
564
|
-
const res = await fetch(davUrl(path), { method: "PROPFIND", headers });
|
|
565
|
-
if (!res.ok) throw new Error(`WebDAV PROPFIND failed: ${res.status}`);
|
|
566
|
-
const items = await parseMultiStatus(res);
|
|
567
|
-
const prefix = davUrl(path);
|
|
568
|
-
return items.filter((i) => i.path !== prefix && i.path !== `${prefix}/`).map((i) => i.path.split("/").filter(Boolean).pop() || "");
|
|
569
|
-
},
|
|
570
|
-
async stat(path) {
|
|
571
|
-
const headers = { Depth: "0" };
|
|
572
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
573
|
-
const res = await fetch(davUrl(path), { method: "PROPFIND", headers });
|
|
574
|
-
if (!res.ok) throw new Error(`ENOENT: ${path}`);
|
|
575
|
-
const items = await parseMultiStatus(res);
|
|
576
|
-
const item = items[0];
|
|
577
|
-
return { isFile: () => !item.isDir, isDirectory: () => item.isDir, size: item.size };
|
|
578
|
-
},
|
|
579
|
-
async exists(path) {
|
|
580
|
-
return exists(path);
|
|
581
|
-
},
|
|
582
|
-
async mkdir(path) {
|
|
583
|
-
const headers = {};
|
|
584
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
585
|
-
const res = await fetch(davUrl(path), { method: "MKCOL", headers });
|
|
586
|
-
if (!res.ok && res.status !== 405) throw new Error(`WebDAV MKCOL failed: ${res.status}`);
|
|
587
|
-
},
|
|
588
|
-
async unlink(path) {
|
|
589
|
-
await davFetch(path, "DELETE");
|
|
590
|
-
},
|
|
591
|
-
async rmdir(path) {
|
|
592
|
-
const items = await (async () => {
|
|
593
|
-
const headers = { Depth: "1" };
|
|
594
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
595
|
-
const res = await fetch(davUrl(path), { method: "PROPFIND", headers });
|
|
596
|
-
if (!res.ok) return [];
|
|
597
|
-
const parsed = await parseMultiStatus(res);
|
|
598
|
-
const prefix = davUrl(path);
|
|
599
|
-
return parsed.filter((i) => i.path !== prefix && i.path !== `${prefix}/`);
|
|
600
|
-
})();
|
|
601
|
-
for (const item of items) {
|
|
602
|
-
if (item.isDir) await backend.rmdir(item.path);
|
|
603
|
-
else await backend.unlink(item.path);
|
|
604
|
-
}
|
|
605
|
-
await davFetch(path, "DELETE");
|
|
606
|
-
},
|
|
607
|
-
async rename(oldPath, newPath) {
|
|
608
|
-
const headers = { Destination: davUrl(newPath) };
|
|
609
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
610
|
-
await fetch(davUrl(oldPath), { method: "MOVE", headers });
|
|
611
|
-
}
|
|
612
|
-
};
|
|
613
|
-
return backend;
|
|
614
|
-
});
|
|
615
400
|
|
|
616
401
|
// src/version.ts
|
|
617
402
|
function versionPathFor(configFilePath) {
|
|
@@ -1323,7 +1108,9 @@ async function createConfigRepo(appId, options) {
|
|
|
1323
1108
|
readVersion,
|
|
1324
1109
|
registerBackend,
|
|
1325
1110
|
sha256,
|
|
1111
|
+
unregisterBackend,
|
|
1326
1112
|
verifyOrRepairVersion,
|
|
1327
1113
|
versionPathFor,
|
|
1114
|
+
wrapZenFSFileSystem,
|
|
1328
1115
|
writeVersion
|
|
1329
1116
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -264,6 +264,9 @@ var registry = /* @__PURE__ */ new Map();
|
|
|
264
264
|
function registerBackend(type, factory) {
|
|
265
265
|
registry.set(type, factory);
|
|
266
266
|
}
|
|
267
|
+
function unregisterBackend(type) {
|
|
268
|
+
return registry.delete(type);
|
|
269
|
+
}
|
|
267
270
|
async function createBackend(descriptor) {
|
|
268
271
|
const factory = registry.get(descriptor.type);
|
|
269
272
|
if (!factory) {
|
|
@@ -345,226 +348,6 @@ registerBackend("InMemory", async (options) => {
|
|
|
345
348
|
const label = options.label ?? `zen-fs-config-${++inMemoryCounter}`;
|
|
346
349
|
return wrapZenFSFileSystem({ backend: InMemory, maxSize, label });
|
|
347
350
|
});
|
|
348
|
-
var idbCounter = 0;
|
|
349
|
-
registerBackend("IndexedDB", async (options) => {
|
|
350
|
-
const { IndexedDB } = await import("@zenfs/dom");
|
|
351
|
-
const storeName = options.storeName ?? `zen-fs-config-${++idbCounter}`;
|
|
352
|
-
return wrapZenFSFileSystem({ backend: IndexedDB, storeName });
|
|
353
|
-
});
|
|
354
|
-
registerBackend("WebStorage", async (options) => {
|
|
355
|
-
const { WebStorage } = await import("@zenfs/dom");
|
|
356
|
-
const storageType = options.storageType ?? "localStorage";
|
|
357
|
-
let storage;
|
|
358
|
-
if (storageType === "sessionStorage" && typeof sessionStorage !== "undefined") {
|
|
359
|
-
storage = sessionStorage;
|
|
360
|
-
} else {
|
|
361
|
-
storage = localStorage;
|
|
362
|
-
}
|
|
363
|
-
return wrapZenFSFileSystem({ backend: WebStorage, storage });
|
|
364
|
-
});
|
|
365
|
-
registerBackend("GitHub", async (options) => {
|
|
366
|
-
const { Github } = await import("zen-fs-github");
|
|
367
|
-
return wrapZenFSFileSystem({
|
|
368
|
-
backend: Github,
|
|
369
|
-
token: options.token,
|
|
370
|
-
owner: options.owner,
|
|
371
|
-
repo: options.repo,
|
|
372
|
-
branch: options.branch,
|
|
373
|
-
baseUrl: options.baseUrl && options.baseUrl.trim() || void 0
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
registerBackend("Gitee", async (options) => {
|
|
377
|
-
const zenGitee = await import("zen-fs-gitee");
|
|
378
|
-
const { GiteeFS } = zenGitee;
|
|
379
|
-
const origInit = GiteeFS.prototype.init;
|
|
380
|
-
const patched = /* @__PURE__ */ new WeakSet();
|
|
381
|
-
GiteeFS.prototype.init = async function() {
|
|
382
|
-
let firstErr;
|
|
383
|
-
try {
|
|
384
|
-
return await origInit.call(this);
|
|
385
|
-
} catch (err) {
|
|
386
|
-
if (!err.message?.includes("404") && !err.message?.includes("Tree not found")) {
|
|
387
|
-
throw err;
|
|
388
|
-
}
|
|
389
|
-
firstErr = err;
|
|
390
|
-
}
|
|
391
|
-
if (patched.has(this)) throw firstErr;
|
|
392
|
-
patched.add(this);
|
|
393
|
-
console.log(`[Gitee] init failed with branch="${this.api.branch}", resolving to SHA...`);
|
|
394
|
-
const baseUrl = this.api.baseUrl || "https://gitee.com/api/v5";
|
|
395
|
-
const auth = `access_token=${this.api.token}`;
|
|
396
|
-
const branchUrl = `${baseUrl}/repos/${this.api.owner}/${this.api.repo}/branches/${this.api.branch}?${auth}`;
|
|
397
|
-
let branchRes = await fetch(branchUrl);
|
|
398
|
-
let branchData;
|
|
399
|
-
if (branchRes.ok) {
|
|
400
|
-
branchData = await branchRes.json();
|
|
401
|
-
} else {
|
|
402
|
-
console.log(`[Gitee] Branch "${this.api.branch}" not found, creating...`);
|
|
403
|
-
const defaultBranch = this.api.branch === "master" ? "main" : "master";
|
|
404
|
-
let defaultSha;
|
|
405
|
-
for (const name of [defaultBranch, "main", "master"]) {
|
|
406
|
-
const dr = await fetch(`${baseUrl}/repos/${this.api.owner}/${this.api.repo}/branches/${name}?${auth}`);
|
|
407
|
-
if (dr.ok) {
|
|
408
|
-
const dd = await dr.json();
|
|
409
|
-
defaultSha = dd.commit?.sha;
|
|
410
|
-
if (defaultSha) break;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
if (!defaultSha) {
|
|
414
|
-
console.log(`[Gitee] No branches found in repo, skipping tree init`);
|
|
415
|
-
this.initialized = true;
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
const createRes = await fetch(`${baseUrl}/repos/${this.api.owner}/${this.api.repo}/branches?${auth}`, {
|
|
419
|
-
method: "POST",
|
|
420
|
-
headers: { "Content-Type": "application/json" },
|
|
421
|
-
body: JSON.stringify({
|
|
422
|
-
refs: defaultSha,
|
|
423
|
-
branch_name: this.api.branch
|
|
424
|
-
})
|
|
425
|
-
});
|
|
426
|
-
if (!createRes.ok) {
|
|
427
|
-
const errText = await createRes.text().catch(() => "");
|
|
428
|
-
throw new Error(`Gitee: failed to create branch "${this.api.branch}": ${createRes.status} ${errText}`);
|
|
429
|
-
}
|
|
430
|
-
branchData = await createRes.json();
|
|
431
|
-
console.log(`[Gitee] Branch "${this.api.branch}" created from ${defaultSha.slice(0, 8)}`);
|
|
432
|
-
}
|
|
433
|
-
const commitSha = branchData.commit?.sha;
|
|
434
|
-
if (!commitSha) throw new Error(`Gitee: could not get commit SHA for branch "${this.api.branch}"`);
|
|
435
|
-
const commitUrl = `${baseUrl}/repos/${this.api.owner}/${this.api.repo}/git/commits/${commitSha}?${auth}`;
|
|
436
|
-
const commitRes = await fetch(commitUrl);
|
|
437
|
-
if (!commitRes.ok) throw new Error(`Gitee: commit ${commitSha} not found (${commitRes.status})`);
|
|
438
|
-
const commitData = await commitRes.json();
|
|
439
|
-
const treeSha = commitData.tree?.sha;
|
|
440
|
-
if (!treeSha) throw new Error(`Gitee: could not get tree SHA from commit ${commitSha}`);
|
|
441
|
-
const realBranch = this.api.branch;
|
|
442
|
-
this.api.branch = treeSha;
|
|
443
|
-
console.log(`[Gitee] Resolved branch="${realBranch}" \u2192 commit=${commitSha.slice(0, 8)} \u2192 tree=${treeSha.slice(0, 8)}`);
|
|
444
|
-
try {
|
|
445
|
-
return await origInit.call(this);
|
|
446
|
-
} finally {
|
|
447
|
-
this.api.branch = realBranch;
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
return wrapZenFSFileSystem({
|
|
451
|
-
backend: zenGitee.Gitee,
|
|
452
|
-
token: options.token,
|
|
453
|
-
owner: options.owner,
|
|
454
|
-
repo: options.repo,
|
|
455
|
-
branch: options.branch,
|
|
456
|
-
baseUrl: options.baseUrl && options.baseUrl.trim() || void 0
|
|
457
|
-
});
|
|
458
|
-
});
|
|
459
|
-
registerBackend("WebDAV", async (options) => {
|
|
460
|
-
const url = options.url ?? "";
|
|
461
|
-
const username = options.username ?? "";
|
|
462
|
-
const password = options.password ?? "";
|
|
463
|
-
const rootPath = options.rootPath ?? "/";
|
|
464
|
-
if (!url) throw new Error('WebDAV backend requires "url" option');
|
|
465
|
-
const authHeader = username ? `Basic ${btoa(`${username}:${password}`)}` : "";
|
|
466
|
-
const davUrl = (path) => {
|
|
467
|
-
const cleanRoot = rootPath.replace(/\/$/, "");
|
|
468
|
-
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
469
|
-
return `${url.replace(/\/$/, "")}${cleanRoot}${cleanPath}`;
|
|
470
|
-
};
|
|
471
|
-
const davFetch = async (path, method, body) => {
|
|
472
|
-
const headers = {};
|
|
473
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
474
|
-
if (body) headers["Content-Type"] = "application/xml";
|
|
475
|
-
const res = await fetch(davUrl(path), { method, headers, body });
|
|
476
|
-
if (!res.ok && res.status !== 404) throw new Error(`WebDAV ${res.status} ${method} ${davUrl(path)}`);
|
|
477
|
-
return res;
|
|
478
|
-
};
|
|
479
|
-
const parseMultiStatus = async (res) => {
|
|
480
|
-
const text = await res.text();
|
|
481
|
-
const results = [];
|
|
482
|
-
const responses = text.match(/<D:response[^>]*>[\s\S]*?<\/D:response>/gi) || [];
|
|
483
|
-
for (const resp of responses) {
|
|
484
|
-
const href = (resp.match(/<D:href>([^<]+)<\/D:href>/i) || [])[1] || "";
|
|
485
|
-
const isDir = /<D:collection\s*\/>/i.test(resp) || /<D:resourcetype>.*<D:collection/.test(resp);
|
|
486
|
-
const sizeMatch = resp.match(/<D:getcontentlength>([^<]+)<\/D:getcontentlength>/i);
|
|
487
|
-
const size = sizeMatch ? parseInt(sizeMatch[1]) : 0;
|
|
488
|
-
const decoded = decodeURIComponent(href);
|
|
489
|
-
results.push({ path: decoded, isDir, size });
|
|
490
|
-
}
|
|
491
|
-
return results;
|
|
492
|
-
};
|
|
493
|
-
const exists = async (path) => {
|
|
494
|
-
const res = await davFetch(path, "PROPFIND");
|
|
495
|
-
return res.ok;
|
|
496
|
-
};
|
|
497
|
-
const backend = {
|
|
498
|
-
async readFile(path, ...args) {
|
|
499
|
-
const res = await davFetch(path, "GET");
|
|
500
|
-
if (!res.ok) throw new Error(`ENOENT: ${path}`);
|
|
501
|
-
if (args[0] === "utf-8") return res.text();
|
|
502
|
-
const buf = await res.arrayBuffer();
|
|
503
|
-
return new Uint8Array(buf);
|
|
504
|
-
},
|
|
505
|
-
async writeFile(path, data, _options) {
|
|
506
|
-
const headers = { "Content-Type": "application/octet-stream" };
|
|
507
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
508
|
-
await fetch(davUrl(path), {
|
|
509
|
-
method: "PUT",
|
|
510
|
-
headers,
|
|
511
|
-
body: data instanceof ArrayBuffer ? data : data instanceof Uint8Array ? new Uint8Array(data).buffer : new TextEncoder().encode(data)
|
|
512
|
-
});
|
|
513
|
-
},
|
|
514
|
-
async readdir(path) {
|
|
515
|
-
const headers = { Depth: "1" };
|
|
516
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
517
|
-
const res = await fetch(davUrl(path), { method: "PROPFIND", headers });
|
|
518
|
-
if (!res.ok) throw new Error(`WebDAV PROPFIND failed: ${res.status}`);
|
|
519
|
-
const items = await parseMultiStatus(res);
|
|
520
|
-
const prefix = davUrl(path);
|
|
521
|
-
return items.filter((i) => i.path !== prefix && i.path !== `${prefix}/`).map((i) => i.path.split("/").filter(Boolean).pop() || "");
|
|
522
|
-
},
|
|
523
|
-
async stat(path) {
|
|
524
|
-
const headers = { Depth: "0" };
|
|
525
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
526
|
-
const res = await fetch(davUrl(path), { method: "PROPFIND", headers });
|
|
527
|
-
if (!res.ok) throw new Error(`ENOENT: ${path}`);
|
|
528
|
-
const items = await parseMultiStatus(res);
|
|
529
|
-
const item = items[0];
|
|
530
|
-
return { isFile: () => !item.isDir, isDirectory: () => item.isDir, size: item.size };
|
|
531
|
-
},
|
|
532
|
-
async exists(path) {
|
|
533
|
-
return exists(path);
|
|
534
|
-
},
|
|
535
|
-
async mkdir(path) {
|
|
536
|
-
const headers = {};
|
|
537
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
538
|
-
const res = await fetch(davUrl(path), { method: "MKCOL", headers });
|
|
539
|
-
if (!res.ok && res.status !== 405) throw new Error(`WebDAV MKCOL failed: ${res.status}`);
|
|
540
|
-
},
|
|
541
|
-
async unlink(path) {
|
|
542
|
-
await davFetch(path, "DELETE");
|
|
543
|
-
},
|
|
544
|
-
async rmdir(path) {
|
|
545
|
-
const items = await (async () => {
|
|
546
|
-
const headers = { Depth: "1" };
|
|
547
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
548
|
-
const res = await fetch(davUrl(path), { method: "PROPFIND", headers });
|
|
549
|
-
if (!res.ok) return [];
|
|
550
|
-
const parsed = await parseMultiStatus(res);
|
|
551
|
-
const prefix = davUrl(path);
|
|
552
|
-
return parsed.filter((i) => i.path !== prefix && i.path !== `${prefix}/`);
|
|
553
|
-
})();
|
|
554
|
-
for (const item of items) {
|
|
555
|
-
if (item.isDir) await backend.rmdir(item.path);
|
|
556
|
-
else await backend.unlink(item.path);
|
|
557
|
-
}
|
|
558
|
-
await davFetch(path, "DELETE");
|
|
559
|
-
},
|
|
560
|
-
async rename(oldPath, newPath) {
|
|
561
|
-
const headers = { Destination: davUrl(newPath) };
|
|
562
|
-
if (authHeader) headers["Authorization"] = authHeader;
|
|
563
|
-
await fetch(davUrl(oldPath), { method: "MOVE", headers });
|
|
564
|
-
}
|
|
565
|
-
};
|
|
566
|
-
return backend;
|
|
567
|
-
});
|
|
568
351
|
|
|
569
352
|
// src/version.ts
|
|
570
353
|
function versionPathFor(configFilePath) {
|
|
@@ -1275,7 +1058,9 @@ export {
|
|
|
1275
1058
|
readVersion,
|
|
1276
1059
|
registerBackend,
|
|
1277
1060
|
sha256,
|
|
1061
|
+
unregisterBackend,
|
|
1278
1062
|
verifyOrRepairVersion,
|
|
1279
1063
|
versionPathFor,
|
|
1064
|
+
wrapZenFSFileSystem,
|
|
1280
1065
|
writeVersion
|
|
1281
1066
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zen-fs-config",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
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",
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
22
22
|
"clean": "rm -rf dist",
|
|
23
23
|
"typecheck": "tsc --noEmit",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
24
26
|
"prepublishOnly": "npm run clean && npm run build"
|
|
25
27
|
},
|
|
26
28
|
"keywords": [
|
|
@@ -39,20 +41,15 @@
|
|
|
39
41
|
"peerDependencies": {
|
|
40
42
|
"@zenfs/core": ">=2.3.0",
|
|
41
43
|
"zen-fs-cache": ">=1.0.0",
|
|
42
|
-
"zen-fs-gitee": ">=1.0.0",
|
|
43
|
-
"zen-fs-github": ">=1.0.0",
|
|
44
44
|
"zen-fs-sync": ">=0.1.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@zenfs/core": "^2.5.7",
|
|
48
48
|
"tsup": "^8.5.1",
|
|
49
49
|
"typescript": "^5.9.3",
|
|
50
|
+
"vitest": "^1.6.1",
|
|
50
51
|
"zen-fs-cache": "^1.0.1",
|
|
51
|
-
"zen-fs-gitee": "^1.0.0",
|
|
52
|
-
"zen-fs-github": "^1.0.0",
|
|
53
52
|
"zen-fs-sync": "^0.1.0"
|
|
54
53
|
},
|
|
55
|
-
"dependencies": {
|
|
56
|
-
"@zenfs/dom": "^1.2.9"
|
|
57
|
-
}
|
|
54
|
+
"dependencies": {}
|
|
58
55
|
}
|