@bendyline/docblocks 1.1.2 → 2.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.
- package/README.md +76 -11
- package/dist/chunk-2D7Y4IR3.js +1293 -0
- package/dist/chunk-2D7Y4IR3.js.map +1 -0
- package/dist/chunk-5MYAICRE.js +957 -0
- package/dist/chunk-5MYAICRE.js.map +1 -0
- package/dist/chunk-75UUF6C4.js +802 -0
- package/dist/chunk-75UUF6C4.js.map +1 -0
- package/dist/chunk-AEHCVWU5.js +25 -0
- package/dist/chunk-AEHCVWU5.js.map +1 -0
- package/dist/chunk-AYZ4HN5M.js +20 -0
- package/dist/chunk-AYZ4HN5M.js.map +1 -0
- package/dist/chunk-EGNDADXB.js +111 -0
- package/dist/chunk-EGNDADXB.js.map +1 -0
- package/dist/chunk-EZKJZUNJ.js +13 -0
- package/dist/chunk-EZKJZUNJ.js.map +1 -0
- package/dist/chunk-FNW556FN.js +75 -0
- package/dist/chunk-FNW556FN.js.map +1 -0
- package/dist/chunk-GCPBEMMH.js +563 -0
- package/dist/chunk-GCPBEMMH.js.map +1 -0
- package/dist/chunk-J32GU3F3.js +1664 -0
- package/dist/chunk-J32GU3F3.js.map +1 -0
- package/dist/chunk-PGR7LK6D.js +1062 -0
- package/dist/chunk-PGR7LK6D.js.map +1 -0
- package/dist/chunk-RQJRQF46.js +787 -0
- package/dist/chunk-RQJRQF46.js.map +1 -0
- package/dist/{chunk-ME76RUMR.js → chunk-RVQHY3XU.js} +15 -1
- package/dist/chunk-RVQHY3XU.js.map +1 -0
- package/dist/chunk-SI2LU2RP.js +224 -0
- package/dist/chunk-SI2LU2RP.js.map +1 -0
- package/dist/chunk-W5UUUII5.js +142 -0
- package/dist/chunk-W5UUUII5.js.map +1 -0
- package/dist/document/document-session.d.ts +127 -0
- package/dist/document/document-session.d.ts.map +1 -0
- package/dist/document/document-session.js +749 -0
- package/dist/document/document-session.js.map +1 -0
- package/dist/document/filesystem-target.d.ts +17 -0
- package/dist/document/filesystem-target.d.ts.map +1 -0
- package/dist/document/filesystem-target.js +70 -0
- package/dist/document/filesystem-target.js.map +1 -0
- package/dist/document/index.d.ts +6 -0
- package/dist/document/index.d.ts.map +1 -0
- package/dist/document/index.js +4 -0
- package/dist/document/index.js.map +1 -0
- package/dist/document/recovery-journal.d.ts +125 -0
- package/dist/document/recovery-journal.d.ts.map +1 -0
- package/dist/document/recovery-journal.js +385 -0
- package/dist/document/recovery-journal.js.map +1 -0
- package/dist/document/types.d.ts +89 -0
- package/dist/document/types.d.ts.map +1 -0
- package/dist/document/types.js +9 -0
- package/dist/document/types.js.map +1 -0
- package/dist/filesystem/commit-lock.d.ts +3 -0
- package/dist/filesystem/commit-lock.d.ts.map +1 -0
- package/dist/filesystem/commit-lock.js +20 -0
- package/dist/filesystem/commit-lock.js.map +1 -0
- package/dist/filesystem/dbk-workspace.d.ts +34 -0
- package/dist/filesystem/dbk-workspace.d.ts.map +1 -0
- package/dist/filesystem/dbk-workspace.js +85 -0
- package/dist/filesystem/dbk-workspace.js.map +1 -0
- package/dist/filesystem/electron-provider-v2.d.ts +36 -0
- package/dist/filesystem/electron-provider-v2.d.ts.map +1 -0
- package/dist/filesystem/electron-provider-v2.js +503 -0
- package/dist/filesystem/electron-provider-v2.js.map +1 -0
- package/dist/filesystem/electron-provider.d.ts +6 -3
- package/dist/filesystem/electron-provider.d.ts.map +1 -1
- package/dist/filesystem/electron-provider.js +18 -13
- package/dist/filesystem/electron-provider.js.map +1 -1
- package/dist/filesystem/electron.d.ts +4 -0
- package/dist/filesystem/electron.d.ts.map +1 -0
- package/dist/filesystem/electron.js +4 -0
- package/dist/filesystem/electron.js.map +1 -0
- package/dist/filesystem/filesystem-content-container.d.ts +1 -1
- package/dist/filesystem/filesystem-content-container.d.ts.map +1 -1
- package/dist/filesystem/filesystem-content-container.js +50 -13
- package/dist/filesystem/filesystem-content-container.js.map +1 -1
- package/dist/filesystem/fs-error.d.ts +46 -0
- package/dist/filesystem/fs-error.d.ts.map +1 -0
- package/dist/filesystem/fs-error.js +170 -0
- package/dist/filesystem/fs-error.js.map +1 -0
- package/dist/filesystem/index.d.ts +15 -1
- package/dist/filesystem/index.d.ts.map +1 -1
- package/dist/filesystem/index.js +13 -0
- package/dist/filesystem/index.js.map +1 -1
- package/dist/filesystem/indexeddb-content-container.d.ts +3 -1
- package/dist/filesystem/indexeddb-content-container.d.ts.map +1 -1
- package/dist/filesystem/indexeddb-content-container.js +18 -11
- package/dist/filesystem/indexeddb-content-container.js.map +1 -1
- package/dist/filesystem/indexeddb-provider-v2.d.ts +96 -0
- package/dist/filesystem/indexeddb-provider-v2.d.ts.map +1 -0
- package/dist/filesystem/indexeddb-provider-v2.js +1241 -0
- package/dist/filesystem/indexeddb-provider-v2.js.map +1 -0
- package/dist/filesystem/indexeddb-provider.d.ts +21 -14
- package/dist/filesystem/indexeddb-provider.d.ts.map +1 -1
- package/dist/filesystem/indexeddb-provider.js +111 -261
- package/dist/filesystem/indexeddb-provider.js.map +1 -1
- package/dist/filesystem/indexeddb-store.d.ts +34 -0
- package/dist/filesystem/indexeddb-store.d.ts.map +1 -0
- package/dist/filesystem/indexeddb-store.js +165 -0
- package/dist/filesystem/indexeddb-store.js.map +1 -0
- package/dist/filesystem/indexeddb.d.ts +5 -0
- package/dist/filesystem/indexeddb.d.ts.map +1 -0
- package/dist/filesystem/indexeddb.js +5 -0
- package/dist/filesystem/indexeddb.js.map +1 -0
- package/dist/filesystem/legacy-v2-adapter.d.ts +62 -0
- package/dist/filesystem/legacy-v2-adapter.d.ts.map +1 -0
- package/dist/filesystem/legacy-v2-adapter.js +499 -0
- package/dist/filesystem/legacy-v2-adapter.js.map +1 -0
- package/dist/filesystem/memory-provider-v2.d.ts +92 -0
- package/dist/filesystem/memory-provider-v2.d.ts.map +1 -0
- package/dist/filesystem/memory-provider-v2.js +615 -0
- package/dist/filesystem/memory-provider-v2.js.map +1 -0
- package/dist/filesystem/memory-provider.d.ts +54 -0
- package/dist/filesystem/memory-provider.d.ts.map +1 -0
- package/dist/filesystem/memory-provider.js +132 -0
- package/dist/filesystem/memory-provider.js.map +1 -0
- package/dist/filesystem/memory.d.ts +4 -0
- package/dist/filesystem/memory.d.ts.map +1 -0
- package/dist/filesystem/memory.js +4 -0
- package/dist/filesystem/memory.js.map +1 -0
- package/dist/filesystem/move-entry.d.ts +9 -0
- package/dist/filesystem/move-entry.d.ts.map +1 -0
- package/dist/filesystem/move-entry.js +103 -0
- package/dist/filesystem/move-entry.js.map +1 -0
- package/dist/filesystem/move-error.d.ts +53 -0
- package/dist/filesystem/move-error.d.ts.map +1 -0
- package/dist/filesystem/move-error.js +99 -0
- package/dist/filesystem/move-error.js.map +1 -0
- package/dist/filesystem/native-provider-v2.d.ts +75 -0
- package/dist/filesystem/native-provider-v2.d.ts.map +1 -0
- package/dist/filesystem/native-provider-v2.js +849 -0
- package/dist/filesystem/native-provider-v2.js.map +1 -0
- package/dist/filesystem/native-provider.d.ts +9 -1
- package/dist/filesystem/native-provider.d.ts.map +1 -1
- package/dist/filesystem/native-provider.js +172 -37
- package/dist/filesystem/native-provider.js.map +1 -1
- package/dist/filesystem/native.d.ts +4 -0
- package/dist/filesystem/native.d.ts.map +1 -0
- package/dist/filesystem/native.js +4 -0
- package/dist/filesystem/native.js.map +1 -0
- package/dist/filesystem/types.d.ts +37 -4
- package/dist/filesystem/types.d.ts.map +1 -1
- package/dist/filesystem/types.js +11 -3
- package/dist/filesystem/types.js.map +1 -1
- package/dist/filesystem/v2.d.ts +172 -0
- package/dist/filesystem/v2.d.ts.map +1 -0
- package/dist/filesystem/v2.js +16 -0
- package/dist/filesystem/v2.js.map +1 -0
- package/dist/filesystem/workspace-path.d.ts +21 -0
- package/dist/filesystem/workspace-path.d.ts.map +1 -0
- package/dist/filesystem/workspace-path.js +80 -0
- package/dist/filesystem/workspace-path.js.map +1 -0
- package/dist/host/filesystem-v2.d.ts +45 -0
- package/dist/host/filesystem-v2.d.ts.map +1 -0
- package/dist/host/filesystem-v2.js +13 -0
- package/dist/host/filesystem-v2.js.map +1 -0
- package/dist/host/git.d.ts +200 -0
- package/dist/host/git.d.ts.map +1 -0
- package/dist/host/git.js +15 -0
- package/dist/host/git.js.map +1 -0
- package/dist/host/index.d.ts +4 -1
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +2 -0
- package/dist/host/index.js.map +1 -1
- package/dist/host/types.d.ts +114 -31
- package/dist/host/types.d.ts.map +1 -1
- package/dist/host/wire-policy.d.ts +22 -0
- package/dist/host/wire-policy.d.ts.map +1 -0
- package/dist/host/wire-policy.js +92 -0
- package/dist/host/wire-policy.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +4 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/types.d.ts +466 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +25 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/mcp/wire.d.ts +29 -0
- package/dist/mcp/wire.d.ts.map +1 -0
- package/dist/mcp/wire.js +1046 -0
- package/dist/mcp/wire.js.map +1 -0
- package/dist/mcp/zod.d.ts +5763 -0
- package/dist/mcp/zod.d.ts.map +1 -0
- package/dist/mcp/zod.js +908 -0
- package/dist/mcp/zod.js.map +1 -0
- package/dist/types-CX893hgH.d.ts +468 -0
- package/dist/types-CsX6EN2t.d.ts +301 -0
- package/dist/types-eOlt9J7n.d.ts +478 -0
- package/dist/vscode/index.d.ts +3 -0
- package/dist/vscode/index.d.ts.map +1 -0
- package/dist/vscode/index.js +3 -0
- package/dist/vscode/index.js.map +1 -0
- package/dist/vscode/messages.d.ts +204 -0
- package/dist/vscode/messages.d.ts.map +1 -0
- package/dist/vscode/messages.js +494 -0
- package/dist/vscode/messages.js.map +1 -0
- package/dist/workspace/index.d.ts +3 -2
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +2 -1
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/reconcile-electron.d.ts +19 -0
- package/dist/workspace/reconcile-electron.d.ts.map +1 -0
- package/dist/workspace/reconcile-electron.js +42 -0
- package/dist/workspace/reconcile-electron.js.map +1 -0
- package/dist/workspace/types.d.ts +41 -1
- package/dist/workspace/types.d.ts.map +1 -1
- package/dist/workspace/types.js +0 -4
- package/dist/workspace/types.js.map +1 -1
- package/dist/workspace/workspace-manager.d.ts +17 -5
- package/dist/workspace/workspace-manager.d.ts.map +1 -1
- package/dist/workspace/workspace-manager.js +55 -11
- package/dist/workspace/workspace-manager.js.map +1 -1
- package/dist/workspace-path-CWVrcuPL.d.ts +21 -0
- package/package.json +43 -2
- package/src/document/document-session.ts +834 -0
- package/src/document/filesystem-target.ts +109 -0
- package/src/document/index.ts +41 -0
- package/src/document/recovery-journal.ts +536 -0
- package/src/document/types.ts +116 -0
- package/src/filesystem/commit-lock.ts +29 -0
- package/src/filesystem/dbk-workspace.ts +135 -0
- package/src/filesystem/electron-provider-v2.ts +630 -0
- package/src/filesystem/electron-provider.ts +25 -14
- package/src/filesystem/electron.ts +3 -0
- package/src/filesystem/filesystem-content-container.ts +53 -15
- package/src/filesystem/fs-error.ts +234 -0
- package/src/filesystem/index.ts +119 -0
- package/src/filesystem/indexeddb-content-container.ts +20 -12
- package/src/filesystem/indexeddb-provider-v2.ts +1740 -0
- package/src/filesystem/indexeddb-provider.ts +126 -300
- package/src/filesystem/indexeddb-store.ts +207 -0
- package/src/filesystem/indexeddb.ts +12 -0
- package/src/filesystem/legacy-v2-adapter.ts +779 -0
- package/src/filesystem/memory-provider-v2.ts +896 -0
- package/src/filesystem/memory-provider.ts +180 -0
- package/src/filesystem/memory.ts +15 -0
- package/src/filesystem/move-entry.ts +142 -0
- package/src/filesystem/move-error.ts +159 -0
- package/src/filesystem/native-provider-v2.ts +1165 -0
- package/src/filesystem/native-provider.ts +194 -41
- package/src/filesystem/native.ts +16 -0
- package/src/filesystem/types.ts +49 -4
- package/src/filesystem/v2.ts +231 -0
- package/src/filesystem/workspace-path.ts +92 -0
- package/src/host/filesystem-v2.ts +104 -0
- package/src/host/git.ts +246 -0
- package/src/host/index.ts +46 -0
- package/src/host/types.ts +155 -34
- package/src/host/wire-policy.ts +103 -0
- package/src/index.ts +2 -1
- package/src/mcp/index.ts +3 -0
- package/src/mcp/types.ts +594 -0
- package/src/mcp/wire.ts +1280 -0
- package/src/mcp/zod.ts +1000 -0
- package/src/vscode/index.ts +2 -0
- package/src/vscode/messages.ts +704 -0
- package/src/workspace/index.ts +8 -1
- package/src/workspace/reconcile-electron.ts +65 -0
- package/src/workspace/types.ts +39 -1
- package/src/workspace/workspace-manager.ts +71 -10
- package/dist/chunk-GUNM43XZ.js +0 -63
- package/dist/chunk-GUNM43XZ.js.map +0 -1
- package/dist/chunk-IM7LCQJ6.js +0 -850
- package/dist/chunk-IM7LCQJ6.js.map +0 -1
- package/dist/chunk-ME76RUMR.js.map +0 -1
- package/dist/index-pgBHBQ9G.d.ts +0 -213
|
@@ -0,0 +1,957 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseFileSystemVersion
|
|
3
|
+
} from "./chunk-AYZ4HN5M.js";
|
|
4
|
+
import {
|
|
5
|
+
FileSystemMoveRecoveryError,
|
|
6
|
+
FileSystemPartialMoveError,
|
|
7
|
+
describeEntryMove,
|
|
8
|
+
describePartialMove
|
|
9
|
+
} from "./chunk-EGNDADXB.js";
|
|
10
|
+
import {
|
|
11
|
+
FsError,
|
|
12
|
+
WORKSPACE_ROOT,
|
|
13
|
+
fsErrorFromUnknown,
|
|
14
|
+
parseWorkspacePath,
|
|
15
|
+
workspacePathBasename,
|
|
16
|
+
workspacePathContains,
|
|
17
|
+
workspacePathDirname,
|
|
18
|
+
workspacePathJoin,
|
|
19
|
+
workspacePathToLegacy
|
|
20
|
+
} from "./chunk-SI2LU2RP.js";
|
|
21
|
+
import {
|
|
22
|
+
getFileSystemProviderV2
|
|
23
|
+
} from "./chunk-EZKJZUNJ.js";
|
|
24
|
+
|
|
25
|
+
// src/filesystem/legacy-v2-adapter.ts
|
|
26
|
+
var EPOCH = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
27
|
+
var LegacyFileSystemProviderV2Adapter = class {
|
|
28
|
+
constructor(provider, options) {
|
|
29
|
+
this.provider = provider;
|
|
30
|
+
this.options = options;
|
|
31
|
+
this.operationTail = Promise.resolve();
|
|
32
|
+
this.subscriptions = /* @__PURE__ */ new Set();
|
|
33
|
+
this.closing = false;
|
|
34
|
+
this.disposed = false;
|
|
35
|
+
this.disposePromise = null;
|
|
36
|
+
this.id = provider.id;
|
|
37
|
+
this.label = provider.label;
|
|
38
|
+
this.capabilities = Object.freeze({
|
|
39
|
+
writeAtomicity: options.writeAtomicity ?? "none",
|
|
40
|
+
moveAtomicity: options.moveAtomicity ?? "none",
|
|
41
|
+
// A tree snapshot spans multiple v1 calls; callers that bypass this
|
|
42
|
+
// adapter can still interleave, even though adapter operations serialize.
|
|
43
|
+
snapshotAtomicity: "none",
|
|
44
|
+
conditionalWrite: "process",
|
|
45
|
+
recursiveRemove: true,
|
|
46
|
+
watch: false,
|
|
47
|
+
caseSensitivity: options.caseSensitivity ?? "platform",
|
|
48
|
+
symlinkPolicy: options.symlinkPolicy ?? "unsupported",
|
|
49
|
+
durability: options.durability ?? "best-effort"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async stat(path) {
|
|
53
|
+
const canonical = this.canonical(path);
|
|
54
|
+
return this.enqueue("stat", async () => {
|
|
55
|
+
const entry = (await this.captureTree("stat")).entries.get(canonical);
|
|
56
|
+
return entry ? copyEntrySnapshot(entry) : null;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async readFile(path) {
|
|
60
|
+
const canonical = this.canonical(path);
|
|
61
|
+
return this.enqueue("read", async () => {
|
|
62
|
+
const entry = (await this.captureTree("read")).entries.get(canonical);
|
|
63
|
+
if (!entry) return null;
|
|
64
|
+
if (entry.kind !== "file") {
|
|
65
|
+
throw this.error("type-mismatch", "read", canonical, "Expected a file, found a directory.");
|
|
66
|
+
}
|
|
67
|
+
return Object.freeze({ entry: fileMetadata(entry), data: entry.data.slice(0) });
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async readDirectory(path) {
|
|
71
|
+
const canonical = this.canonical(path);
|
|
72
|
+
return this.enqueue("list", async () => {
|
|
73
|
+
const tree = await this.captureTree("list");
|
|
74
|
+
const entry = tree.entries.get(canonical);
|
|
75
|
+
if (!entry) throw this.error("not-found", "list", canonical, "Directory does not exist.");
|
|
76
|
+
if (entry.kind !== "directory") {
|
|
77
|
+
throw this.error("type-mismatch", "list", canonical, "Expected a directory, found a file.");
|
|
78
|
+
}
|
|
79
|
+
const children = [...tree.entries.entries()].filter(([entryPath]) => entryPath && workspacePathDirname(entryPath) === canonical).map(([, child]) => copyEntrySnapshot(child)).sort(compareSnapshots);
|
|
80
|
+
return Object.freeze(children);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async writeFile(path, data, options = {}) {
|
|
84
|
+
const canonical = this.canonicalNonRoot(path, "write");
|
|
85
|
+
const ownedData = copyBytes(data);
|
|
86
|
+
return this.enqueue("write", async () => {
|
|
87
|
+
const tree = await this.captureTree("write");
|
|
88
|
+
const existing = tree.entries.get(canonical);
|
|
89
|
+
if (existing?.kind === "directory") {
|
|
90
|
+
throw this.error(
|
|
91
|
+
"type-mismatch",
|
|
92
|
+
"write",
|
|
93
|
+
canonical,
|
|
94
|
+
"Cannot replace a directory with a file."
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const mode = options.mode ?? "upsert";
|
|
98
|
+
if (mode === "create" && existing) {
|
|
99
|
+
throw this.error("already-exists", "write", canonical, "File already exists.");
|
|
100
|
+
}
|
|
101
|
+
if (mode === "replace" && !existing) {
|
|
102
|
+
throw this.error("not-found", "write", canonical, "File does not exist.");
|
|
103
|
+
}
|
|
104
|
+
this.assertExpectedVersion(existing, options.expectedVersion, "write", canonical);
|
|
105
|
+
this.assertParents(tree, canonical, options.createParents ?? false, "write");
|
|
106
|
+
await this.invoke(
|
|
107
|
+
() => this.provider.writeBinary(workspacePathToLegacy(canonical), ownedData),
|
|
108
|
+
"write",
|
|
109
|
+
canonical
|
|
110
|
+
);
|
|
111
|
+
const written = (await this.captureTree("write")).entries.get(canonical);
|
|
112
|
+
if (written?.kind !== "file" || !equalBytes(written.data, ownedData)) {
|
|
113
|
+
throw this.error(
|
|
114
|
+
"io",
|
|
115
|
+
"write",
|
|
116
|
+
canonical,
|
|
117
|
+
"The legacy provider did not persist the requested bytes."
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return fileMetadata(written);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async createDirectory(path, options = {}) {
|
|
124
|
+
const canonical = this.canonical(path);
|
|
125
|
+
return this.enqueue("create-directory", async () => {
|
|
126
|
+
const tree = await this.captureTree("create-directory");
|
|
127
|
+
const existing = tree.entries.get(canonical);
|
|
128
|
+
if (existing) {
|
|
129
|
+
if (existing.kind !== "directory") {
|
|
130
|
+
throw this.error(
|
|
131
|
+
"type-mismatch",
|
|
132
|
+
"create-directory",
|
|
133
|
+
canonical,
|
|
134
|
+
"Cannot replace a file with a directory."
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if ((options.mode ?? "ensure") === "create") {
|
|
138
|
+
throw this.error(
|
|
139
|
+
"already-exists",
|
|
140
|
+
"create-directory",
|
|
141
|
+
canonical,
|
|
142
|
+
"Directory already exists."
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return copyDirectorySnapshot(existing);
|
|
146
|
+
}
|
|
147
|
+
this.assertParents(tree, canonical, options.createParents ?? false, "create-directory");
|
|
148
|
+
await this.invoke(
|
|
149
|
+
() => this.provider.createDirectory(workspacePathToLegacy(canonical)),
|
|
150
|
+
"create-directory",
|
|
151
|
+
canonical
|
|
152
|
+
);
|
|
153
|
+
const created = (await this.captureTree("create-directory")).entries.get(canonical);
|
|
154
|
+
if (created?.kind !== "directory") {
|
|
155
|
+
throw this.error(
|
|
156
|
+
"io",
|
|
157
|
+
"create-directory",
|
|
158
|
+
canonical,
|
|
159
|
+
"The legacy provider did not create the directory."
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return copyDirectorySnapshot(created);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async remove(path, options = {}) {
|
|
166
|
+
const canonical = this.canonicalNonRoot(path, "remove");
|
|
167
|
+
return this.enqueue("remove", async () => {
|
|
168
|
+
const tree = await this.captureTree("remove");
|
|
169
|
+
const existing = tree.entries.get(canonical);
|
|
170
|
+
if (!existing) {
|
|
171
|
+
if ((options.missing ?? "error") === "ignore") {
|
|
172
|
+
return Object.freeze({ removed: false, version: tree.version });
|
|
173
|
+
}
|
|
174
|
+
throw this.error("not-found", "remove", canonical, "Entry does not exist.");
|
|
175
|
+
}
|
|
176
|
+
this.assertExpectedVersion(existing, options.expectedVersion, "remove", canonical);
|
|
177
|
+
if (existing.kind === "directory" && !options.recursive && [...tree.entries.keys()].some(
|
|
178
|
+
(candidate) => workspacePathContains(canonical, candidate, false)
|
|
179
|
+
)) {
|
|
180
|
+
throw this.error("not-empty", "remove", canonical, "Directory is not empty.");
|
|
181
|
+
}
|
|
182
|
+
await this.invoke(
|
|
183
|
+
() => this.provider.delete(workspacePathToLegacy(canonical)),
|
|
184
|
+
"remove",
|
|
185
|
+
canonical
|
|
186
|
+
);
|
|
187
|
+
const after = await this.captureTree("remove");
|
|
188
|
+
if ([...after.entries.keys()].some((candidate) => workspacePathContains(canonical, candidate))) {
|
|
189
|
+
throw this.error(
|
|
190
|
+
"io",
|
|
191
|
+
"remove",
|
|
192
|
+
canonical,
|
|
193
|
+
"The legacy provider did not remove the complete entry."
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
return Object.freeze({ removed: true, version: after.version });
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
async move(oldPath, newPath, options = {}) {
|
|
200
|
+
const sourcePath = this.canonicalNonRoot(oldPath, "move");
|
|
201
|
+
const destinationPath = this.canonicalNonRoot(newPath, "move");
|
|
202
|
+
return this.enqueue("move", async () => {
|
|
203
|
+
const tree = await this.captureTree("move");
|
|
204
|
+
const source = tree.entries.get(sourcePath);
|
|
205
|
+
if (!source) throw this.error("not-found", "move", sourcePath, "Source does not exist.");
|
|
206
|
+
this.assertExpectedVersion(source, options.expectedVersion, "move", sourcePath);
|
|
207
|
+
if (sourcePath === destinationPath) return copyEntrySnapshot(source);
|
|
208
|
+
if (tree.entries.has(destinationPath)) {
|
|
209
|
+
throw new FsError("already-exists", "Destination already exists.", {
|
|
210
|
+
operation: "move",
|
|
211
|
+
path: sourcePath,
|
|
212
|
+
destinationPath
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
if (source.kind === "directory" && workspacePathContains(sourcePath, destinationPath, false)) {
|
|
216
|
+
throw new FsError("invalid-path", "Cannot move a directory into itself.", {
|
|
217
|
+
operation: "move",
|
|
218
|
+
path: sourcePath,
|
|
219
|
+
destinationPath
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
this.assertParents(tree, destinationPath, options.createParents ?? false, "move");
|
|
223
|
+
const sourceEntries = [...tree.entries.entries()].filter(
|
|
224
|
+
([candidate]) => workspacePathContains(sourcePath, candidate)
|
|
225
|
+
);
|
|
226
|
+
await this.invoke(
|
|
227
|
+
() => this.provider.rename(
|
|
228
|
+
workspacePathToLegacy(sourcePath),
|
|
229
|
+
workspacePathToLegacy(destinationPath)
|
|
230
|
+
),
|
|
231
|
+
"move",
|
|
232
|
+
sourcePath,
|
|
233
|
+
destinationPath
|
|
234
|
+
);
|
|
235
|
+
const after = await this.captureTree("move");
|
|
236
|
+
if ([...after.entries.keys()].some((candidate) => workspacePathContains(sourcePath, candidate))) {
|
|
237
|
+
throw this.error(
|
|
238
|
+
"io",
|
|
239
|
+
"move",
|
|
240
|
+
sourcePath,
|
|
241
|
+
"The legacy provider left source entries behind."
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
for (const [oldEntryPath, oldEntry] of sourceEntries) {
|
|
245
|
+
const suffix = oldEntryPath.slice(sourcePath.length);
|
|
246
|
+
const movedPath = parseWorkspacePath(`${destinationPath}${suffix}`);
|
|
247
|
+
const moved = after.entries.get(movedPath);
|
|
248
|
+
if (!moved || moved.kind !== oldEntry.kind || moved.version !== oldEntry.version) {
|
|
249
|
+
throw new FsError(
|
|
250
|
+
"io",
|
|
251
|
+
"The legacy provider did not move the complete entry unchanged.",
|
|
252
|
+
{
|
|
253
|
+
operation: "move",
|
|
254
|
+
path: oldEntryPath,
|
|
255
|
+
destinationPath: movedPath
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return copyEntrySnapshot(after.entries.get(destinationPath));
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
async snapshot() {
|
|
264
|
+
return this.enqueue("snapshot", async () => {
|
|
265
|
+
const tree = await this.captureTree("snapshot");
|
|
266
|
+
const entries = [...tree.entries.values()].sort((left, right) => compareText(left.path, right.path)).map((entry) => copySnapshotEntry(entry));
|
|
267
|
+
return Object.freeze({ version: tree.version, entries: Object.freeze(entries) });
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
watch(_listener, options = {}) {
|
|
271
|
+
this.assertAccepting("watch");
|
|
272
|
+
const error = new FsError(
|
|
273
|
+
"not-supported",
|
|
274
|
+
"The legacy adapter cannot observe external changes.",
|
|
275
|
+
{
|
|
276
|
+
operation: "watch"
|
|
277
|
+
}
|
|
278
|
+
);
|
|
279
|
+
let closed = false;
|
|
280
|
+
let disposePromise = null;
|
|
281
|
+
const ready = Promise.reject(error);
|
|
282
|
+
void ready.catch((caught) => {
|
|
283
|
+
try {
|
|
284
|
+
options.onError?.(caught);
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
const subscription = {
|
|
289
|
+
get closed() {
|
|
290
|
+
return closed;
|
|
291
|
+
},
|
|
292
|
+
ready,
|
|
293
|
+
dispose: () => {
|
|
294
|
+
if (disposePromise) return disposePromise;
|
|
295
|
+
closed = true;
|
|
296
|
+
this.subscriptions.delete(subscription);
|
|
297
|
+
disposePromise = Promise.resolve();
|
|
298
|
+
return disposePromise;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
this.subscriptions.add(subscription);
|
|
302
|
+
return subscription;
|
|
303
|
+
}
|
|
304
|
+
dispose() {
|
|
305
|
+
if (this.disposePromise) return this.disposePromise;
|
|
306
|
+
this.closing = true;
|
|
307
|
+
const run = this.operationTail.then(async () => {
|
|
308
|
+
try {
|
|
309
|
+
await Promise.all([...this.subscriptions].map((subscription) => subscription.dispose()));
|
|
310
|
+
await this.options.dispose?.();
|
|
311
|
+
} catch (error) {
|
|
312
|
+
throw fsErrorFromUnknown(error, { operation: "dispose" });
|
|
313
|
+
} finally {
|
|
314
|
+
this.disposed = true;
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
this.operationTail = run.then(
|
|
318
|
+
() => void 0,
|
|
319
|
+
() => void 0
|
|
320
|
+
);
|
|
321
|
+
this.disposePromise = run;
|
|
322
|
+
return run;
|
|
323
|
+
}
|
|
324
|
+
enqueue(operation, task) {
|
|
325
|
+
this.assertAccepting(operation);
|
|
326
|
+
const run = this.operationTail.then(task);
|
|
327
|
+
this.operationTail = run.then(
|
|
328
|
+
() => void 0,
|
|
329
|
+
() => void 0
|
|
330
|
+
);
|
|
331
|
+
return run;
|
|
332
|
+
}
|
|
333
|
+
assertAccepting(operation) {
|
|
334
|
+
if (!this.closing && !this.disposed) return;
|
|
335
|
+
throw new FsError("disposed", "Filesystem provider is disposed.", { operation });
|
|
336
|
+
}
|
|
337
|
+
canonical(path) {
|
|
338
|
+
return parseWorkspacePath(path);
|
|
339
|
+
}
|
|
340
|
+
canonicalNonRoot(path, operation) {
|
|
341
|
+
const canonical = this.canonical(path);
|
|
342
|
+
if (!canonical) {
|
|
343
|
+
throw this.error(
|
|
344
|
+
"invalid-path",
|
|
345
|
+
operation,
|
|
346
|
+
canonical,
|
|
347
|
+
"The workspace root is not an entry target."
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
return canonical;
|
|
351
|
+
}
|
|
352
|
+
assertExpectedVersion(entry, expected, operation, path) {
|
|
353
|
+
if (expected === void 0) return;
|
|
354
|
+
if (expected === null ? !entry : entry?.version === expected) return;
|
|
355
|
+
throw this.error(
|
|
356
|
+
"conflict",
|
|
357
|
+
operation,
|
|
358
|
+
path,
|
|
359
|
+
"Entry version does not match the expected version."
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
assertParents(tree, path, createParents, operation) {
|
|
363
|
+
let parent = workspacePathDirname(path);
|
|
364
|
+
const parents = [];
|
|
365
|
+
while (parent) {
|
|
366
|
+
parents.unshift(parent);
|
|
367
|
+
parent = workspacePathDirname(parent);
|
|
368
|
+
}
|
|
369
|
+
for (const candidate of parents) {
|
|
370
|
+
const entry = tree.entries.get(candidate);
|
|
371
|
+
if (entry?.kind === "file") {
|
|
372
|
+
throw this.error("type-mismatch", operation, candidate, "A parent path is a file.");
|
|
373
|
+
}
|
|
374
|
+
if (!entry && !createParents) {
|
|
375
|
+
throw this.error("not-found", operation, candidate, "Parent directory does not exist.");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async captureTree(operation) {
|
|
380
|
+
const entries = /* @__PURE__ */ new Map();
|
|
381
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
382
|
+
const visitDirectory = async (path) => {
|
|
383
|
+
if (visiting.has(path)) {
|
|
384
|
+
throw this.error("io", operation, path, "The legacy provider returned a directory cycle.");
|
|
385
|
+
}
|
|
386
|
+
visiting.add(path);
|
|
387
|
+
const listed = await this.invoke(
|
|
388
|
+
() => this.provider.readDirectory(workspacePathToLegacy(path)),
|
|
389
|
+
operation,
|
|
390
|
+
path
|
|
391
|
+
);
|
|
392
|
+
if (!Array.isArray(listed)) {
|
|
393
|
+
throw this.error(
|
|
394
|
+
"io",
|
|
395
|
+
operation,
|
|
396
|
+
path,
|
|
397
|
+
"The legacy provider returned an invalid directory listing."
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
const children = [];
|
|
401
|
+
const seen = /* @__PURE__ */ new Set();
|
|
402
|
+
for (const candidate of listed) {
|
|
403
|
+
if (!isLegacyEntry(candidate)) {
|
|
404
|
+
throw this.error(
|
|
405
|
+
"io",
|
|
406
|
+
operation,
|
|
407
|
+
path,
|
|
408
|
+
"The legacy provider returned an invalid directory entry."
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
const legacyEntry = candidate;
|
|
412
|
+
const childPath = parseWorkspacePath(legacyEntry.path);
|
|
413
|
+
if (!childPath || workspacePathDirname(childPath) !== path || workspacePathBasename(childPath) !== legacyEntry.name) {
|
|
414
|
+
throw this.error(
|
|
415
|
+
"io",
|
|
416
|
+
operation,
|
|
417
|
+
path,
|
|
418
|
+
"The legacy provider returned a non-child entry."
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
if (seen.has(childPath) || entries.has(childPath)) {
|
|
422
|
+
throw this.error(
|
|
423
|
+
"io",
|
|
424
|
+
operation,
|
|
425
|
+
childPath,
|
|
426
|
+
"The legacy provider returned duplicate entry paths."
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
seen.add(childPath);
|
|
430
|
+
if (legacyEntry.kind === "directory") {
|
|
431
|
+
children.push(await visitDirectory(childPath));
|
|
432
|
+
} else {
|
|
433
|
+
const file = await this.captureFile(childPath, operation);
|
|
434
|
+
entries.set(childPath, file);
|
|
435
|
+
children.push(file);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
children.sort(compareSnapshots);
|
|
439
|
+
const version = await contentVersion(
|
|
440
|
+
"directory",
|
|
441
|
+
new TextEncoder().encode(
|
|
442
|
+
children.map((child) => `${child.kind}\0${child.name.length}:${child.name}\0${child.version}`).join("\n")
|
|
443
|
+
),
|
|
444
|
+
operation,
|
|
445
|
+
path
|
|
446
|
+
);
|
|
447
|
+
const directory = Object.freeze({
|
|
448
|
+
kind: "directory",
|
|
449
|
+
path,
|
|
450
|
+
name: workspacePathBasename(path),
|
|
451
|
+
size: null,
|
|
452
|
+
version,
|
|
453
|
+
lastModified: newestLastModified(children)
|
|
454
|
+
});
|
|
455
|
+
entries.set(path, directory);
|
|
456
|
+
visiting.delete(path);
|
|
457
|
+
return directory;
|
|
458
|
+
};
|
|
459
|
+
const root = await visitDirectory(WORKSPACE_ROOT);
|
|
460
|
+
return { entries, version: root.version };
|
|
461
|
+
}
|
|
462
|
+
async captureFile(path, operation) {
|
|
463
|
+
const legacyPath = workspacePathToLegacy(path);
|
|
464
|
+
let data;
|
|
465
|
+
if (this.options.payloadModel === "binary-authoritative") {
|
|
466
|
+
const binary = await this.invoke(() => this.provider.readBinary(legacyPath), operation, path);
|
|
467
|
+
data = binary ? binary.slice(0) : null;
|
|
468
|
+
} else if (this.options.payloadModel === "binary-first-text-fallback") {
|
|
469
|
+
const binary = await this.invoke(() => this.provider.readBinary(legacyPath), operation, path);
|
|
470
|
+
if (binary) {
|
|
471
|
+
data = binary.slice(0);
|
|
472
|
+
} else {
|
|
473
|
+
const text = await this.invoke(() => this.provider.readFile(legacyPath), operation, path);
|
|
474
|
+
data = text === null ? null : new TextEncoder().encode(text).buffer;
|
|
475
|
+
}
|
|
476
|
+
} else {
|
|
477
|
+
const binary = await this.invoke(() => this.provider.readBinary(legacyPath), operation, path);
|
|
478
|
+
const text = await this.invoke(() => this.provider.readFile(legacyPath), operation, path);
|
|
479
|
+
if (binary !== null && text !== null) {
|
|
480
|
+
throw this.error(
|
|
481
|
+
"conflict",
|
|
482
|
+
operation,
|
|
483
|
+
path,
|
|
484
|
+
"The split legacy store contains both text and binary payloads."
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
data = binary ? binary.slice(0) : text === null ? null : new TextEncoder().encode(text).buffer;
|
|
488
|
+
}
|
|
489
|
+
if (!data) {
|
|
490
|
+
throw this.error(
|
|
491
|
+
"io",
|
|
492
|
+
operation,
|
|
493
|
+
path,
|
|
494
|
+
"The legacy provider listed a file without a payload."
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
const meta = await this.invoke(() => this.provider.stat(legacyPath), operation, path);
|
|
498
|
+
if (!meta || parseWorkspacePath(meta.path) !== path || meta.name !== workspacePathBasename(path) || meta.size !== data.byteLength || !Number.isFinite(Date.parse(meta.lastModified))) {
|
|
499
|
+
throw this.error(
|
|
500
|
+
"io",
|
|
501
|
+
operation,
|
|
502
|
+
path,
|
|
503
|
+
"The legacy provider returned inconsistent file metadata."
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
return Object.freeze({
|
|
507
|
+
kind: "file",
|
|
508
|
+
path,
|
|
509
|
+
name: workspacePathBasename(path),
|
|
510
|
+
size: data.byteLength,
|
|
511
|
+
version: await contentVersion("file", new Uint8Array(data), operation, path),
|
|
512
|
+
lastModified: meta.lastModified,
|
|
513
|
+
data
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
async invoke(task, operation, path, destinationPath) {
|
|
517
|
+
try {
|
|
518
|
+
return await task();
|
|
519
|
+
} catch (error) {
|
|
520
|
+
throw fsErrorFromUnknown(error, { operation, path, destinationPath });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
error(code, operation, path, message) {
|
|
524
|
+
return new FsError(code, message, { operation, path });
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
async function contentVersion(kind, data, operation, path) {
|
|
528
|
+
const prefix = new TextEncoder().encode(`${kind}\0`);
|
|
529
|
+
const input = new Uint8Array(prefix.byteLength + data.byteLength);
|
|
530
|
+
input.set(prefix);
|
|
531
|
+
input.set(data, prefix.byteLength);
|
|
532
|
+
let digest;
|
|
533
|
+
try {
|
|
534
|
+
digest = await globalThis.crypto.subtle.digest("SHA-256", input.buffer);
|
|
535
|
+
} catch (error) {
|
|
536
|
+
throw fsErrorFromUnknown(error, { operation, path, defaultCode: "not-supported" });
|
|
537
|
+
}
|
|
538
|
+
const hex = [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
539
|
+
return parseFileSystemVersion(`sha256:${hex}`);
|
|
540
|
+
}
|
|
541
|
+
function isLegacyEntry(value) {
|
|
542
|
+
if (!value || typeof value !== "object") return false;
|
|
543
|
+
const candidate = value;
|
|
544
|
+
return (candidate.kind === "file" || candidate.kind === "directory") && typeof candidate.name === "string" && typeof candidate.path === "string";
|
|
545
|
+
}
|
|
546
|
+
function copyBytes(data) {
|
|
547
|
+
const source = ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);
|
|
548
|
+
const copy = new Uint8Array(source.byteLength);
|
|
549
|
+
copy.set(source);
|
|
550
|
+
return copy.buffer;
|
|
551
|
+
}
|
|
552
|
+
function equalBytes(left, right) {
|
|
553
|
+
const leftBytes = new Uint8Array(left);
|
|
554
|
+
const rightBytes = new Uint8Array(right);
|
|
555
|
+
return leftBytes.byteLength === rightBytes.byteLength && leftBytes.every((value, index) => value === rightBytes[index]);
|
|
556
|
+
}
|
|
557
|
+
function fileMetadata(file) {
|
|
558
|
+
return Object.freeze({
|
|
559
|
+
kind: "file",
|
|
560
|
+
path: file.path,
|
|
561
|
+
name: file.name,
|
|
562
|
+
size: file.size,
|
|
563
|
+
version: file.version,
|
|
564
|
+
lastModified: file.lastModified
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
function copyDirectorySnapshot(directory) {
|
|
568
|
+
return Object.freeze({ ...directory });
|
|
569
|
+
}
|
|
570
|
+
function copyEntrySnapshot(entry) {
|
|
571
|
+
return entry.kind === "file" ? fileMetadata(entry) : copyDirectorySnapshot(entry);
|
|
572
|
+
}
|
|
573
|
+
function copySnapshotEntry(entry) {
|
|
574
|
+
return entry.kind === "file" ? Object.freeze({ ...fileMetadata(entry), data: entry.data.slice(0) }) : copyDirectorySnapshot(entry);
|
|
575
|
+
}
|
|
576
|
+
function newestLastModified(children) {
|
|
577
|
+
let newest = 0;
|
|
578
|
+
for (const child of children) {
|
|
579
|
+
const timestamp = Date.parse(child.lastModified);
|
|
580
|
+
if (Number.isFinite(timestamp)) newest = Math.max(newest, timestamp);
|
|
581
|
+
}
|
|
582
|
+
return newest ? new Date(newest).toISOString() : EPOCH;
|
|
583
|
+
}
|
|
584
|
+
function compareSnapshots(left, right) {
|
|
585
|
+
if (left.kind !== right.kind) return left.kind === "directory" ? -1 : 1;
|
|
586
|
+
return compareText(left.name, right.name);
|
|
587
|
+
}
|
|
588
|
+
function compareText(left, right) {
|
|
589
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/filesystem/move-entry.ts
|
|
593
|
+
function normalisePath(path) {
|
|
594
|
+
return parseWorkspacePath(path);
|
|
595
|
+
}
|
|
596
|
+
function dirname(path) {
|
|
597
|
+
const slash = path.lastIndexOf("/");
|
|
598
|
+
return slash < 0 ? "" : path.slice(0, slash);
|
|
599
|
+
}
|
|
600
|
+
function basename(path) {
|
|
601
|
+
const slash = path.lastIndexOf("/");
|
|
602
|
+
return slash < 0 ? path : path.slice(slash + 1);
|
|
603
|
+
}
|
|
604
|
+
function documentCompanionPath(documentPath) {
|
|
605
|
+
const rooted = documentPath.replace(/\\/g, "/").startsWith("/");
|
|
606
|
+
const clean = parseWorkspacePath(documentPath);
|
|
607
|
+
if (!clean) throw new Error("The workspace root has no document companion path.");
|
|
608
|
+
const parent = dirname(clean);
|
|
609
|
+
const name = basename(clean).replace(/\.[^.]+$/, "");
|
|
610
|
+
if (parent) return `${parent}/${name}_files`;
|
|
611
|
+
return rooted ? `/${name}_files` : `${name}_files`;
|
|
612
|
+
}
|
|
613
|
+
function isMarkdownPath(path) {
|
|
614
|
+
return /\.md$/i.test(basename(path));
|
|
615
|
+
}
|
|
616
|
+
async function moveFileSystemEntry(provider, oldPath, newPath, kind) {
|
|
617
|
+
const providerV2 = getFileSystemProviderV2(provider);
|
|
618
|
+
const exists = (candidate) => providerV2 ? providerV2.stat(parseWorkspacePath(candidate)).then((entry) => entry !== null) : provider.exists(candidate);
|
|
619
|
+
const move = (source, destination) => providerV2 ? providerV2.move(parseWorkspacePath(source), parseWorkspacePath(destination), {
|
|
620
|
+
createParents: true
|
|
621
|
+
}).then(() => void 0) : provider.rename(source, destination);
|
|
622
|
+
const oldNormalised = normalisePath(oldPath);
|
|
623
|
+
const newNormalised = normalisePath(newPath);
|
|
624
|
+
if (oldNormalised === newNormalised) return;
|
|
625
|
+
if (kind === "directory" && newNormalised.startsWith(`${oldNormalised}/`)) {
|
|
626
|
+
throw new Error("A folder cannot be moved into itself.");
|
|
627
|
+
}
|
|
628
|
+
if (await exists(newPath)) {
|
|
629
|
+
throw new Error(`An entry named "${basename(newPath)}" already exists there.`);
|
|
630
|
+
}
|
|
631
|
+
let oldCompanion = null;
|
|
632
|
+
let newCompanion = null;
|
|
633
|
+
if (kind === "file" && isMarkdownPath(oldPath)) {
|
|
634
|
+
const candidate = documentCompanionPath(oldPath);
|
|
635
|
+
if (await exists(candidate)) {
|
|
636
|
+
oldCompanion = candidate;
|
|
637
|
+
newCompanion = documentCompanionPath(newPath);
|
|
638
|
+
if (normalisePath(oldCompanion) !== normalisePath(newCompanion) && await exists(newCompanion)) {
|
|
639
|
+
throw new Error(`The companion folder "${basename(newCompanion)}" already exists there.`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
try {
|
|
644
|
+
await move(oldPath, newPath);
|
|
645
|
+
} catch (error) {
|
|
646
|
+
const state = await describeEntryMove(provider, oldPath, newPath);
|
|
647
|
+
if (state.source === "present" && state.destination === "missing") throw error;
|
|
648
|
+
throw new FileSystemMoveRecoveryError(oldPath, newPath, state, error);
|
|
649
|
+
}
|
|
650
|
+
if (!oldCompanion || !newCompanion) return;
|
|
651
|
+
try {
|
|
652
|
+
await move(oldCompanion, newCompanion);
|
|
653
|
+
} catch (error) {
|
|
654
|
+
try {
|
|
655
|
+
await move(newPath, oldPath);
|
|
656
|
+
} catch (rollbackError) {
|
|
657
|
+
const state2 = await describePartialMove(
|
|
658
|
+
provider,
|
|
659
|
+
oldPath,
|
|
660
|
+
newPath,
|
|
661
|
+
oldCompanion,
|
|
662
|
+
newCompanion
|
|
663
|
+
);
|
|
664
|
+
throw new FileSystemPartialMoveError(
|
|
665
|
+
oldPath,
|
|
666
|
+
newPath,
|
|
667
|
+
oldCompanion,
|
|
668
|
+
newCompanion,
|
|
669
|
+
state2,
|
|
670
|
+
error,
|
|
671
|
+
rollbackError
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
const state = await describePartialMove(provider, oldPath, newPath, oldCompanion, newCompanion);
|
|
675
|
+
const restoredSourceOnly = state.source === "present" && state.destination === "missing" && state.companionSource === "present" && state.companionDestination === "missing";
|
|
676
|
+
if (!restoredSourceOnly) {
|
|
677
|
+
throw new FileSystemPartialMoveError(
|
|
678
|
+
oldPath,
|
|
679
|
+
newPath,
|
|
680
|
+
oldCompanion,
|
|
681
|
+
newCompanion,
|
|
682
|
+
state,
|
|
683
|
+
error,
|
|
684
|
+
null
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// src/filesystem/dbk-workspace.ts
|
|
692
|
+
function normaliseRelativePath(path, label) {
|
|
693
|
+
if (path.includes("\\") || path.includes("\0")) {
|
|
694
|
+
throw new Error(`DBK ${label} must use a safe forward-slash path: ${path}`);
|
|
695
|
+
}
|
|
696
|
+
const normalized = path.replace(/^\/+|\/+$/g, "").replace(/\/+/g, "/");
|
|
697
|
+
const segments = normalized ? normalized.split("/") : [];
|
|
698
|
+
if (segments.length === 0 || segments.some((segment) => segment === "." || segment === "..")) {
|
|
699
|
+
throw new Error(`DBK ${label} must not be empty or traverse directories: ${path}`);
|
|
700
|
+
}
|
|
701
|
+
return segments.join("/");
|
|
702
|
+
}
|
|
703
|
+
function joinPath(parent, child) {
|
|
704
|
+
return `${parent.replace(/\/+$/g, "")}/${child.replace(/^\/+/, "")}`;
|
|
705
|
+
}
|
|
706
|
+
async function createDbkWorkspaceSnapshot(source, options) {
|
|
707
|
+
const targetDocumentPath = normaliseRelativePath(
|
|
708
|
+
options.targetDocumentPath,
|
|
709
|
+
"target document path"
|
|
710
|
+
);
|
|
711
|
+
const discoveredDocumentPath = await source.getDocumentPath();
|
|
712
|
+
if (!discoveredDocumentPath)
|
|
713
|
+
throw new Error("The external DBK has no primary markdown document.");
|
|
714
|
+
const sourceDocumentPath = normaliseRelativePath(discoveredDocumentPath, "primary document path");
|
|
715
|
+
const requestedLayout = options.assetLayout ?? "auto";
|
|
716
|
+
const assetLayout = requestedLayout === "auto" ? sourceDocumentPath === targetDocumentPath ? "preserve" : "companion" : requestedLayout;
|
|
717
|
+
const companionPath = normaliseRelativePath(
|
|
718
|
+
documentCompanionPath(targetDocumentPath),
|
|
719
|
+
"companion directory path"
|
|
720
|
+
);
|
|
721
|
+
const listed = await source.listFiles();
|
|
722
|
+
const entries = [...listed].sort((a, b) => a.path.localeCompare(b.path));
|
|
723
|
+
const seenSourcePaths = /* @__PURE__ */ new Set();
|
|
724
|
+
const files = [];
|
|
725
|
+
let documentContent = null;
|
|
726
|
+
for (const entry of entries) {
|
|
727
|
+
const sourcePath = normaliseRelativePath(entry.path, "entry path");
|
|
728
|
+
if (seenSourcePaths.has(sourcePath)) {
|
|
729
|
+
throw new Error(`The external DBK contains duplicate paths: ${sourcePath}`);
|
|
730
|
+
}
|
|
731
|
+
seenSourcePaths.add(sourcePath);
|
|
732
|
+
const data = await source.readFile(entry.path);
|
|
733
|
+
if (data === null) {
|
|
734
|
+
throw new Error(`The external DBK changed while reading: ${sourcePath}`);
|
|
735
|
+
}
|
|
736
|
+
if (sourcePath === sourceDocumentPath) {
|
|
737
|
+
documentContent = new TextDecoder().decode(data);
|
|
738
|
+
files.push({ kind: "text", path: targetDocumentPath, content: documentContent });
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
const targetPath = assetLayout === "preserve" ? sourcePath : joinPath(companionPath, sourcePath);
|
|
742
|
+
if (/\.(?:md|mdx|txt)$/i.test(sourcePath)) {
|
|
743
|
+
files.push({ kind: "text", path: targetPath, content: new TextDecoder().decode(data) });
|
|
744
|
+
} else {
|
|
745
|
+
files.push({ kind: "binary", path: targetPath, data });
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (documentContent === null) {
|
|
749
|
+
throw new Error(`The external DBK did not include its primary document: ${sourceDocumentPath}`);
|
|
750
|
+
}
|
|
751
|
+
return {
|
|
752
|
+
sourceDocumentPath,
|
|
753
|
+
targetDocumentPath,
|
|
754
|
+
documentContent,
|
|
755
|
+
assetLayout,
|
|
756
|
+
files
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
async function replaceMemoryWorkspaceFromDbk(provider, source, options) {
|
|
760
|
+
const expectedTreeVersion = provider.treeVersion;
|
|
761
|
+
const snapshot = await createDbkWorkspaceSnapshot(source, options);
|
|
762
|
+
provider.replaceContents(snapshot, expectedTreeVersion);
|
|
763
|
+
return snapshot;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/filesystem/filesystem-content-container.ts
|
|
767
|
+
import { findDocumentPath } from "@bendyline/squisq/storage";
|
|
768
|
+
var EXTENSION_MIME_MAP = {
|
|
769
|
+
".md": "text/markdown",
|
|
770
|
+
".txt": "text/plain",
|
|
771
|
+
".json": "application/json",
|
|
772
|
+
".jpg": "image/jpeg",
|
|
773
|
+
".jpeg": "image/jpeg",
|
|
774
|
+
".png": "image/png",
|
|
775
|
+
".gif": "image/gif",
|
|
776
|
+
".svg": "image/svg+xml",
|
|
777
|
+
".webp": "image/webp",
|
|
778
|
+
".avif": "image/avif",
|
|
779
|
+
".mp4": "video/mp4",
|
|
780
|
+
".webm": "video/webm",
|
|
781
|
+
".mp3": "audio/mpeg",
|
|
782
|
+
".wav": "audio/wav",
|
|
783
|
+
".ogg": "audio/ogg"
|
|
784
|
+
};
|
|
785
|
+
function guessMimeType(path) {
|
|
786
|
+
const dot = path.lastIndexOf(".");
|
|
787
|
+
if (dot === -1) return "application/octet-stream";
|
|
788
|
+
const ext = path.slice(dot).toLowerCase();
|
|
789
|
+
return EXTENSION_MIME_MAP[ext] ?? "application/octet-stream";
|
|
790
|
+
}
|
|
791
|
+
function joinPrefix(prefix, p) {
|
|
792
|
+
return workspacePathJoin(parseWorkspacePath(prefix), p);
|
|
793
|
+
}
|
|
794
|
+
var FileSystemContentContainer = class {
|
|
795
|
+
constructor(provider, prefix = ".docblocks/media") {
|
|
796
|
+
this.provider = provider;
|
|
797
|
+
this.prefix = parseWorkspacePath(prefix);
|
|
798
|
+
}
|
|
799
|
+
async readFile(path) {
|
|
800
|
+
const full = joinPrefix(this.prefix, path);
|
|
801
|
+
const providerV2 = getFileSystemProviderV2(this.provider);
|
|
802
|
+
if (providerV2) return (await providerV2.readFile(parseWorkspacePath(full)))?.data ?? null;
|
|
803
|
+
const binary = await this.provider.readBinary(full);
|
|
804
|
+
if (binary) return binary;
|
|
805
|
+
const text = await this.provider.readFile(full);
|
|
806
|
+
if (text === null) return null;
|
|
807
|
+
return new TextEncoder().encode(text).buffer;
|
|
808
|
+
}
|
|
809
|
+
async writeFile(path, data, _mimeType) {
|
|
810
|
+
const full = joinPrefix(this.prefix, path);
|
|
811
|
+
const providerV2 = getFileSystemProviderV2(this.provider);
|
|
812
|
+
if (providerV2) {
|
|
813
|
+
await providerV2.writeFile(parseWorkspacePath(full), data, {
|
|
814
|
+
mode: "upsert",
|
|
815
|
+
createParents: true
|
|
816
|
+
});
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
await this.provider.writeBinary(full, data);
|
|
820
|
+
}
|
|
821
|
+
async removeFile(path) {
|
|
822
|
+
const full = joinPrefix(this.prefix, path);
|
|
823
|
+
const providerV2 = getFileSystemProviderV2(this.provider);
|
|
824
|
+
if (providerV2) {
|
|
825
|
+
await providerV2.remove(parseWorkspacePath(full), { missing: "ignore" });
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
await this.provider.delete(full);
|
|
829
|
+
}
|
|
830
|
+
async listFiles(prefix) {
|
|
831
|
+
const entries = [];
|
|
832
|
+
const providerV2 = getFileSystemProviderV2(this.provider);
|
|
833
|
+
const root = parseWorkspacePath(this.prefix);
|
|
834
|
+
const walk = async (dir) => {
|
|
835
|
+
let children;
|
|
836
|
+
try {
|
|
837
|
+
children = providerV2 ? await providerV2.readDirectory(parseWorkspacePath(dir)) : await this.provider.readDirectory(dir);
|
|
838
|
+
} catch (error) {
|
|
839
|
+
if (dir === root && error instanceof FsError && error.code === "not-found") return;
|
|
840
|
+
throw error;
|
|
841
|
+
}
|
|
842
|
+
for (const child of children) {
|
|
843
|
+
if (child.kind === "directory") {
|
|
844
|
+
await walk(child.path);
|
|
845
|
+
} else {
|
|
846
|
+
const childPath = parseWorkspacePath(child.path);
|
|
847
|
+
const prefixPath = parseWorkspacePath(this.prefix);
|
|
848
|
+
if (!workspacePathContains(prefixPath, childPath)) continue;
|
|
849
|
+
const rel = childPath === prefixPath ? "" : childPath.slice(prefixPath.length + 1);
|
|
850
|
+
if (prefix && !rel.startsWith(prefix)) continue;
|
|
851
|
+
const size = child.size ?? (await this.provider.stat(child.path))?.size ?? 0;
|
|
852
|
+
entries.push({
|
|
853
|
+
path: rel,
|
|
854
|
+
mimeType: guessMimeType(rel),
|
|
855
|
+
size
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
await walk(root);
|
|
861
|
+
return entries;
|
|
862
|
+
}
|
|
863
|
+
async exists(path) {
|
|
864
|
+
const full = joinPrefix(this.prefix, path);
|
|
865
|
+
const providerV2 = getFileSystemProviderV2(this.provider);
|
|
866
|
+
return providerV2 ? await providerV2.stat(parseWorkspacePath(full)) !== null : this.provider.exists(full);
|
|
867
|
+
}
|
|
868
|
+
async getDocumentPath() {
|
|
869
|
+
return findDocumentPath(await this.listFiles());
|
|
870
|
+
}
|
|
871
|
+
async readDocument() {
|
|
872
|
+
const docPath = await this.getDocumentPath();
|
|
873
|
+
if (!docPath) return null;
|
|
874
|
+
const data = await this.readFile(docPath);
|
|
875
|
+
if (!data) return null;
|
|
876
|
+
return new TextDecoder().decode(data);
|
|
877
|
+
}
|
|
878
|
+
async writeDocument(markdown, filename) {
|
|
879
|
+
const name = filename ?? "index.md";
|
|
880
|
+
const data = new TextEncoder().encode(markdown);
|
|
881
|
+
await this.writeFile(name, data, "text/markdown");
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
// src/filesystem/file-media-provider.ts
|
|
886
|
+
function stripExt(name) {
|
|
887
|
+
return name.replace(/\.[^.]+$/, "");
|
|
888
|
+
}
|
|
889
|
+
function createFileMediaProvider(container, markdownBasename) {
|
|
890
|
+
const folder = stripExt(markdownBasename) + "_files";
|
|
891
|
+
const prefix = folder + "/";
|
|
892
|
+
const blobUrlCache = /* @__PURE__ */ new Map();
|
|
893
|
+
function toKey(ref) {
|
|
894
|
+
const clean = ref.replace(/^\/+/, "");
|
|
895
|
+
return clean.startsWith(prefix) ? clean : prefix + clean;
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
async resolveUrl(ref) {
|
|
899
|
+
const key = toKey(ref);
|
|
900
|
+
const cached = blobUrlCache.get(key);
|
|
901
|
+
if (cached) return cached;
|
|
902
|
+
const data = await container.readFile(key);
|
|
903
|
+
if (!data) return ref;
|
|
904
|
+
const entries = await container.listFiles();
|
|
905
|
+
const entry = entries.find((e) => e.path === key);
|
|
906
|
+
const mimeType = entry?.mimeType ?? "application/octet-stream";
|
|
907
|
+
const url = URL.createObjectURL(new Blob([data], { type: mimeType }));
|
|
908
|
+
blobUrlCache.set(key, url);
|
|
909
|
+
return url;
|
|
910
|
+
},
|
|
911
|
+
async listMedia() {
|
|
912
|
+
const entries = await container.listFiles(prefix);
|
|
913
|
+
return entries.filter((e) => !e.path.toLowerCase().endsWith(".md")).map((e) => ({
|
|
914
|
+
name: e.path,
|
|
915
|
+
mimeType: e.mimeType,
|
|
916
|
+
size: e.size
|
|
917
|
+
}));
|
|
918
|
+
},
|
|
919
|
+
async addMedia(name, data, mimeType) {
|
|
920
|
+
const key = toKey(name);
|
|
921
|
+
const cached = blobUrlCache.get(key);
|
|
922
|
+
if (cached) {
|
|
923
|
+
URL.revokeObjectURL(cached);
|
|
924
|
+
blobUrlCache.delete(key);
|
|
925
|
+
}
|
|
926
|
+
const buffer = data instanceof Blob ? new Uint8Array(await data.arrayBuffer()) : data;
|
|
927
|
+
await container.writeFile(key, buffer, mimeType);
|
|
928
|
+
return key;
|
|
929
|
+
},
|
|
930
|
+
async removeMedia(ref) {
|
|
931
|
+
const key = toKey(ref);
|
|
932
|
+
const cached = blobUrlCache.get(key);
|
|
933
|
+
if (cached) {
|
|
934
|
+
URL.revokeObjectURL(cached);
|
|
935
|
+
blobUrlCache.delete(key);
|
|
936
|
+
}
|
|
937
|
+
await container.removeFile(key);
|
|
938
|
+
},
|
|
939
|
+
dispose() {
|
|
940
|
+
for (const url of blobUrlCache.values()) {
|
|
941
|
+
URL.revokeObjectURL(url);
|
|
942
|
+
}
|
|
943
|
+
blobUrlCache.clear();
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
export {
|
|
949
|
+
LegacyFileSystemProviderV2Adapter,
|
|
950
|
+
documentCompanionPath,
|
|
951
|
+
moveFileSystemEntry,
|
|
952
|
+
createDbkWorkspaceSnapshot,
|
|
953
|
+
replaceMemoryWorkspaceFromDbk,
|
|
954
|
+
FileSystemContentContainer,
|
|
955
|
+
createFileMediaProvider
|
|
956
|
+
};
|
|
957
|
+
//# sourceMappingURL=chunk-5MYAICRE.js.map
|