shell-dsl 0.0.33 → 0.0.35

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.
Files changed (76) hide show
  1. package/README.md +37 -3
  2. package/dist/cjs/package.json +1 -1
  3. package/dist/cjs/src/fs/memfs-adapter.cjs +56 -2
  4. package/dist/cjs/src/fs/memfs-adapter.cjs.map +3 -3
  5. package/dist/cjs/src/fs/real-fs.cjs +134 -3
  6. package/dist/cjs/src/fs/real-fs.cjs.map +3 -3
  7. package/dist/cjs/src/fs/special-files.cjs +3 -2
  8. package/dist/cjs/src/fs/special-files.cjs.map +3 -3
  9. package/dist/cjs/src/fs/web-fs.cjs +72 -3
  10. package/dist/cjs/src/fs/web-fs.cjs.map +3 -3
  11. package/dist/cjs/src/index.cjs.map +2 -2
  12. package/dist/cjs/src/types.cjs.map +2 -2
  13. package/dist/cjs/src/vcs/content.cjs +106 -0
  14. package/dist/cjs/src/vcs/content.cjs.map +10 -0
  15. package/dist/cjs/src/vcs/diff.cjs +94 -45
  16. package/dist/cjs/src/vcs/diff.cjs.map +3 -3
  17. package/dist/cjs/src/vcs/index.cjs.map +1 -1
  18. package/dist/cjs/src/vcs/objects.cjs +141 -0
  19. package/dist/cjs/src/vcs/objects.cjs.map +10 -0
  20. package/dist/cjs/src/vcs/rules.cjs +183 -0
  21. package/dist/cjs/src/vcs/rules.cjs.map +10 -0
  22. package/dist/cjs/src/vcs/snapshot.cjs +214 -48
  23. package/dist/cjs/src/vcs/snapshot.cjs.map +3 -3
  24. package/dist/cjs/src/vcs/storage.cjs +44 -3
  25. package/dist/cjs/src/vcs/storage.cjs.map +3 -3
  26. package/dist/cjs/src/vcs/text-diff.cjs +219 -0
  27. package/dist/cjs/src/vcs/text-diff.cjs.map +10 -0
  28. package/dist/cjs/src/vcs/vcs.cjs +138 -74
  29. package/dist/cjs/src/vcs/vcs.cjs.map +3 -3
  30. package/dist/cjs/src/vcs/walk.cjs +24 -18
  31. package/dist/cjs/src/vcs/walk.cjs.map +3 -3
  32. package/dist/mjs/package.json +1 -1
  33. package/dist/mjs/src/fs/memfs-adapter.mjs +57 -2
  34. package/dist/mjs/src/fs/memfs-adapter.mjs.map +3 -3
  35. package/dist/mjs/src/fs/real-fs.mjs +135 -3
  36. package/dist/mjs/src/fs/real-fs.mjs.map +3 -3
  37. package/dist/mjs/src/fs/special-files.mjs +3 -2
  38. package/dist/mjs/src/fs/special-files.mjs.map +3 -3
  39. package/dist/mjs/src/fs/web-fs.mjs +72 -3
  40. package/dist/mjs/src/fs/web-fs.mjs.map +3 -3
  41. package/dist/mjs/src/index.mjs.map +2 -2
  42. package/dist/mjs/src/types.mjs.map +2 -2
  43. package/dist/mjs/src/vcs/content.mjs +66 -0
  44. package/dist/mjs/src/vcs/content.mjs.map +10 -0
  45. package/dist/mjs/src/vcs/diff.mjs +94 -45
  46. package/dist/mjs/src/vcs/diff.mjs.map +3 -3
  47. package/dist/mjs/src/vcs/index.mjs.map +1 -1
  48. package/dist/mjs/src/vcs/objects.mjs +106 -0
  49. package/dist/mjs/src/vcs/objects.mjs.map +10 -0
  50. package/dist/mjs/src/vcs/rules.mjs +143 -0
  51. package/dist/mjs/src/vcs/rules.mjs.map +10 -0
  52. package/dist/mjs/src/vcs/snapshot.mjs +215 -49
  53. package/dist/mjs/src/vcs/snapshot.mjs.map +3 -3
  54. package/dist/mjs/src/vcs/storage.mjs +45 -3
  55. package/dist/mjs/src/vcs/storage.mjs.map +3 -3
  56. package/dist/mjs/src/vcs/text-diff.mjs +179 -0
  57. package/dist/mjs/src/vcs/text-diff.mjs.map +10 -0
  58. package/dist/mjs/src/vcs/vcs.mjs +145 -76
  59. package/dist/mjs/src/vcs/vcs.mjs.map +3 -3
  60. package/dist/mjs/src/vcs/walk.mjs +24 -18
  61. package/dist/mjs/src/vcs/walk.mjs.map +3 -3
  62. package/dist/types/src/fs/real-fs.d.ts +12 -1
  63. package/dist/types/src/index.d.ts +2 -2
  64. package/dist/types/src/types.d.ts +10 -0
  65. package/dist/types/src/vcs/content.d.ts +6 -0
  66. package/dist/types/src/vcs/diff.d.ts +11 -7
  67. package/dist/types/src/vcs/index.d.ts +1 -1
  68. package/dist/types/src/vcs/objects.d.ts +22 -0
  69. package/dist/types/src/vcs/rules.d.ts +23 -0
  70. package/dist/types/src/vcs/snapshot.d.ts +18 -7
  71. package/dist/types/src/vcs/storage.d.ts +7 -1
  72. package/dist/types/src/vcs/text-diff.d.ts +1 -0
  73. package/dist/types/src/vcs/types.d.ts +44 -4
  74. package/dist/types/src/vcs/vcs.d.ts +8 -2
  75. package/dist/types/src/vcs/walk.d.ts +15 -3
  76. package/package.json +7 -2
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/vcs/text-diff.ts"],
4
+ "sourcesContent": [
5
+ "interface DiffOp {\n type: \"equal\" | \"insert\" | \"delete\";\n line: string;\n}\n\ninterface HunkRange {\n start: number;\n end: number;\n}\n\nconst CONTEXT_LINES = 3;\n\nexport function createUnifiedPatch(path: string, previousText: string, nextText: string): string | undefined {\n if (previousText === nextText) {\n return undefined;\n }\n\n const previousLines = splitLines(previousText);\n const nextLines = splitLines(nextText);\n const operations = diffLines(previousLines, nextLines);\n const hunks = collectHunks(operations);\n\n if (hunks.length === 0) {\n return undefined;\n }\n\n const lines = [`--- a/${path}`, `+++ b/${path}`];\n for (const hunk of hunks) {\n lines.push(formatHunkHeader(operations, hunk));\n for (let i = hunk.start; i < hunk.end; i++) {\n const operation = operations[i]!;\n const prefix =\n operation.type === \"equal\" ? \" \" : operation.type === \"delete\" ? \"-\" : \"+\";\n lines.push(prefix + operation.line);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction splitLines(text: string): string[] {\n if (!text) {\n return [];\n }\n const normalized = text.replace(/\\r\\n/g, \"\\n\");\n const parts = normalized.split(\"\\n\");\n if (normalized.endsWith(\"\\n\")) {\n parts.pop();\n }\n return parts;\n}\n\nfunction diffLines(previousLines: string[], nextLines: string[]): DiffOp[] {\n const previousCount = previousLines.length;\n const nextCount = nextLines.length;\n const max = previousCount + nextCount;\n const trace: Map<number, number>[] = [];\n let frontier = new Map<number, number>();\n frontier.set(1, 0);\n\n for (let depth = 0; depth <= max; depth++) {\n trace.push(new Map(frontier));\n\n for (let diagonal = -depth; diagonal <= depth; diagonal += 2) {\n const nextX = frontier.get(diagonal + 1) ?? 0;\n const prevX = frontier.get(diagonal - 1) ?? 0;\n let x: number;\n\n if (diagonal === -depth || (diagonal !== depth && prevX < nextX)) {\n x = nextX;\n } else {\n x = prevX + 1;\n }\n\n let y = x - diagonal;\n while (\n x < previousCount &&\n y < nextCount &&\n previousLines[x] === nextLines[y]\n ) {\n x++;\n y++;\n }\n\n frontier.set(diagonal, x);\n\n if (x >= previousCount && y >= nextCount) {\n return backtrack(trace, previousLines, nextLines);\n }\n }\n }\n\n return [];\n}\n\nfunction backtrack(\n trace: Map<number, number>[],\n previousLines: string[],\n nextLines: string[],\n): DiffOp[] {\n const operations: DiffOp[] = [];\n let x = previousLines.length;\n let y = nextLines.length;\n\n for (let depth = trace.length - 1; depth >= 0; depth--) {\n const frontier = trace[depth]!;\n const diagonal = x - y;\n\n let previousDiagonal: number;\n if (\n diagonal === -depth ||\n (diagonal !== depth && (frontier.get(diagonal - 1) ?? 0) < (frontier.get(diagonal + 1) ?? 0))\n ) {\n previousDiagonal = diagonal + 1;\n } else {\n previousDiagonal = diagonal - 1;\n }\n\n const previousX = frontier.get(previousDiagonal) ?? 0;\n const previousY = previousX - previousDiagonal;\n\n while (x > previousX && y > previousY) {\n operations.push({ type: \"equal\", line: previousLines[x - 1]! });\n x--;\n y--;\n }\n\n if (depth === 0) {\n break;\n }\n\n if (x === previousX) {\n operations.push({ type: \"insert\", line: nextLines[y - 1]! });\n y--;\n } else {\n operations.push({ type: \"delete\", line: previousLines[x - 1]! });\n x--;\n }\n }\n\n while (x > 0 && y > 0) {\n operations.push({ type: \"equal\", line: previousLines[x - 1]! });\n x--;\n y--;\n }\n while (x > 0) {\n operations.push({ type: \"delete\", line: previousLines[x - 1]! });\n x--;\n }\n while (y > 0) {\n operations.push({ type: \"insert\", line: nextLines[y - 1]! });\n y--;\n }\n\n return operations.reverse();\n}\n\nfunction collectHunks(operations: DiffOp[]): HunkRange[] {\n const changeRanges: HunkRange[] = [];\n let rangeStart: number | null = null;\n\n for (let index = 0; index < operations.length; index++) {\n if (operations[index]!.type === \"equal\") {\n continue;\n }\n if (rangeStart === null) {\n rangeStart = Math.max(0, index - CONTEXT_LINES);\n }\n let rangeEnd = Math.min(operations.length, index + CONTEXT_LINES + 1);\n\n while (\n rangeEnd < operations.length &&\n operations.slice(index + 1, rangeEnd).some((op) => op.type !== \"equal\")\n ) {\n rangeEnd = Math.min(operations.length, rangeEnd + CONTEXT_LINES);\n }\n\n const previousRange = changeRanges[changeRanges.length - 1];\n if (previousRange && rangeStart <= previousRange.end) {\n previousRange.end = Math.max(previousRange.end, rangeEnd);\n } else {\n changeRanges.push({ start: rangeStart, end: rangeEnd });\n }\n rangeStart = null;\n }\n\n return changeRanges;\n}\n\nfunction formatHunkHeader(operations: DiffOp[], hunk: HunkRange): string {\n let oldStart = 1;\n let newStart = 1;\n\n for (let index = 0; index < hunk.start; index++) {\n const operation = operations[index]!;\n if (operation.type !== \"insert\") {\n oldStart++;\n }\n if (operation.type !== \"delete\") {\n newStart++;\n }\n }\n\n let oldCount = 0;\n let newCount = 0;\n for (let index = hunk.start; index < hunk.end; index++) {\n const operation = operations[index]!;\n if (operation.type !== \"insert\") {\n oldCount++;\n }\n if (operation.type !== \"delete\") {\n newCount++;\n }\n }\n\n return `@@ -${formatRange(oldStart, oldCount)} +${formatRange(newStart, newCount)} @@`;\n}\n\nfunction formatRange(start: number, count: number): string {\n if (count === 0) {\n return `${start - 1},0`;\n }\n if (count === 1) {\n return String(start);\n }\n return `${start},${count}`;\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "9428FE9E0FC0D7BB64756E2164756E21",
9
+ "names": []
10
+ }
@@ -1,41 +1,50 @@
1
1
  // src/vcs/vcs.ts
2
2
  import { VCSStorage } from "./storage.mjs";
3
- import { diffManifests, diffWorkingTree } from "./diff.mjs";
4
- import { buildTreeManifest, restoreTree } from "./snapshot.mjs";
5
- import { matchGlobPath } from "./match.mjs";
3
+ import { diffManifests } from "./diff.mjs";
4
+ import { VCSObjectStore } from "./objects.mjs";
5
+ import { matchVCSPath, VCSRules } from "./rules.mjs";
6
+ import {
7
+ buildTreeManifest,
8
+ rebuildIndexForManifest,
9
+ restoreTree,
10
+ updateIndexForScopedPaths
11
+ } from "./snapshot.mjs";
6
12
 
7
13
  class VersionControlSystem {
8
14
  workFs;
9
15
  workPath;
10
16
  storage;
11
- vcsDirName;
17
+ objectStore;
18
+ vcsInternalPath;
19
+ rules;
12
20
  constructor(config) {
13
21
  this.workFs = config.fs;
14
22
  this.workPath = config.fs.resolve(config.path);
15
23
  const metaFs = config.vcsPath?.fs ?? config.fs;
16
24
  const metaPath = config.vcsPath?.path ?? metaFs.resolve(config.path, ".vcs");
17
25
  this.storage = new VCSStorage(metaFs, metaPath);
18
- const resolvedMeta = metaFs.resolve(metaPath);
19
- const resolvedWork = this.workPath;
20
- if (resolvedMeta.startsWith(resolvedWork + "/") || resolvedMeta.startsWith(resolvedWork + "\\")) {
21
- const rel = resolvedMeta.slice(resolvedWork.length + 1);
22
- this.vcsDirName = rel.split("/")[0] ?? ".vcs";
23
- } else {
24
- this.vcsDirName = "";
25
- }
26
- }
27
- get excludeDirs() {
28
- return this.vcsDirName ? [this.vcsDirName] : [];
26
+ this.objectStore = new VCSObjectStore(this.storage.fileSystem, this.storage.resolve());
27
+ this.vcsInternalPath = resolveInternalPath(config.fs, metaFs, this.workPath, metaPath);
28
+ this.rules = new VCSRules({
29
+ internalPath: this.vcsInternalPath,
30
+ ignore: config.ignore,
31
+ attributes: config.attributes
32
+ });
29
33
  }
30
34
  async init() {
31
- if (await this.storage.isInitialized())
35
+ if (await this.storage.isInitialized()) {
36
+ await this.storage.assertSupportedFormat();
32
37
  return;
38
+ }
33
39
  await this.storage.initialize();
40
+ await this.objectStore.initialize();
34
41
  }
35
42
  async ensureInit() {
36
43
  if (!await this.storage.isInitialized()) {
37
44
  await this.init();
45
+ return;
38
46
  }
47
+ await this.storage.assertSupportedFormat();
39
48
  }
40
49
  async resolveHead() {
41
50
  const head = await this.storage.readHead();
@@ -56,38 +65,57 @@ class VersionControlSystem {
56
65
  const rev = await this.storage.readRevision(revision);
57
66
  return rev.tree;
58
67
  }
68
+ async currentIndex() {
69
+ return this.storage.readIndex();
70
+ }
59
71
  async commit(message, opts) {
60
72
  await this.ensureInit();
61
73
  const { branch, revision: parentId } = await this.resolveHead();
62
74
  const parentManifest = parentId !== null ? (await this.storage.readRevision(parentId)).tree : {};
75
+ const previousIndex = await this.currentIndex();
76
+ const working = await buildTreeManifest(this.workFs, this.workPath, {
77
+ objectStore: this.objectStore,
78
+ rules: this.rules,
79
+ trackedPaths: Object.keys(parentManifest),
80
+ indexEntries: previousIndex
81
+ });
63
82
  let newTree;
64
83
  let changes;
65
84
  if (opts?.paths && opts.paths.length > 0) {
66
- const fullManifest = await buildTreeManifest(this.workFs, this.workPath, this.excludeDirs);
67
- const matchedPaths = filterPathsByGlobs(Object.keys(fullManifest), opts.paths);
68
- newTree = { ...parentManifest };
85
+ const matchedPaths = filterPathsByGlobs(Object.keys(working.manifest), opts.paths);
69
86
  const parentMatchedPaths = filterPathsByGlobs(Object.keys(parentManifest), opts.paths);
70
- for (const p of parentMatchedPaths) {
71
- if (!fullManifest[p]) {
72
- delete newTree[p];
87
+ newTree = { ...parentManifest };
88
+ for (const path of parentMatchedPaths) {
89
+ if (!working.manifest[path]) {
90
+ delete newTree[path];
73
91
  }
74
92
  }
75
- for (const p of matchedPaths) {
76
- newTree[p] = fullManifest[p];
93
+ for (const path of matchedPaths) {
94
+ newTree[path] = working.manifest[path];
77
95
  }
78
96
  const relevantBefore = {};
79
97
  const relevantAfter = {};
80
98
  const allRelevant = new Set([...matchedPaths, ...parentMatchedPaths]);
81
- for (const p of allRelevant) {
82
- if (parentManifest[p])
83
- relevantBefore[p] = parentManifest[p];
84
- if (newTree[p])
85
- relevantAfter[p] = newTree[p];
99
+ for (const path of allRelevant) {
100
+ if (parentManifest[path])
101
+ relevantBefore[path] = parentManifest[path];
102
+ if (newTree[path])
103
+ relevantAfter[path] = newTree[path];
86
104
  }
87
- changes = diffManifests(relevantBefore, relevantAfter);
105
+ changes = await diffManifests(relevantBefore, relevantAfter, {
106
+ rules: this.rules,
107
+ objectStore: this.objectStore,
108
+ beforeIndex: previousIndex,
109
+ afterIndex: working.indexEntries
110
+ });
88
111
  } else {
89
- newTree = await buildTreeManifest(this.workFs, this.workPath, this.excludeDirs);
90
- changes = diffManifests(parentManifest, newTree);
112
+ newTree = working.manifest;
113
+ changes = await diffManifests(parentManifest, newTree, {
114
+ rules: this.rules,
115
+ objectStore: this.objectStore,
116
+ beforeIndex: previousIndex,
117
+ afterIndex: working.indexEntries
118
+ });
91
119
  }
92
120
  if (changes.length === 0) {
93
121
  throw new Error("nothing to commit");
@@ -103,6 +131,7 @@ class VersionControlSystem {
103
131
  tree: newTree
104
132
  };
105
133
  await this.storage.writeRevision(rev);
134
+ await this.storage.writeIndex(working.indexEntries);
106
135
  if (branch) {
107
136
  await this.storage.writeBranch(branch, { revision: id });
108
137
  } else {
@@ -132,30 +161,35 @@ class VersionControlSystem {
132
161
  } catch {
133
162
  throw new Error(`revision ${targetRevision} not found`);
134
163
  }
164
+ const currentManifest = await this.headManifest();
135
165
  if (isPartial) {
136
- const matchedPaths = filterPathsByGlobs(Object.keys(rev.tree), opts.paths);
137
- const filteredManifest = {};
138
- for (const p of matchedPaths) {
139
- filteredManifest[p] = rev.tree[p];
140
- }
141
- await restoreTree(this.workFs, this.workPath, filteredManifest, {
166
+ await restoreTree(this.workFs, this.workPath, rev.tree, this.objectStore, {
142
167
  fullRestore: false,
143
- paths: matchedPaths
168
+ paths: opts.paths,
169
+ rules: this.rules,
170
+ trackedPaths: Object.keys(currentManifest)
144
171
  });
145
- } else {
146
- if (!opts?.force) {
147
- const changes = await this.status();
148
- if (changes.length > 0) {
149
- throw new Error("working tree has uncommitted changes (use force to discard)");
150
- }
151
- }
152
- await restoreTree(this.workFs, this.workPath, rev.tree, { fullRestore: true });
153
- if (targetBranch) {
154
- await this.storage.writeHead({ ref: `refs/heads/${targetBranch}` });
155
- } else {
156
- await this.storage.writeHead({ revision: targetRevision });
172
+ const updatedIndex = await updateIndexForScopedPaths(this.workFs, this.workPath, rev.tree, this.objectStore, await this.currentIndex(), opts.paths);
173
+ await this.storage.writeIndex(updatedIndex);
174
+ return;
175
+ }
176
+ if (!opts?.force) {
177
+ const changes = await this.status();
178
+ if (changes.length > 0) {
179
+ throw new Error("working tree has uncommitted changes (use force to discard)");
157
180
  }
158
181
  }
182
+ await restoreTree(this.workFs, this.workPath, rev.tree, this.objectStore, {
183
+ fullRestore: true,
184
+ rules: this.rules,
185
+ trackedPaths: Object.keys(currentManifest)
186
+ });
187
+ await this.storage.writeIndex(await rebuildIndexForManifest(this.workFs, this.workPath, rev.tree, this.objectStore));
188
+ if (targetBranch) {
189
+ await this.storage.writeHead({ ref: `refs/heads/${targetBranch}` });
190
+ } else {
191
+ await this.storage.writeHead({ revision: targetRevision });
192
+ }
159
193
  }
160
194
  async branch(name) {
161
195
  await this.ensureInit();
@@ -211,29 +245,22 @@ class VersionControlSystem {
211
245
  } catch {
212
246
  break;
213
247
  }
214
- const changedPaths = rev.changes.map((c) => c.path);
248
+ const changedPaths = rev.changes.map((change) => change.path);
215
249
  if (opts?.path) {
216
- const matchesPath = changedPaths.some((p) => matchGlobPath(opts.path, p));
217
- if (matchesPath) {
218
- entries.push({
219
- id: rev.id,
220
- parent: rev.parent,
221
- branch: rev.branch,
222
- message: rev.message,
223
- timestamp: rev.timestamp,
224
- paths: changedPaths
225
- });
250
+ const matchesPath = changedPaths.some((path) => matchVCSPath(opts.path, path));
251
+ if (!matchesPath) {
252
+ currentId = rev.parent;
253
+ continue;
226
254
  }
227
- } else {
228
- entries.push({
229
- id: rev.id,
230
- parent: rev.parent,
231
- branch: rev.branch,
232
- message: rev.message,
233
- timestamp: rev.timestamp,
234
- paths: changedPaths
235
- });
236
255
  }
256
+ entries.push({
257
+ id: rev.id,
258
+ parent: rev.parent,
259
+ branch: rev.branch,
260
+ message: rev.message,
261
+ timestamp: rev.timestamp,
262
+ paths: changedPaths
263
+ });
237
264
  currentId = rev.parent;
238
265
  }
239
266
  return entries;
@@ -241,13 +268,42 @@ class VersionControlSystem {
241
268
  async status() {
242
269
  await this.ensureInit();
243
270
  const manifest = await this.headManifest();
244
- return diffWorkingTree(this.workFs, this.workPath, manifest, this.excludeDirs);
271
+ const previousIndex = await this.currentIndex();
272
+ const working = await buildTreeManifest(this.workFs, this.workPath, {
273
+ objectStore: this.objectStore,
274
+ rules: this.rules,
275
+ trackedPaths: Object.keys(manifest),
276
+ indexEntries: previousIndex
277
+ });
278
+ await this.storage.writeIndex(working.indexEntries);
279
+ return diffManifests(manifest, working.manifest, {
280
+ rules: this.rules,
281
+ objectStore: this.objectStore,
282
+ beforeIndex: previousIndex,
283
+ afterIndex: working.indexEntries
284
+ });
245
285
  }
246
286
  async diff(revA, revB) {
247
287
  await this.ensureInit();
248
288
  const a = await this.storage.readRevision(revA);
249
289
  const b = await this.storage.readRevision(revB);
250
- return diffManifests(a.tree, b.tree);
290
+ return diffManifests(a.tree, b.tree, {
291
+ rules: this.rules,
292
+ objectStore: this.objectStore
293
+ });
294
+ }
295
+ async readBlob(blobId, encoding) {
296
+ const content = await this.objectStore.readBlob(blobId);
297
+ return encoding ? content.toString(encoding) : content;
298
+ }
299
+ async readRevisionFile(revisionId, path, encoding) {
300
+ const revision = await this.storage.readRevision(revisionId);
301
+ const normalizedPath = path.replace(/^\/+/, "");
302
+ const entry = revision.tree[normalizedPath];
303
+ if (!entry || entry.kind === "directory") {
304
+ throw new Error(`file "${path}" not found in revision ${revisionId}`);
305
+ }
306
+ return this.readBlob(entry.blobId, encoding);
251
307
  }
252
308
  async head() {
253
309
  await this.ensureInit();
@@ -255,11 +311,24 @@ class VersionControlSystem {
255
311
  }
256
312
  }
257
313
  function filterPathsByGlobs(paths, patterns) {
258
- const normalizedPatterns = patterns.map((p) => p.startsWith("/") ? p.slice(1) : p);
259
- return paths.filter((filePath) => normalizedPatterns.some((pattern) => matchGlobPath(pattern, filePath)));
314
+ return paths.filter((filePath) => patterns.some((pattern) => matchVCSPath(pattern, filePath)));
315
+ }
316
+ function resolveInternalPath(workFs, metaFs, workPath, metaPath) {
317
+ if (workFs !== metaFs)
318
+ return "";
319
+ const normalizedWork = normalizeFsPath(workPath);
320
+ const normalizedMeta = normalizeFsPath(metaFs.resolve(metaPath));
321
+ if (normalizedMeta === normalizedWork)
322
+ return "";
323
+ if (!normalizedMeta.startsWith(`${normalizedWork}/`))
324
+ return "";
325
+ return normalizedMeta.slice(normalizedWork.length + 1);
326
+ }
327
+ function normalizeFsPath(path) {
328
+ return path.replace(/\\/g, "/").replace(/\/+$/, "");
260
329
  }
261
330
  export {
262
331
  VersionControlSystem
263
332
  };
264
333
 
265
- //# debugId=09DEFDF82D3AF06864756E2164756E21
334
+ //# debugId=CA82B0F6ED31CC1264756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/vcs/vcs.ts"],
4
4
  "sourcesContent": [
5
- "import type { VirtualFS } from \"../types.mjs\";\nimport type {\n VCSConfig,\n Revision,\n DiffEntry,\n TreeManifest,\n CommitOptions,\n CheckoutOptions,\n LogOptions,\n LogEntry,\n BranchInfo,\n} from \"./types.mjs\";\nimport { VCSStorage } from \"./storage.mjs\";\nimport { diffManifests, diffWorkingTree } from \"./diff.mjs\";\nimport { buildTreeManifest, restoreTree } from \"./snapshot.mjs\";\nimport { matchGlobPath } from \"./match.mjs\";\n\nexport class VersionControlSystem {\n private readonly workFs: VirtualFS;\n private readonly workPath: string;\n private readonly storage: VCSStorage;\n private readonly vcsDirName: string;\n\n constructor(config: VCSConfig) {\n this.workFs = config.fs;\n this.workPath = config.fs.resolve(config.path);\n\n const metaFs = config.vcsPath?.fs ?? config.fs;\n const metaPath = config.vcsPath?.path ?? metaFs.resolve(config.path, \".vcs\");\n this.storage = new VCSStorage(metaFs, metaPath);\n\n // Determine the vcs directory name relative to workPath for exclusion\n const resolvedMeta = metaFs.resolve(metaPath);\n const resolvedWork = this.workPath;\n if (resolvedMeta.startsWith(resolvedWork + \"/\") || resolvedMeta.startsWith(resolvedWork + \"\\\\\")) {\n const rel = resolvedMeta.slice(resolvedWork.length + 1);\n this.vcsDirName = rel.split(\"/\")[0] ?? \".vcs\";\n } else {\n // VCS dir is outside the work tree, no exclusion needed\n this.vcsDirName = \"\";\n }\n }\n\n private get excludeDirs(): string[] {\n return this.vcsDirName ? [this.vcsDirName] : [];\n }\n\n /** Initialize the .vcs directory. Called automatically on first operation if needed. */\n async init(): Promise<void> {\n if (await this.storage.isInitialized()) return;\n await this.storage.initialize();\n }\n\n private async ensureInit(): Promise<void> {\n if (!(await this.storage.isInitialized())) {\n await this.init();\n }\n }\n\n /** Get the current HEAD revision number, or null if no commits yet. */\n private async resolveHead(): Promise<{ branch: string | null; revision: number | null }> {\n const head = await this.storage.readHead();\n if (head.revision !== undefined) {\n return { branch: null, revision: head.revision };\n }\n if (head.ref) {\n const branchName = head.ref.replace(\"refs/heads/\", \"\");\n const branchRef = await this.storage.readBranch(branchName);\n return { branch: branchName, revision: branchRef?.revision ?? null };\n }\n return { branch: null, revision: null };\n }\n\n /** Get current HEAD manifest, or empty if no commits. */\n private async headManifest(): Promise<TreeManifest> {\n const { revision } = await this.resolveHead();\n if (revision === null) return {};\n const rev = await this.storage.readRevision(revision);\n return rev.tree;\n }\n\n /** Commit all pending changes, or selective changes if paths are provided. */\n async commit(message: string, opts?: CommitOptions): Promise<Revision> {\n await this.ensureInit();\n\n const { branch, revision: parentId } = await this.resolveHead();\n const parentManifest = parentId !== null\n ? (await this.storage.readRevision(parentId)).tree\n : {};\n\n let newTree: TreeManifest;\n let changes: DiffEntry[];\n\n if (opts?.paths && opts.paths.length > 0) {\n // Selective commit: only include matching files\n const fullManifest = await buildTreeManifest(this.workFs, this.workPath, this.excludeDirs);\n const matchedPaths = filterPathsByGlobs(Object.keys(fullManifest), opts.paths);\n\n // Start with parent manifest, overlay matched files from working tree\n newTree = { ...parentManifest };\n\n // Also check for deletions: files in parent that match patterns but are gone from working tree\n const parentMatchedPaths = filterPathsByGlobs(Object.keys(parentManifest), opts.paths);\n for (const p of parentMatchedPaths) {\n if (!fullManifest[p]) {\n delete newTree[p]; // file was deleted\n }\n }\n\n for (const p of matchedPaths) {\n newTree[p] = fullManifest[p]!;\n }\n\n // Compute changes only for matched paths\n const relevantBefore: TreeManifest = {};\n const relevantAfter: TreeManifest = {};\n const allRelevant = new Set([...matchedPaths, ...parentMatchedPaths]);\n for (const p of allRelevant) {\n if (parentManifest[p]) relevantBefore[p] = parentManifest[p]!;\n if (newTree[p]) relevantAfter[p] = newTree[p]!;\n }\n changes = diffManifests(relevantBefore, relevantAfter);\n } else {\n // Full commit\n newTree = await buildTreeManifest(this.workFs, this.workPath, this.excludeDirs);\n changes = diffManifests(parentManifest, newTree);\n }\n\n if (changes.length === 0) {\n throw new Error(\"nothing to commit\");\n }\n\n const id = await this.storage.nextRevisionId();\n const rev: Revision = {\n id,\n parent: parentId,\n branch: branch ?? \"detached\",\n message,\n timestamp: new Date().toISOString(),\n changes,\n tree: newTree,\n };\n\n await this.storage.writeRevision(rev);\n\n // Update branch ref or HEAD\n if (branch) {\n await this.storage.writeBranch(branch, { revision: id });\n } else {\n await this.storage.writeHead({ revision: id });\n }\n\n return rev;\n }\n\n /** Checkout a revision number or branch name. */\n async checkout(target: string | number, opts?: CheckoutOptions): Promise<void> {\n await this.ensureInit();\n\n const isPartial = opts?.paths && opts.paths.length > 0;\n\n let targetRevision: number;\n let targetBranch: string | null = null;\n\n if (typeof target === \"string\") {\n // Check if it's a branch name\n const branchRef = await this.storage.readBranch(target);\n if (branchRef) {\n targetBranch = target;\n targetRevision = branchRef.revision;\n } else {\n throw new Error(`unknown branch or revision: \"${target}\"`);\n }\n } else {\n targetRevision = target;\n }\n\n // Verify revision exists\n let rev: Revision;\n try {\n rev = await this.storage.readRevision(targetRevision);\n } catch {\n throw new Error(`revision ${targetRevision} not found`);\n }\n\n if (isPartial) {\n // Partial checkout: restore specific files, don't update HEAD\n const matchedPaths = filterPathsByGlobs(Object.keys(rev.tree), opts!.paths!);\n const filteredManifest: TreeManifest = {};\n for (const p of matchedPaths) {\n filteredManifest[p] = rev.tree[p]!;\n }\n await restoreTree(this.workFs, this.workPath, filteredManifest, {\n fullRestore: false,\n paths: matchedPaths,\n });\n } else {\n // Full checkout\n if (!opts?.force) {\n const changes = await this.status();\n if (changes.length > 0) {\n throw new Error(\"working tree has uncommitted changes (use force to discard)\");\n }\n }\n\n await restoreTree(this.workFs, this.workPath, rev.tree, { fullRestore: true });\n\n // Update HEAD\n if (targetBranch) {\n await this.storage.writeHead({ ref: `refs/heads/${targetBranch}` });\n } else {\n await this.storage.writeHead({ revision: targetRevision });\n }\n }\n }\n\n /** Create a new branch at HEAD. */\n async branch(name: string): Promise<void> {\n await this.ensureInit();\n\n const existing = await this.storage.readBranch(name);\n if (existing) {\n throw new Error(`branch \"${name}\" already exists`);\n }\n\n const { revision } = await this.resolveHead();\n if (revision === null) {\n throw new Error(\"cannot create branch: no commits yet\");\n }\n\n await this.storage.writeBranch(name, { revision });\n }\n\n /** List all branches. */\n async branches(): Promise<BranchInfo[]> {\n await this.ensureInit();\n\n const names = await this.storage.listBranches();\n const head = await this.resolveHead();\n const result: BranchInfo[] = [];\n\n for (const name of names) {\n const ref = await this.storage.readBranch(name);\n if (ref) {\n result.push({\n name,\n revision: ref.revision,\n current: head.branch === name,\n });\n }\n }\n\n return result.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n /** Get revision history. */\n async log(opts?: LogOptions): Promise<LogEntry[]> {\n await this.ensureInit();\n\n let startRevision: number | null;\n\n if (opts?.branch) {\n const branchRef = await this.storage.readBranch(opts.branch);\n if (!branchRef) throw new Error(`branch \"${opts.branch}\" not found`);\n startRevision = branchRef.revision;\n } else {\n const { revision } = await this.resolveHead();\n startRevision = revision;\n }\n\n if (startRevision === null) return [];\n\n const entries: LogEntry[] = [];\n let currentId: number | null = startRevision;\n\n while (currentId !== null) {\n if (opts?.limit && entries.length >= opts.limit) break;\n\n let rev: Revision;\n try {\n rev = await this.storage.readRevision(currentId);\n } catch {\n break;\n }\n\n const changedPaths = rev.changes.map((c) => c.path);\n\n if (opts?.path) {\n // Filter: only include if this revision touches the specified path\n const matchesPath = changedPaths.some((p) => matchGlobPath(opts.path!, p));\n if (matchesPath) {\n entries.push({\n id: rev.id,\n parent: rev.parent,\n branch: rev.branch,\n message: rev.message,\n timestamp: rev.timestamp,\n paths: changedPaths,\n });\n }\n } else {\n entries.push({\n id: rev.id,\n parent: rev.parent,\n branch: rev.branch,\n message: rev.message,\n timestamp: rev.timestamp,\n paths: changedPaths,\n });\n }\n\n currentId = rev.parent;\n }\n\n return entries;\n }\n\n /** Get uncommitted changes as DiffEntry[]. */\n async status(): Promise<DiffEntry[]> {\n await this.ensureInit();\n const manifest = await this.headManifest();\n return diffWorkingTree(this.workFs, this.workPath, manifest, this.excludeDirs);\n }\n\n /** Diff between two revisions. */\n async diff(revA: number, revB: number): Promise<DiffEntry[]> {\n await this.ensureInit();\n const a = await this.storage.readRevision(revA);\n const b = await this.storage.readRevision(revB);\n return diffManifests(a.tree, b.tree);\n }\n\n /** Get current HEAD info. */\n async head(): Promise<{ branch: string | null; revision: number | null }> {\n await this.ensureInit();\n return this.resolveHead();\n }\n}\n\n/**\n * Filter a list of paths to only those matching any of the given glob patterns.\n * Patterns may start with `/` which is stripped before matching.\n */\nfunction filterPathsByGlobs(paths: string[], patterns: string[]): string[] {\n const normalizedPatterns = patterns.map((p) => (p.startsWith(\"/\") ? p.slice(1) : p));\n return paths.filter((filePath) =>\n normalizedPatterns.some((pattern) => matchGlobPath(pattern, filePath)),\n );\n}\n"
5
+ "import type { VirtualFS } from \"../types.mjs\";\nimport type {\n VCSConfig,\n Revision,\n DiffEntry,\n TreeManifest,\n CommitOptions,\n CheckoutOptions,\n LogOptions,\n LogEntry,\n BranchInfo,\n VCSIndexEntry,\n} from \"./types.mjs\";\nimport { VCSStorage } from \"./storage.mjs\";\nimport { diffManifests } from \"./diff.mjs\";\nimport { VCSObjectStore } from \"./objects.mjs\";\nimport { matchVCSPath, VCSRules } from \"./rules.mjs\";\nimport {\n buildTreeManifest,\n rebuildIndexForManifest,\n restoreTree,\n updateIndexForScopedPaths,\n} from \"./snapshot.mjs\";\n\nexport class VersionControlSystem {\n private readonly workFs: VirtualFS;\n private readonly workPath: string;\n private readonly storage: VCSStorage;\n private readonly objectStore: VCSObjectStore;\n private readonly vcsInternalPath: string;\n private readonly rules: VCSRules;\n\n constructor(config: VCSConfig) {\n this.workFs = config.fs;\n this.workPath = config.fs.resolve(config.path);\n\n const metaFs = config.vcsPath?.fs ?? config.fs;\n const metaPath = config.vcsPath?.path ?? metaFs.resolve(config.path, \".vcs\");\n this.storage = new VCSStorage(metaFs, metaPath);\n this.objectStore = new VCSObjectStore(this.storage.fileSystem, this.storage.resolve());\n\n this.vcsInternalPath = resolveInternalPath(config.fs, metaFs, this.workPath, metaPath);\n\n this.rules = new VCSRules({\n internalPath: this.vcsInternalPath,\n ignore: config.ignore,\n attributes: config.attributes,\n });\n }\n\n /** Initialize the .vcs directory. Called automatically on first operation if needed. */\n async init(): Promise<void> {\n if (await this.storage.isInitialized()) {\n await this.storage.assertSupportedFormat();\n return;\n }\n await this.storage.initialize();\n await this.objectStore.initialize();\n }\n\n private async ensureInit(): Promise<void> {\n if (!(await this.storage.isInitialized())) {\n await this.init();\n return;\n }\n await this.storage.assertSupportedFormat();\n }\n\n /** Get the current HEAD revision number, or null if no commits yet. */\n private async resolveHead(): Promise<{ branch: string | null; revision: number | null }> {\n const head = await this.storage.readHead();\n if (head.revision !== undefined) {\n return { branch: null, revision: head.revision };\n }\n if (head.ref) {\n const branchName = head.ref.replace(\"refs/heads/\", \"\");\n const branchRef = await this.storage.readBranch(branchName);\n return { branch: branchName, revision: branchRef?.revision ?? null };\n }\n return { branch: null, revision: null };\n }\n\n /** Get current HEAD manifest, or empty if no commits. */\n private async headManifest(): Promise<TreeManifest> {\n const { revision } = await this.resolveHead();\n if (revision === null) return {};\n const rev = await this.storage.readRevision(revision);\n return rev.tree;\n }\n\n private async currentIndex(): Promise<Record<string, VCSIndexEntry>> {\n return this.storage.readIndex();\n }\n\n /** Commit all pending changes, or selective changes if paths are provided. */\n async commit(message: string, opts?: CommitOptions): Promise<Revision> {\n await this.ensureInit();\n\n const { branch, revision: parentId } = await this.resolveHead();\n const parentManifest = parentId !== null\n ? (await this.storage.readRevision(parentId)).tree\n : {};\n const previousIndex = await this.currentIndex();\n const working = await buildTreeManifest(this.workFs, this.workPath, {\n objectStore: this.objectStore,\n rules: this.rules,\n trackedPaths: Object.keys(parentManifest),\n indexEntries: previousIndex,\n });\n\n let newTree: TreeManifest;\n let changes: DiffEntry[];\n\n if (opts?.paths && opts.paths.length > 0) {\n const matchedPaths = filterPathsByGlobs(Object.keys(working.manifest), opts.paths);\n const parentMatchedPaths = filterPathsByGlobs(Object.keys(parentManifest), opts.paths);\n newTree = { ...parentManifest };\n\n for (const path of parentMatchedPaths) {\n if (!working.manifest[path]) {\n delete newTree[path];\n }\n }\n for (const path of matchedPaths) {\n newTree[path] = working.manifest[path]!;\n }\n\n const relevantBefore: TreeManifest = {};\n const relevantAfter: TreeManifest = {};\n const allRelevant = new Set([...matchedPaths, ...parentMatchedPaths]);\n for (const path of allRelevant) {\n if (parentManifest[path]) relevantBefore[path] = parentManifest[path]!;\n if (newTree[path]) relevantAfter[path] = newTree[path]!;\n }\n changes = await diffManifests(relevantBefore, relevantAfter, {\n rules: this.rules,\n objectStore: this.objectStore,\n beforeIndex: previousIndex,\n afterIndex: working.indexEntries,\n });\n } else {\n newTree = working.manifest;\n changes = await diffManifests(parentManifest, newTree, {\n rules: this.rules,\n objectStore: this.objectStore,\n beforeIndex: previousIndex,\n afterIndex: working.indexEntries,\n });\n }\n\n if (changes.length === 0) {\n throw new Error(\"nothing to commit\");\n }\n\n const id = await this.storage.nextRevisionId();\n const rev: Revision = {\n id,\n parent: parentId,\n branch: branch ?? \"detached\",\n message,\n timestamp: new Date().toISOString(),\n changes,\n tree: newTree,\n };\n\n await this.storage.writeRevision(rev);\n await this.storage.writeIndex(working.indexEntries);\n\n if (branch) {\n await this.storage.writeBranch(branch, { revision: id });\n } else {\n await this.storage.writeHead({ revision: id });\n }\n\n return rev;\n }\n\n /** Checkout a revision number or branch name. */\n async checkout(target: string | number, opts?: CheckoutOptions): Promise<void> {\n await this.ensureInit();\n\n const isPartial = opts?.paths && opts.paths.length > 0;\n\n let targetRevision: number;\n let targetBranch: string | null = null;\n\n if (typeof target === \"string\") {\n const branchRef = await this.storage.readBranch(target);\n if (branchRef) {\n targetBranch = target;\n targetRevision = branchRef.revision;\n } else {\n throw new Error(`unknown branch or revision: \"${target}\"`);\n }\n } else {\n targetRevision = target;\n }\n\n let rev: Revision;\n try {\n rev = await this.storage.readRevision(targetRevision);\n } catch {\n throw new Error(`revision ${targetRevision} not found`);\n }\n\n const currentManifest = await this.headManifest();\n\n if (isPartial) {\n await restoreTree(this.workFs, this.workPath, rev.tree, this.objectStore, {\n fullRestore: false,\n paths: opts!.paths!,\n rules: this.rules,\n trackedPaths: Object.keys(currentManifest),\n });\n\n const updatedIndex = await updateIndexForScopedPaths(\n this.workFs,\n this.workPath,\n rev.tree,\n this.objectStore,\n await this.currentIndex(),\n opts!.paths!,\n );\n await this.storage.writeIndex(updatedIndex);\n return;\n }\n\n if (!opts?.force) {\n const changes = await this.status();\n if (changes.length > 0) {\n throw new Error(\"working tree has uncommitted changes (use force to discard)\");\n }\n }\n\n await restoreTree(this.workFs, this.workPath, rev.tree, this.objectStore, {\n fullRestore: true,\n rules: this.rules,\n trackedPaths: Object.keys(currentManifest),\n });\n\n await this.storage.writeIndex(\n await rebuildIndexForManifest(this.workFs, this.workPath, rev.tree, this.objectStore),\n );\n\n if (targetBranch) {\n await this.storage.writeHead({ ref: `refs/heads/${targetBranch}` });\n } else {\n await this.storage.writeHead({ revision: targetRevision });\n }\n }\n\n /** Create a new branch at HEAD. */\n async branch(name: string): Promise<void> {\n await this.ensureInit();\n\n const existing = await this.storage.readBranch(name);\n if (existing) {\n throw new Error(`branch \"${name}\" already exists`);\n }\n\n const { revision } = await this.resolveHead();\n if (revision === null) {\n throw new Error(\"cannot create branch: no commits yet\");\n }\n\n await this.storage.writeBranch(name, { revision });\n }\n\n /** List all branches. */\n async branches(): Promise<BranchInfo[]> {\n await this.ensureInit();\n\n const names = await this.storage.listBranches();\n const head = await this.resolveHead();\n const result: BranchInfo[] = [];\n\n for (const name of names) {\n const ref = await this.storage.readBranch(name);\n if (ref) {\n result.push({\n name,\n revision: ref.revision,\n current: head.branch === name,\n });\n }\n }\n\n return result.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n /** Get revision history. */\n async log(opts?: LogOptions): Promise<LogEntry[]> {\n await this.ensureInit();\n\n let startRevision: number | null;\n\n if (opts?.branch) {\n const branchRef = await this.storage.readBranch(opts.branch);\n if (!branchRef) throw new Error(`branch \"${opts.branch}\" not found`);\n startRevision = branchRef.revision;\n } else {\n const { revision } = await this.resolveHead();\n startRevision = revision;\n }\n\n if (startRevision === null) return [];\n\n const entries: LogEntry[] = [];\n let currentId: number | null = startRevision;\n\n while (currentId !== null) {\n if (opts?.limit && entries.length >= opts.limit) break;\n\n let rev: Revision;\n try {\n rev = await this.storage.readRevision(currentId);\n } catch {\n break;\n }\n\n const changedPaths = rev.changes.map((change) => change.path);\n\n if (opts?.path) {\n const matchesPath = changedPaths.some((path) => matchVCSPath(opts.path!, path));\n if (!matchesPath) {\n currentId = rev.parent;\n continue;\n }\n }\n\n entries.push({\n id: rev.id,\n parent: rev.parent,\n branch: rev.branch,\n message: rev.message,\n timestamp: rev.timestamp,\n paths: changedPaths,\n });\n\n currentId = rev.parent;\n }\n\n return entries;\n }\n\n /** Get uncommitted changes as DiffEntry[]. */\n async status(): Promise<DiffEntry[]> {\n await this.ensureInit();\n const manifest = await this.headManifest();\n const previousIndex = await this.currentIndex();\n const working = await buildTreeManifest(this.workFs, this.workPath, {\n objectStore: this.objectStore,\n rules: this.rules,\n trackedPaths: Object.keys(manifest),\n indexEntries: previousIndex,\n });\n await this.storage.writeIndex(working.indexEntries);\n return diffManifests(manifest, working.manifest, {\n rules: this.rules,\n objectStore: this.objectStore,\n beforeIndex: previousIndex,\n afterIndex: working.indexEntries,\n });\n }\n\n /** Diff between two revisions. */\n async diff(revA: number, revB: number): Promise<DiffEntry[]> {\n await this.ensureInit();\n const a = await this.storage.readRevision(revA);\n const b = await this.storage.readRevision(revB);\n return diffManifests(a.tree, b.tree, {\n rules: this.rules,\n objectStore: this.objectStore,\n });\n }\n\n async readBlob(blobId: string): Promise<Buffer>;\n async readBlob(blobId: string, encoding: BufferEncoding): Promise<string>;\n async readBlob(blobId: string, encoding?: BufferEncoding): Promise<Buffer | string> {\n const content = await this.objectStore.readBlob(blobId);\n return encoding ? content.toString(encoding) : content;\n }\n\n async readRevisionFile(revisionId: number, path: string): Promise<Buffer>;\n async readRevisionFile(\n revisionId: number,\n path: string,\n encoding: BufferEncoding,\n ): Promise<string>;\n async readRevisionFile(\n revisionId: number,\n path: string,\n encoding?: BufferEncoding,\n ): Promise<Buffer | string> {\n const revision = await this.storage.readRevision(revisionId);\n const normalizedPath = path.replace(/^\\/+/, \"\");\n const entry = revision.tree[normalizedPath];\n if (!entry || entry.kind === \"directory\") {\n throw new Error(`file \"${path}\" not found in revision ${revisionId}`);\n }\n return this.readBlob(entry.blobId, encoding as BufferEncoding);\n }\n\n /** Get current HEAD info. */\n async head(): Promise<{ branch: string | null; revision: number | null }> {\n await this.ensureInit();\n return this.resolveHead();\n }\n}\n\nfunction filterPathsByGlobs(paths: string[], patterns: string[]): string[] {\n return paths.filter((filePath) =>\n patterns.some((pattern) => matchVCSPath(pattern, filePath)),\n );\n}\n\nfunction resolveInternalPath(\n workFs: VirtualFS,\n metaFs: VirtualFS,\n workPath: string,\n metaPath: string,\n): string {\n if (workFs !== metaFs) return \"\";\n\n const normalizedWork = normalizeFsPath(workPath);\n const normalizedMeta = normalizeFsPath(metaFs.resolve(metaPath));\n\n if (normalizedMeta === normalizedWork) return \"\";\n if (!normalizedMeta.startsWith(`${normalizedWork}/`)) return \"\";\n\n return normalizedMeta.slice(normalizedWork.length + 1);\n}\n\nfunction normalizeFsPath(path: string): string {\n return path.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "09DEFDF82D3AF06864756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "CA82B0F6ED31CC1264756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,19 +1,21 @@
1
1
  // src/vcs/walk.ts
2
- async function walkTree(fs, root, exclude = []) {
2
+ async function walkTree(fs, root, options = {}) {
3
+ const results = await walkTreeEntries(fs, root, options);
4
+ return results.filter((entry) => entry.kind === "file").map((entry) => entry.path);
5
+ }
6
+ async function walkTreeEntries(fs, root, options = {}) {
3
7
  const results = [];
4
- await walkDir(fs, root, root, exclude, results);
5
- return results.sort();
8
+ await walkDir(fs, root, "", options, results);
9
+ return results.sort((a, b) => a.path.localeCompare(b.path));
6
10
  }
7
- async function walkDir(fs, base, dir, exclude, results) {
11
+ async function walkDir(fs, dir, relativeDir, options, results) {
8
12
  let entries;
9
13
  try {
10
14
  entries = await fs.readdir(dir);
11
15
  } catch {
12
- return;
16
+ return false;
13
17
  }
14
18
  for (const entry of entries) {
15
- if (exclude.includes(entry))
16
- continue;
17
19
  const fullPath = fs.resolve(dir, entry);
18
20
  let stat;
19
21
  try {
@@ -21,23 +23,27 @@ async function walkDir(fs, base, dir, exclude, results) {
21
23
  } catch {
22
24
  continue;
23
25
  }
26
+ const relative = relativeDir ? `${relativeDir}/${entry}` : entry;
24
27
  if (stat.isDirectory()) {
25
- await walkDir(fs, base, fullPath, exclude, results);
28
+ if (options.enterDirectory && !await options.enterDirectory(relative)) {
29
+ continue;
30
+ }
31
+ const empty = await walkDir(fs, fullPath, relative, options, results);
32
+ if (options.includeDirectory && await options.includeDirectory(relative, { empty })) {
33
+ results.push({ path: relative, kind: "directory" });
34
+ }
26
35
  } else if (stat.isFile()) {
27
- const relative = relativePath(base, fullPath);
28
- results.push(relative);
36
+ if (options.includeFile && !await options.includeFile(relative)) {
37
+ continue;
38
+ }
39
+ results.push({ path: relative, kind: "file" });
29
40
  }
30
41
  }
31
- }
32
- function relativePath(base, full) {
33
- const normalizedBase = base.endsWith("/") ? base : base + "/";
34
- if (full.startsWith(normalizedBase)) {
35
- return full.slice(normalizedBase.length);
36
- }
37
- return full;
42
+ return entries.length === 0;
38
43
  }
39
44
  export {
45
+ walkTreeEntries,
40
46
  walkTree
41
47
  };
42
48
 
43
- //# debugId=BC9373798114D03264756E2164756E21
49
+ //# debugId=9529ABAA0ECE187C64756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/vcs/walk.ts"],
4
4
  "sourcesContent": [
5
- "import type { VirtualFS } from \"../types.mjs\";\n\n/**\n * Recursively walk a directory tree and return all file paths\n * relative to the given root. Excludes directories whose names\n * match the exclude list.\n */\nexport async function walkTree(\n fs: VirtualFS,\n root: string,\n exclude: string[] = [],\n): Promise<string[]> {\n const results: string[] = [];\n await walkDir(fs, root, root, exclude, results);\n return results.sort();\n}\n\nasync function walkDir(\n fs: VirtualFS,\n base: string,\n dir: string,\n exclude: string[],\n results: string[],\n): Promise<void> {\n let entries: string[];\n try {\n entries = await fs.readdir(dir);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (exclude.includes(entry)) continue;\n\n const fullPath = fs.resolve(dir, entry);\n let stat;\n try {\n stat = await fs.stat(fullPath);\n } catch {\n continue;\n }\n\n if (stat.isDirectory()) {\n await walkDir(fs, base, fullPath, exclude, results);\n } else if (stat.isFile()) {\n // Compute relative path from base\n const relative = relativePath(base, fullPath);\n results.push(relative);\n }\n }\n}\n\nfunction relativePath(base: string, full: string): string {\n const normalizedBase = base.endsWith(\"/\") ? base : base + \"/\";\n if (full.startsWith(normalizedBase)) {\n return full.slice(normalizedBase.length);\n }\n return full;\n}\n"
5
+ "import type { VirtualFS } from \"../types.mjs\";\n\ninterface WalkTreeOptions {\n enterDirectory?: (relPath: string) => boolean | Promise<boolean>;\n includeFile?: (relPath: string) => boolean | Promise<boolean>;\n includeDirectory?: (\n relPath: string,\n info: { empty: boolean },\n ) => boolean | Promise<boolean>;\n}\n\nexport interface WalkTreeEntry {\n path: string;\n kind: \"file\" | \"directory\";\n}\n\n/**\n * Recursively walk a directory tree and return all file paths\n * relative to the given root.\n */\nexport async function walkTree(\n fs: VirtualFS,\n root: string,\n options: WalkTreeOptions = {},\n): Promise<string[]> {\n const results = await walkTreeEntries(fs, root, options);\n return results\n .filter((entry) => entry.kind === \"file\")\n .map((entry) => entry.path);\n}\n\nexport async function walkTreeEntries(\n fs: VirtualFS,\n root: string,\n options: WalkTreeOptions = {},\n): Promise<WalkTreeEntry[]> {\n const results: WalkTreeEntry[] = [];\n await walkDir(fs, root, \"\", options, results);\n return results.sort((a, b) => a.path.localeCompare(b.path));\n}\n\nasync function walkDir(\n fs: VirtualFS,\n dir: string,\n relativeDir: string,\n options: WalkTreeOptions,\n results: WalkTreeEntry[],\n): Promise<boolean> {\n let entries: string[];\n try {\n entries = await fs.readdir(dir);\n } catch {\n return false;\n }\n\n for (const entry of entries) {\n const fullPath = fs.resolve(dir, entry);\n let stat;\n try {\n stat = await fs.stat(fullPath);\n } catch {\n continue;\n }\n\n const relative = relativeDir ? `${relativeDir}/${entry}` : entry;\n\n if (stat.isDirectory()) {\n if (options.enterDirectory && !(await options.enterDirectory(relative))) {\n continue;\n }\n const empty = await walkDir(fs, fullPath, relative, options, results);\n if (options.includeDirectory && (await options.includeDirectory(relative, { empty }))) {\n results.push({ path: relative, kind: \"directory\" });\n }\n } else if (stat.isFile()) {\n if (options.includeFile && !(await options.includeFile(relative))) {\n continue;\n }\n results.push({ path: relative, kind: \"file\" });\n }\n }\n\n return entries.length === 0;\n}\n"
6
6
  ],
7
- "mappings": ";AAOA,eAAsB,QAAQ,CAC5B,IACA,MACA,UAAoB,CAAC,GACF;AAAA,EACnB,MAAM,UAAoB,CAAC;AAAA,EAC3B,MAAM,QAAQ,IAAI,MAAM,MAAM,SAAS,OAAO;AAAA,EAC9C,OAAO,QAAQ,KAAK;AAAA;AAGtB,eAAe,OAAO,CACpB,IACA,MACA,KACA,SACA,SACe;AAAA,EACf,IAAI;AAAA,EACJ,IAAI;AAAA,IACF,UAAU,MAAM,GAAG,QAAQ,GAAG;AAAA,IAC9B,MAAM;AAAA,IACN;AAAA;AAAA,EAGF,WAAW,SAAS,SAAS;AAAA,IAC3B,IAAI,QAAQ,SAAS,KAAK;AAAA,MAAG;AAAA,IAE7B,MAAM,WAAW,GAAG,QAAQ,KAAK,KAAK;AAAA,IACtC,IAAI;AAAA,IACJ,IAAI;AAAA,MACF,OAAO,MAAM,GAAG,KAAK,QAAQ;AAAA,MAC7B,MAAM;AAAA,MACN;AAAA;AAAA,IAGF,IAAI,KAAK,YAAY,GAAG;AAAA,MACtB,MAAM,QAAQ,IAAI,MAAM,UAAU,SAAS,OAAO;AAAA,IACpD,EAAO,SAAI,KAAK,OAAO,GAAG;AAAA,MAExB,MAAM,WAAW,aAAa,MAAM,QAAQ;AAAA,MAC5C,QAAQ,KAAK,QAAQ;AAAA,IACvB;AAAA,EACF;AAAA;AAGF,SAAS,YAAY,CAAC,MAAc,MAAsB;AAAA,EACxD,MAAM,iBAAiB,KAAK,SAAS,GAAG,IAAI,OAAO,OAAO;AAAA,EAC1D,IAAI,KAAK,WAAW,cAAc,GAAG;AAAA,IACnC,OAAO,KAAK,MAAM,eAAe,MAAM;AAAA,EACzC;AAAA,EACA,OAAO;AAAA;",
8
- "debugId": "BC9373798114D03264756E2164756E21",
7
+ "mappings": ";AAoBA,eAAsB,QAAQ,CAC5B,IACA,MACA,UAA2B,CAAC,GACT;AAAA,EACnB,MAAM,UAAU,MAAM,gBAAgB,IAAI,MAAM,OAAO;AAAA,EACvD,OAAO,QACJ,OAAO,CAAC,UAAU,MAAM,SAAS,MAAM,EACvC,IAAI,CAAC,UAAU,MAAM,IAAI;AAAA;AAG9B,eAAsB,eAAe,CACnC,IACA,MACA,UAA2B,CAAC,GACF;AAAA,EAC1B,MAAM,UAA2B,CAAC;AAAA,EAClC,MAAM,QAAQ,IAAI,MAAM,IAAI,SAAS,OAAO;AAAA,EAC5C,OAAO,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAAA;AAG5D,eAAe,OAAO,CACpB,IACA,KACA,aACA,SACA,SACkB;AAAA,EAClB,IAAI;AAAA,EACJ,IAAI;AAAA,IACF,UAAU,MAAM,GAAG,QAAQ,GAAG;AAAA,IAC9B,MAAM;AAAA,IACN,OAAO;AAAA;AAAA,EAGT,WAAW,SAAS,SAAS;AAAA,IAC3B,MAAM,WAAW,GAAG,QAAQ,KAAK,KAAK;AAAA,IACtC,IAAI;AAAA,IACJ,IAAI;AAAA,MACF,OAAO,MAAM,GAAG,KAAK,QAAQ;AAAA,MAC7B,MAAM;AAAA,MACN;AAAA;AAAA,IAGF,MAAM,WAAW,cAAc,GAAG,eAAe,UAAU;AAAA,IAE3D,IAAI,KAAK,YAAY,GAAG;AAAA,MACtB,IAAI,QAAQ,kBAAkB,CAAE,MAAM,QAAQ,eAAe,QAAQ,GAAI;AAAA,QACvE;AAAA,MACF;AAAA,MACA,MAAM,QAAQ,MAAM,QAAQ,IAAI,UAAU,UAAU,SAAS,OAAO;AAAA,MACpE,IAAI,QAAQ,oBAAqB,MAAM,QAAQ,iBAAiB,UAAU,EAAE,MAAM,CAAC,GAAI;AAAA,QACrF,QAAQ,KAAK,EAAE,MAAM,UAAU,MAAM,YAAY,CAAC;AAAA,MACpD;AAAA,IACF,EAAO,SAAI,KAAK,OAAO,GAAG;AAAA,MACxB,IAAI,QAAQ,eAAe,CAAE,MAAM,QAAQ,YAAY,QAAQ,GAAI;AAAA,QACjE;AAAA,MACF;AAAA,MACA,QAAQ,KAAK,EAAE,MAAM,UAAU,MAAM,OAAO,CAAC;AAAA,IAC/C;AAAA,EACF;AAAA,EAEA,OAAO,QAAQ,WAAW;AAAA;",
8
+ "debugId": "9529ABAA0ECE187C64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,4 +1,4 @@
1
- import type { VirtualFS, FileStat } from "../types.ts";
1
+ import type { VirtualFS, VirtualFSWritable, FileStat } from "../types.ts";
2
2
  export type Permission = "read-write" | "read-only" | "excluded";
3
3
  export type PermissionRules = Record<string, Permission>;
4
4
  export interface PathOps {
@@ -21,6 +21,7 @@ export interface UnderlyingFS {
21
21
  isDirectory(): boolean;
22
22
  size: number;
23
23
  mtime: Date;
24
+ mtimeMs?: number;
24
25
  }>;
25
26
  writeFile(path: string, data: Buffer | string): Promise<void>;
26
27
  appendFile(path: string, data: Buffer | string): Promise<void>;
@@ -32,6 +33,12 @@ export interface UnderlyingFS {
32
33
  force?: boolean;
33
34
  }): Promise<void>;
34
35
  };
36
+ streams?: {
37
+ createReadStream?(path: string): AsyncIterable<Uint8Array>;
38
+ createWriteStream?(path: string, opts?: {
39
+ append?: boolean;
40
+ }): Promise<VirtualFSWritable> | VirtualFSWritable;
41
+ };
35
42
  }
36
43
  export declare class FileSystem implements VirtualFS {
37
44
  private readonly mountBase;
@@ -47,11 +54,15 @@ export declare class FileSystem implements VirtualFS {
47
54
  private resolveSafePath;
48
55
  readFile(filePath: string): Promise<Buffer>;
49
56
  readFile(filePath: string, encoding: BufferEncoding): Promise<string>;
57
+ readStream(filePath: string): AsyncIterable<Uint8Array>;
50
58
  readdir(dirPath: string): Promise<string[]>;
51
59
  stat(filePath: string): Promise<FileStat>;
52
60
  exists(filePath: string): Promise<boolean>;
53
61
  writeFile(filePath: string, data: Buffer | string): Promise<void>;
54
62
  appendFile(filePath: string, data: Buffer | string): Promise<void>;
63
+ writeStream(filePath: string, opts?: {
64
+ append?: boolean;
65
+ }): Promise<VirtualFSWritable>;
55
66
  mkdir(dirPath: string, opts?: {
56
67
  recursive?: boolean;
57
68
  }): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  export { ShellDSL, createShellDSL, type Program } from "./shell-dsl.ts";
2
2
  export { ShellPromise, type ShellPromiseOptions } from "./shell-promise.ts";
3
- export type { VirtualFS, FileStat, Command, CommandContext, Stdin, Stdout, Stderr, OutputCollector, ExecResult, ShellConfig, RawValue, } from "./types.ts";
3
+ export type { VirtualFS, VirtualFSWritable, FileStat, Command, CommandContext, Stdin, Stdout, Stderr, OutputCollector, ExecResult, ShellConfig, RawValue, } from "./types.ts";
4
4
  export { isRawValue } from "./types.ts";
5
5
  export { ShellError, LexError, ParseError } from "./errors.ts";
6
6
  export { Lexer, lex, tokenToString } from "./lexer/index.ts";
@@ -16,4 +16,4 @@ export { createStdout, createStderr, createPipe, OutputCollectorImpl, PipeBuffer
16
16
  export { escape, escapeForInterpolation, globVirtualFS } from "./utils/index.ts";
17
17
  export type { GlobVirtualFS, GlobOptions } from "./utils/index.ts";
18
18
  export { VersionControlSystem } from "./vcs/index.ts";
19
- export type { VCSConfig, Revision, DiffEntry, TreeManifest, FileEntry, CommitOptions, CheckoutOptions, LogOptions, LogEntry, BranchInfo, } from "./vcs/index.ts";
19
+ export type { VCSConfig, VCSAttributeRule, VCSResolvedAttributes, VCSDiffMode, VCSPatchSuppressionReason, Revision, DiffEntry, TreeManifest, TreeEntry, FileEntry, DirectoryEntry, VCSIndexEntry, VCSIndexFile, CommitOptions, CheckoutOptions, LogOptions, LogEntry, BranchInfo, } from "./vcs/index.ts";