shell-dsl 0.0.34 → 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 (70) hide show
  1. package/README.md +16 -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 +72 -28
  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 +6 -3
  21. package/dist/cjs/src/vcs/rules.cjs.map +3 -3
  22. package/dist/cjs/src/vcs/snapshot.cjs +89 -39
  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 +108 -61
  29. package/dist/cjs/src/vcs/vcs.cjs.map +3 -3
  30. package/dist/mjs/package.json +1 -1
  31. package/dist/mjs/src/fs/memfs-adapter.mjs +57 -2
  32. package/dist/mjs/src/fs/memfs-adapter.mjs.map +3 -3
  33. package/dist/mjs/src/fs/real-fs.mjs +135 -3
  34. package/dist/mjs/src/fs/real-fs.mjs.map +3 -3
  35. package/dist/mjs/src/fs/special-files.mjs +3 -2
  36. package/dist/mjs/src/fs/special-files.mjs.map +3 -3
  37. package/dist/mjs/src/fs/web-fs.mjs +72 -3
  38. package/dist/mjs/src/fs/web-fs.mjs.map +3 -3
  39. package/dist/mjs/src/index.mjs.map +2 -2
  40. package/dist/mjs/src/types.mjs.map +2 -2
  41. package/dist/mjs/src/vcs/content.mjs +66 -0
  42. package/dist/mjs/src/vcs/content.mjs.map +10 -0
  43. package/dist/mjs/src/vcs/diff.mjs +72 -28
  44. package/dist/mjs/src/vcs/diff.mjs.map +3 -3
  45. package/dist/mjs/src/vcs/index.mjs.map +1 -1
  46. package/dist/mjs/src/vcs/objects.mjs +106 -0
  47. package/dist/mjs/src/vcs/objects.mjs.map +10 -0
  48. package/dist/mjs/src/vcs/rules.mjs +6 -3
  49. package/dist/mjs/src/vcs/rules.mjs.map +3 -3
  50. package/dist/mjs/src/vcs/snapshot.mjs +89 -39
  51. package/dist/mjs/src/vcs/snapshot.mjs.map +3 -3
  52. package/dist/mjs/src/vcs/storage.mjs +45 -3
  53. package/dist/mjs/src/vcs/storage.mjs.map +3 -3
  54. package/dist/mjs/src/vcs/text-diff.mjs +179 -0
  55. package/dist/mjs/src/vcs/text-diff.mjs.map +10 -0
  56. package/dist/mjs/src/vcs/vcs.mjs +115 -63
  57. package/dist/mjs/src/vcs/vcs.mjs.map +3 -3
  58. package/dist/types/src/fs/real-fs.d.ts +12 -1
  59. package/dist/types/src/index.d.ts +2 -2
  60. package/dist/types/src/types.d.ts +10 -0
  61. package/dist/types/src/vcs/content.d.ts +6 -0
  62. package/dist/types/src/vcs/diff.d.ts +10 -7
  63. package/dist/types/src/vcs/index.d.ts +1 -1
  64. package/dist/types/src/vcs/objects.d.ts +22 -0
  65. package/dist/types/src/vcs/snapshot.d.ts +13 -8
  66. package/dist/types/src/vcs/storage.d.ts +7 -1
  67. package/dist/types/src/vcs/text-diff.d.ts +1 -0
  68. package/dist/types/src/vcs/types.d.ts +20 -5
  69. package/dist/types/src/vcs/vcs.d.ts +6 -0
  70. package/package.json +7 -2
@@ -97,8 +97,8 @@ class VCSRules {
97
97
  return this.isIgnored(relPath);
98
98
  }
99
99
  resolveAttributes(relPath) {
100
- let binary = false;
101
- let diff = "text";
100
+ let binary;
101
+ let diff;
102
102
  for (const rule of this.attributeRules) {
103
103
  if (!matchVCSPath(rule.pattern, relPath))
104
104
  continue;
@@ -112,6 +112,9 @@ class VCSRules {
112
112
  if (diff === "binary") {
113
113
  binary = true;
114
114
  }
115
+ if (diff === "text") {
116
+ binary = false;
117
+ }
115
118
  return { binary, diff };
116
119
  }
117
120
  }
@@ -177,4 +180,4 @@ function hasTrackedPathAtOrBelow(relPath, trackedPaths) {
177
180
  return false;
178
181
  }
179
182
 
180
- //# debugId=FD722B600498944364756E2164756E21
183
+ //# debugId=44746568854B06D364756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/vcs/rules.ts"],
4
4
  "sourcesContent": [
5
- "import { matchGlobPath } from \"./match.cjs\";\nimport type { VCSAttributeRule, VCSResolvedAttributes } from \"./types.cjs\";\n\ninterface VCSRulesConfig {\n internalPath?: string;\n internalDirName?: string;\n ignore?: string[];\n attributes?: VCSAttributeRule[];\n}\n\nexport class VCSRules {\n private readonly internalPath: string;\n private readonly ignorePatterns: string[];\n private readonly attributeRules: VCSAttributeRule[];\n\n constructor(config: VCSRulesConfig = {}) {\n this.internalPath = normalizePath(config.internalPath ?? config.internalDirName ?? \"\");\n this.ignorePatterns = [...(config.ignore ?? [])];\n this.attributeRules = [...(config.attributes ?? [])];\n }\n\n isInternalPath(relPath: string): boolean {\n if (!this.internalPath) return false;\n const normalizedPath = normalizePath(relPath);\n if (!normalizedPath) return false;\n return normalizedPath === this.internalPath || normalizedPath.startsWith(`${this.internalPath}/`);\n }\n\n isIgnored(relPath: string): boolean {\n if (this.isInternalPath(relPath)) return true;\n return this.ignorePatterns.some((pattern) => matchVCSPath(pattern, relPath));\n }\n\n shouldEnterDirectory(relPath: string, trackedPaths: Iterable<string>): boolean {\n if (this.isInternalPath(relPath)) return false;\n if (!this.isIgnored(relPath)) return true;\n return hasTrackedPathAtOrBelow(relPath, trackedPaths);\n }\n\n shouldIncludeWorkingFile(relPath: string, trackedPaths: ReadonlySet<string>): boolean {\n if (this.isInternalPath(relPath)) return false;\n if (trackedPaths.has(relPath)) return true;\n return !this.isIgnored(relPath);\n }\n\n shouldIncludeEmptyDirectory(relPath: string, trackedPaths: ReadonlySet<string>): boolean {\n if (this.isInternalPath(relPath)) return false;\n if (trackedPaths.has(relPath)) return true;\n return !this.isIgnored(relPath);\n }\n\n shouldIncludeRestoreScanFile(relPath: string): boolean {\n return !this.isInternalPath(relPath);\n }\n\n shouldPreserveUntrackedIgnored(relPath: string, trackedPaths: ReadonlySet<string>): boolean {\n if (trackedPaths.has(relPath)) return false;\n return this.isIgnored(relPath);\n }\n\n resolveAttributes(relPath: string): VCSResolvedAttributes {\n let binary = false;\n let diff: VCSResolvedAttributes[\"diff\"] = \"text\";\n\n for (const rule of this.attributeRules) {\n if (!matchVCSPath(rule.pattern, relPath)) continue;\n if (rule.binary !== undefined) {\n binary = rule.binary;\n }\n if (rule.diff !== undefined) {\n diff = rule.diff;\n }\n }\n\n if (diff === \"binary\") {\n binary = true;\n }\n\n return { binary, diff };\n }\n}\n\nexport function matchVCSPath(pattern: string, relPath: string): boolean {\n const normalizedPattern = normalizePattern(pattern);\n const normalizedPath = normalizePath(relPath);\n\n if (!normalizedPattern || !normalizedPath) return false;\n\n if (normalizedPattern.mode === \"root-path\") {\n return matchGlobPath(normalizedPattern.pattern, normalizedPath);\n }\n\n if (normalizedPattern.mode === \"root-segment\") {\n const [firstSegment] = normalizedPath.split(\"/\");\n return firstSegment ? matchGlobPath(normalizedPattern.pattern, firstSegment) : false;\n }\n\n if (normalizedPattern.mode === \"root-prefix\") {\n return (\n matchGlobPath(normalizedPattern.pattern, normalizedPath) ||\n matchGlobPath(`${normalizedPattern.pattern}/**`, normalizedPath)\n );\n }\n\n const segments = normalizedPath.split(\"/\");\n return segments.some((segment) => matchGlobPath(normalizedPattern.pattern, segment));\n}\n\nfunction normalizePath(relPath: string): string {\n return relPath.replace(/\\\\/g, \"/\").replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n}\n\nfunction normalizePattern(pattern: string): {\n pattern: string;\n mode: \"root-path\" | \"root-prefix\" | \"root-segment\" | \"segment\";\n} | null {\n let normalized = pattern.trim();\n if (!normalized) return null;\n\n normalized = normalized.replace(/\\\\/g, \"/\");\n const anchored = normalized.startsWith(\"/\");\n const directoryOnly = normalized.endsWith(\"/\");\n normalized = normalized.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n if (!normalized) return null;\n\n if (normalized.includes(\"/\")) {\n return {\n pattern: normalized,\n mode: directoryOnly ? \"root-prefix\" : \"root-path\",\n };\n }\n\n if (anchored) {\n return {\n pattern: normalized,\n mode: \"root-segment\",\n };\n }\n\n return {\n pattern: normalized,\n mode: \"segment\",\n };\n}\n\nfunction hasTrackedPathAtOrBelow(relPath: string, trackedPaths: Iterable<string>): boolean {\n const normalizedPath = normalizePath(relPath);\n if (!normalizedPath) return false;\n\n const prefix = normalizedPath + \"/\";\n for (const trackedPath of trackedPaths) {\n const normalizedTrackedPath = normalizePath(trackedPath);\n if (\n normalizedTrackedPath === normalizedPath ||\n normalizedTrackedPath.startsWith(prefix)\n ) {\n return true;\n }\n }\n return false;\n}\n"
5
+ "import { matchGlobPath } from \"./match.cjs\";\nimport type { VCSAttributeRule, VCSResolvedAttributes } from \"./types.cjs\";\n\ninterface VCSRulesConfig {\n internalPath?: string;\n internalDirName?: string;\n ignore?: string[];\n attributes?: VCSAttributeRule[];\n}\n\nexport class VCSRules {\n private readonly internalPath: string;\n private readonly ignorePatterns: string[];\n private readonly attributeRules: VCSAttributeRule[];\n\n constructor(config: VCSRulesConfig = {}) {\n this.internalPath = normalizePath(config.internalPath ?? config.internalDirName ?? \"\");\n this.ignorePatterns = [...(config.ignore ?? [])];\n this.attributeRules = [...(config.attributes ?? [])];\n }\n\n isInternalPath(relPath: string): boolean {\n if (!this.internalPath) return false;\n const normalizedPath = normalizePath(relPath);\n if (!normalizedPath) return false;\n return normalizedPath === this.internalPath || normalizedPath.startsWith(`${this.internalPath}/`);\n }\n\n isIgnored(relPath: string): boolean {\n if (this.isInternalPath(relPath)) return true;\n return this.ignorePatterns.some((pattern) => matchVCSPath(pattern, relPath));\n }\n\n shouldEnterDirectory(relPath: string, trackedPaths: Iterable<string>): boolean {\n if (this.isInternalPath(relPath)) return false;\n if (!this.isIgnored(relPath)) return true;\n return hasTrackedPathAtOrBelow(relPath, trackedPaths);\n }\n\n shouldIncludeWorkingFile(relPath: string, trackedPaths: ReadonlySet<string>): boolean {\n if (this.isInternalPath(relPath)) return false;\n if (trackedPaths.has(relPath)) return true;\n return !this.isIgnored(relPath);\n }\n\n shouldIncludeEmptyDirectory(relPath: string, trackedPaths: ReadonlySet<string>): boolean {\n if (this.isInternalPath(relPath)) return false;\n if (trackedPaths.has(relPath)) return true;\n return !this.isIgnored(relPath);\n }\n\n shouldIncludeRestoreScanFile(relPath: string): boolean {\n return !this.isInternalPath(relPath);\n }\n\n shouldPreserveUntrackedIgnored(relPath: string, trackedPaths: ReadonlySet<string>): boolean {\n if (trackedPaths.has(relPath)) return false;\n return this.isIgnored(relPath);\n }\n\n resolveAttributes(relPath: string): VCSResolvedAttributes {\n let binary: boolean | undefined;\n let diff: VCSResolvedAttributes[\"diff\"];\n\n for (const rule of this.attributeRules) {\n if (!matchVCSPath(rule.pattern, relPath)) continue;\n if (rule.binary !== undefined) {\n binary = rule.binary;\n }\n if (rule.diff !== undefined) {\n diff = rule.diff;\n }\n }\n\n if (diff === \"binary\") {\n binary = true;\n }\n if (diff === \"text\") {\n binary = false;\n }\n\n return { binary, diff };\n }\n}\n\nexport function matchVCSPath(pattern: string, relPath: string): boolean {\n const normalizedPattern = normalizePattern(pattern);\n const normalizedPath = normalizePath(relPath);\n\n if (!normalizedPattern || !normalizedPath) return false;\n\n if (normalizedPattern.mode === \"root-path\") {\n return matchGlobPath(normalizedPattern.pattern, normalizedPath);\n }\n\n if (normalizedPattern.mode === \"root-segment\") {\n const [firstSegment] = normalizedPath.split(\"/\");\n return firstSegment ? matchGlobPath(normalizedPattern.pattern, firstSegment) : false;\n }\n\n if (normalizedPattern.mode === \"root-prefix\") {\n return (\n matchGlobPath(normalizedPattern.pattern, normalizedPath) ||\n matchGlobPath(`${normalizedPattern.pattern}/**`, normalizedPath)\n );\n }\n\n const segments = normalizedPath.split(\"/\");\n return segments.some((segment) => matchGlobPath(normalizedPattern.pattern, segment));\n}\n\nfunction normalizePath(relPath: string): string {\n return relPath.replace(/\\\\/g, \"/\").replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n}\n\nfunction normalizePattern(pattern: string): {\n pattern: string;\n mode: \"root-path\" | \"root-prefix\" | \"root-segment\" | \"segment\";\n} | null {\n let normalized = pattern.trim();\n if (!normalized) return null;\n\n normalized = normalized.replace(/\\\\/g, \"/\");\n const anchored = normalized.startsWith(\"/\");\n const directoryOnly = normalized.endsWith(\"/\");\n normalized = normalized.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n if (!normalized) return null;\n\n if (normalized.includes(\"/\")) {\n return {\n pattern: normalized,\n mode: directoryOnly ? \"root-prefix\" : \"root-path\",\n };\n }\n\n if (anchored) {\n return {\n pattern: normalized,\n mode: \"root-segment\",\n };\n }\n\n return {\n pattern: normalized,\n mode: \"segment\",\n };\n}\n\nfunction hasTrackedPathAtOrBelow(relPath: string, trackedPaths: Iterable<string>): boolean {\n const normalizedPath = normalizePath(relPath);\n if (!normalizedPath) return false;\n\n const prefix = normalizedPath + \"/\";\n for (const trackedPath of trackedPaths) {\n const normalizedTrackedPath = normalizePath(trackedPath);\n if (\n normalizedTrackedPath === normalizedPath ||\n normalizedTrackedPath.startsWith(prefix)\n ) {\n return true;\n }\n }\n return false;\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "FD722B600498944364756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "44746568854B06D364756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -39,17 +39,20 @@ var __export = (target, all) => {
39
39
  // src/vcs/snapshot.ts
40
40
  var exports_snapshot = {};
41
41
  __export(exports_snapshot, {
42
+ updateIndexForScopedPaths: () => updateIndexForScopedPaths,
42
43
  restoreTree: () => restoreTree,
43
- buildTreeManifest: () => buildTreeManifest,
44
- buildPartialManifest: () => buildPartialManifest
44
+ rebuildIndexForManifest: () => rebuildIndexForManifest,
45
+ buildTreeManifest: () => buildTreeManifest
45
46
  });
46
47
  module.exports = __toCommonJS(exports_snapshot);
48
+ var import_content = require("./content.cjs");
47
49
  var import_rules = require("./rules.cjs");
48
50
  var import_walk = require("./walk.cjs");
49
51
  async function buildTreeManifest(fs, rootPath, options) {
50
52
  const manifest = {};
51
- const rules = options?.rules ?? new import_rules.VCSRules({ internalDirName: ".vcs" });
52
- const trackedPaths = new Set(options?.trackedPaths ?? []);
53
+ const nextIndexEntries = {};
54
+ const rules = options.rules ?? new import_rules.VCSRules({ internalDirName: ".vcs" });
55
+ const trackedPaths = new Set(options.trackedPaths ?? []);
53
56
  const entries = await import_walk.walkTreeEntries(fs, rootPath, {
54
57
  enterDirectory: (relPath) => rules.shouldEnterDirectory(relPath, trackedPaths),
55
58
  includeFile: (relPath) => rules.shouldIncludeWorkingFile(relPath, trackedPaths),
@@ -61,43 +64,37 @@ async function buildTreeManifest(fs, rootPath, options) {
61
64
  continue;
62
65
  }
63
66
  const fullPath = fs.resolve(rootPath, entry.path);
64
- const content = await fs.readFile(fullPath);
65
- const buf = Buffer.from(content);
66
- manifest[entry.path] = {
67
- kind: "file",
68
- content: buf.toString("base64"),
69
- size: buf.length
70
- };
71
- }
72
- return manifest;
73
- }
74
- async function buildPartialManifest(fs, rootPath, paths) {
75
- const manifest = {};
76
- for (const relPath of paths) {
77
- const fullPath = fs.resolve(rootPath, relPath);
78
- if (!await fs.exists(fullPath))
79
- continue;
80
67
  const stat = await fs.stat(fullPath);
81
- if (stat.isDirectory()) {
82
- const entries = await fs.readdir(fullPath);
83
- if (entries.length === 0) {
84
- manifest[relPath] = { kind: "directory", size: 0 };
68
+ const cached = options.indexEntries?.[entry.path];
69
+ if (cached && cached.size === stat.size && cached.mtimeMs === stat.mtimeMs && await options.objectStore.hasBlob(cached.blobId)) {
70
+ const sampleHash = import_content.hashSample(await import_content.readStreamSample(fs.readStream(fullPath)));
71
+ if (sampleHash === cached.sampleHash) {
72
+ manifest[entry.path] = {
73
+ kind: "file",
74
+ blobId: cached.blobId,
75
+ size: cached.size
76
+ };
77
+ nextIndexEntries[entry.path] = cached;
78
+ continue;
85
79
  }
86
- continue;
87
80
  }
88
- if (!stat.isFile())
89
- continue;
90
- const content = await fs.readFile(fullPath);
91
- const buf = Buffer.from(content);
92
- manifest[relPath] = {
81
+ const stored = await options.objectStore.store(fs.readStream(fullPath));
82
+ manifest[entry.path] = {
93
83
  kind: "file",
94
- content: buf.toString("base64"),
95
- size: buf.length
84
+ blobId: stored.blobId,
85
+ size: stored.size
86
+ };
87
+ nextIndexEntries[entry.path] = {
88
+ blobId: stored.blobId,
89
+ size: stored.size,
90
+ mtimeMs: stat.mtimeMs,
91
+ binary: stored.binary,
92
+ sampleHash: stored.sampleHash
96
93
  };
97
94
  }
98
- return manifest;
95
+ return { manifest, indexEntries: nextIndexEntries };
99
96
  }
100
- async function restoreTree(fs, rootPath, manifest, options) {
97
+ async function restoreTree(fs, rootPath, manifest, objectStore, options) {
101
98
  const fullRestore = options?.fullRestore ?? false;
102
99
  const rules = options?.rules ?? new import_rules.VCSRules({ internalDirName: ".vcs" });
103
100
  const trackedPaths = new Set(options?.trackedPaths ?? []);
@@ -132,7 +129,7 @@ async function restoreTree(fs, rootPath, manifest, options) {
132
129
  await ensureDirectoryExists(fs, fs.resolve(rootPath, relPath));
133
130
  continue;
134
131
  }
135
- await writeFileFromEntry(fs, rootPath, relPath, entry);
132
+ await writeFileFromEntry(fs, rootPath, relPath, entry, objectStore);
136
133
  }
137
134
  if (shouldDeleteExtras) {
138
135
  const currentEntries = await import_walk.walkTreeEntries(fs, rootPath, {
@@ -153,12 +150,65 @@ async function restoreTree(fs, rootPath, manifest, options) {
153
150
  }
154
151
  }
155
152
  }
156
- async function writeFileFromEntry(fs, rootPath, relPath, entry) {
153
+ async function rebuildIndexForManifest(fs, rootPath, manifest, objectStore) {
154
+ const entries = {};
155
+ for (const [relPath, entry] of Object.entries(manifest)) {
156
+ if (isDirectoryEntry(entry)) {
157
+ continue;
158
+ }
159
+ const fullPath = fs.resolve(rootPath, relPath);
160
+ const stat = await fs.stat(fullPath);
161
+ entries[relPath] = {
162
+ blobId: entry.blobId,
163
+ size: entry.size,
164
+ mtimeMs: stat.mtimeMs,
165
+ binary: await objectStore.isBinaryBlob(entry.blobId),
166
+ sampleHash: import_content.hashSample(await import_content.readStreamSample(fs.readStream(fullPath)))
167
+ };
168
+ }
169
+ return entries;
170
+ }
171
+ async function updateIndexForScopedPaths(fs, rootPath, manifest, objectStore, existingIndex, patterns) {
172
+ const nextIndex = { ...existingIndex };
173
+ for (const relPath of Object.keys(existingIndex)) {
174
+ if (isPathInScope(relPath, patterns) && !manifest[relPath]) {
175
+ delete nextIndex[relPath];
176
+ }
177
+ }
178
+ for (const [relPath, entry] of Object.entries(manifest)) {
179
+ if (!isPathInScope(relPath, patterns) || isDirectoryEntry(entry)) {
180
+ continue;
181
+ }
182
+ const fullPath = fs.resolve(rootPath, relPath);
183
+ if (!await fs.exists(fullPath)) {
184
+ delete nextIndex[relPath];
185
+ continue;
186
+ }
187
+ const stat = await fs.stat(fullPath);
188
+ nextIndex[relPath] = {
189
+ blobId: entry.blobId,
190
+ size: entry.size,
191
+ mtimeMs: stat.mtimeMs,
192
+ binary: await objectStore.isBinaryBlob(entry.blobId),
193
+ sampleHash: import_content.hashSample(await import_content.readStreamSample(fs.readStream(fullPath)))
194
+ };
195
+ }
196
+ return nextIndex;
197
+ }
198
+ async function writeFileFromEntry(fs, rootPath, relPath, entry, objectStore) {
157
199
  const fullPath = fs.resolve(rootPath, relPath);
158
200
  await ensureDirectoryExists(fs, fs.dirname(fullPath));
159
201
  await removeDirectoryAtPath(fs, fullPath);
160
- const buf = Buffer.from(entry.content, "base64");
161
- await fs.writeFile(fullPath, buf);
202
+ const writer = await fs.writeStream(fullPath);
203
+ try {
204
+ for await (const chunk of objectStore.readBlobStream(entry.blobId)) {
205
+ await writer.write(chunk);
206
+ }
207
+ await writer.close();
208
+ } catch (error) {
209
+ await writer.abort?.(error);
210
+ throw error;
211
+ }
162
212
  }
163
213
  function isDirectoryEntry(entry) {
164
214
  return entry.kind === "directory";
@@ -225,4 +275,4 @@ async function isEmptyDirectory(fs, dirPath) {
225
275
  }
226
276
  }
227
277
 
228
- //# debugId=7E15F1F035E56CC164756E2164756E21
278
+ //# debugId=7BF3A16CF496D89964756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/vcs/snapshot.ts"],
4
4
  "sourcesContent": [
5
- "import type { VirtualFS } from \"../types.cjs\";\nimport type { TreeManifest, FileEntry, TreeEntry } from \"./types.cjs\";\nimport { matchVCSPath, VCSRules } from \"./rules.cjs\";\nimport { walkTreeEntries } from \"./walk.cjs\";\n\n/**\n * Build a TreeManifest from the current working tree.\n */\nexport async function buildTreeManifest(\n fs: VirtualFS,\n rootPath: string,\n options?: {\n rules?: VCSRules;\n trackedPaths?: Iterable<string>;\n },\n): Promise<TreeManifest> {\n const manifest: TreeManifest = {};\n const rules = options?.rules ?? new VCSRules({ internalDirName: \".vcs\" });\n const trackedPaths = new Set(options?.trackedPaths ?? []);\n const entries = await walkTreeEntries(fs, rootPath, {\n enterDirectory: (relPath) => rules.shouldEnterDirectory(relPath, trackedPaths),\n includeFile: (relPath) => rules.shouldIncludeWorkingFile(relPath, trackedPaths),\n includeDirectory: (relPath, info) =>\n info.empty && rules.shouldIncludeEmptyDirectory(relPath, trackedPaths),\n });\n\n for (const entry of entries) {\n if (entry.kind === \"directory\") {\n manifest[entry.path] = { kind: \"directory\", size: 0 };\n continue;\n }\n\n const fullPath = fs.resolve(rootPath, entry.path);\n const content = await fs.readFile(fullPath);\n const buf = Buffer.from(content);\n manifest[entry.path] = {\n kind: \"file\",\n content: buf.toString(\"base64\"),\n size: buf.length,\n };\n }\n\n return manifest;\n}\n\n/**\n * Build a TreeManifest for only the specified relative paths.\n */\nexport async function buildPartialManifest(\n fs: VirtualFS,\n rootPath: string,\n paths: string[],\n): Promise<TreeManifest> {\n const manifest: TreeManifest = {};\n\n for (const relPath of paths) {\n const fullPath = fs.resolve(rootPath, relPath);\n if (!(await fs.exists(fullPath))) continue;\n const stat = await fs.stat(fullPath);\n if (stat.isDirectory()) {\n const entries = await fs.readdir(fullPath);\n if (entries.length === 0) {\n manifest[relPath] = { kind: \"directory\", size: 0 };\n }\n continue;\n }\n if (!stat.isFile()) continue;\n\n const content = await fs.readFile(fullPath);\n const buf = Buffer.from(content);\n manifest[relPath] = {\n kind: \"file\",\n content: buf.toString(\"base64\"),\n size: buf.length,\n };\n }\n\n return manifest;\n}\n\n/**\n * Restore a working tree from a TreeManifest.\n *\n * If `fullRestore` is true, deletes working tree files not in the manifest.\n * If `paths` is provided, only restores matching files.\n */\nexport async function restoreTree(\n fs: VirtualFS,\n rootPath: string,\n manifest: TreeManifest,\n options?: {\n fullRestore?: boolean;\n paths?: string[];\n rules?: VCSRules;\n trackedPaths?: Iterable<string>;\n },\n): Promise<void> {\n const fullRestore = options?.fullRestore ?? false;\n const rules = options?.rules ?? new VCSRules({ internalDirName: \".vcs\" });\n const trackedPaths = new Set(options?.trackedPaths ?? []);\n const scopePatterns = options?.paths ?? null;\n const scopedEntries = Object.entries(manifest)\n .filter(([relPath]) => isPathInScope(relPath, scopePatterns))\n .sort(([a], [b]) => a.localeCompare(b));\n const targetPaths = new Set(scopedEntries.map(([relPath]) => relPath));\n const requiredDirectories = collectRequiredDirectories(scopedEntries);\n const shouldDeleteExtras = fullRestore || scopePatterns !== null;\n\n if (shouldDeleteExtras) {\n const currentEntries = await walkTreeEntries(fs, rootPath, {\n enterDirectory: (relPath) => !rules.isInternalPath(relPath),\n includeFile: (relPath) => rules.shouldIncludeRestoreScanFile(relPath),\n includeDirectory: () => true,\n });\n\n for (const current of currentEntries) {\n if (current.kind !== \"file\") continue;\n if (!isPathInScope(current.path, scopePatterns)) continue;\n if (targetPaths.has(current.path)) continue;\n if (rules.shouldPreserveUntrackedIgnored(current.path, trackedPaths)) continue;\n await fs.rm(fs.resolve(rootPath, current.path));\n }\n }\n\n for (const directory of [...requiredDirectories].sort(comparePathDepth)) {\n await ensureDirectoryExists(fs, fs.resolve(rootPath, directory));\n }\n\n for (const [relPath, entry] of scopedEntries) {\n if (isDirectoryEntry(entry)) {\n await ensureDirectoryExists(fs, fs.resolve(rootPath, relPath));\n continue;\n }\n await writeFileFromEntry(fs, rootPath, relPath, entry);\n }\n\n if (shouldDeleteExtras) {\n const currentEntries = await walkTreeEntries(fs, rootPath, {\n enterDirectory: (relPath) => !rules.isInternalPath(relPath),\n includeFile: () => true,\n includeDirectory: () => true,\n });\n\n const directories = currentEntries\n .filter((entry) => entry.kind === \"directory\")\n .map((entry) => entry.path)\n .filter((relPath) => isPathInScope(relPath, scopePatterns))\n .sort((a, b) => comparePathDepth(b, a));\n\n for (const relPath of directories) {\n if (requiredDirectories.has(relPath)) continue;\n if (rules.shouldPreserveUntrackedIgnored(relPath, trackedPaths)) continue;\n const fullPath = fs.resolve(rootPath, relPath);\n if (await isEmptyDirectory(fs, fullPath)) {\n await fs.rm(fullPath);\n }\n }\n }\n}\n\nasync function writeFileFromEntry(\n fs: VirtualFS,\n rootPath: string,\n relPath: string,\n entry: FileEntry,\n): Promise<void> {\n const fullPath = fs.resolve(rootPath, relPath);\n await ensureDirectoryExists(fs, fs.dirname(fullPath));\n await removeDirectoryAtPath(fs, fullPath);\n\n const buf = Buffer.from(entry.content, \"base64\");\n await fs.writeFile(fullPath, buf);\n}\n\nfunction isDirectoryEntry(entry: TreeEntry): entry is Extract<TreeEntry, { kind: \"directory\" }> {\n return entry.kind === \"directory\";\n}\n\nfunction isPathInScope(relPath: string, patterns: string[] | null): boolean {\n if (!patterns || patterns.length === 0) return true;\n return patterns.some((pattern) => matchVCSPath(pattern, relPath));\n}\n\nfunction collectRequiredDirectories(entries: Array<[string, TreeEntry]>): Set<string> {\n const directories = new Set<string>();\n\n for (const [relPath, entry] of entries) {\n if (isDirectoryEntry(entry)) {\n directories.add(relPath);\n }\n for (const parent of parentDirectories(relPath)) {\n directories.add(parent);\n }\n }\n\n return directories;\n}\n\nfunction parentDirectories(relPath: string): string[] {\n const parts = relPath.split(\"/\").filter(Boolean);\n const parents: string[] = [];\n\n for (let i = 1; i < parts.length; i++) {\n parents.push(parts.slice(0, i).join(\"/\"));\n }\n\n return parents;\n}\n\nfunction comparePathDepth(a: string, b: string): number {\n const depthA = a.split(\"/\").filter(Boolean).length;\n const depthB = b.split(\"/\").filter(Boolean).length;\n if (depthA !== depthB) return depthA - depthB;\n return a.localeCompare(b);\n}\n\nasync function ensureDirectoryExists(fs: VirtualFS, dirPath: string): Promise<void> {\n const parent = fs.dirname(dirPath);\n if (parent !== dirPath) {\n await ensureDirectoryExists(fs, parent);\n }\n\n if (await fs.exists(dirPath)) {\n const stat = await fs.stat(dirPath);\n if (stat.isDirectory()) return;\n await fs.rm(dirPath, { recursive: true, force: true });\n }\n\n await fs.mkdir(dirPath, { recursive: true });\n}\n\nasync function removeDirectoryAtPath(fs: VirtualFS, path: string): Promise<void> {\n if (!(await fs.exists(path))) return;\n const stat = await fs.stat(path);\n if (stat.isDirectory()) {\n await fs.rm(path, { recursive: true, force: true });\n }\n}\n\nasync function isEmptyDirectory(fs: VirtualFS, dirPath: string): Promise<boolean> {\n try {\n const entries = await fs.readdir(dirPath);\n return entries.length === 0;\n } catch {\n return false;\n }\n}\n"
5
+ "import type { VirtualFS } from \"../types.cjs\";\nimport type { TreeManifest, FileEntry, TreeEntry, VCSIndexEntry } from \"./types.cjs\";\nimport { hashSample, readStreamSample } from \"./content.cjs\";\nimport { matchVCSPath, VCSRules } from \"./rules.cjs\";\nimport { walkTreeEntries } from \"./walk.cjs\";\nimport { VCSObjectStore } from \"./objects.cjs\";\n\nexport interface BuildTreeManifestResult {\n manifest: TreeManifest;\n indexEntries: Record<string, VCSIndexEntry>;\n}\n\n/**\n * Build a TreeManifest from the current working tree.\n */\nexport async function buildTreeManifest(\n fs: VirtualFS,\n rootPath: string,\n options: {\n objectStore: VCSObjectStore;\n rules?: VCSRules;\n trackedPaths?: Iterable<string>;\n indexEntries?: Record<string, VCSIndexEntry>;\n },\n): Promise<BuildTreeManifestResult> {\n const manifest: TreeManifest = {};\n const nextIndexEntries: Record<string, VCSIndexEntry> = {};\n const rules = options.rules ?? new VCSRules({ internalDirName: \".vcs\" });\n const trackedPaths = new Set(options.trackedPaths ?? []);\n const entries = await walkTreeEntries(fs, rootPath, {\n enterDirectory: (relPath) => rules.shouldEnterDirectory(relPath, trackedPaths),\n includeFile: (relPath) => rules.shouldIncludeWorkingFile(relPath, trackedPaths),\n includeDirectory: (relPath, info) =>\n info.empty && rules.shouldIncludeEmptyDirectory(relPath, trackedPaths),\n });\n\n for (const entry of entries) {\n if (entry.kind === \"directory\") {\n manifest[entry.path] = { kind: \"directory\", size: 0 };\n continue;\n }\n\n const fullPath = fs.resolve(rootPath, entry.path);\n const stat = await fs.stat(fullPath);\n const cached = options.indexEntries?.[entry.path];\n\n if (\n cached &&\n cached.size === stat.size &&\n cached.mtimeMs === stat.mtimeMs &&\n await options.objectStore.hasBlob(cached.blobId)\n ) {\n const sampleHash = hashSample(await readStreamSample(fs.readStream(fullPath)));\n if (sampleHash === cached.sampleHash) {\n manifest[entry.path] = {\n kind: \"file\",\n blobId: cached.blobId,\n size: cached.size,\n };\n nextIndexEntries[entry.path] = cached;\n continue;\n }\n }\n\n const stored = await options.objectStore.store(fs.readStream(fullPath));\n manifest[entry.path] = {\n kind: \"file\",\n blobId: stored.blobId,\n size: stored.size,\n };\n nextIndexEntries[entry.path] = {\n blobId: stored.blobId,\n size: stored.size,\n mtimeMs: stat.mtimeMs,\n binary: stored.binary,\n sampleHash: stored.sampleHash,\n };\n }\n\n return { manifest, indexEntries: nextIndexEntries };\n}\n\n/**\n * Restore a working tree from a TreeManifest.\n *\n * If `fullRestore` is true, deletes working tree files not in the manifest.\n * If `paths` is provided, only restores matching files.\n */\nexport async function restoreTree(\n fs: VirtualFS,\n rootPath: string,\n manifest: TreeManifest,\n objectStore: VCSObjectStore,\n options?: {\n fullRestore?: boolean;\n paths?: string[];\n rules?: VCSRules;\n trackedPaths?: Iterable<string>;\n },\n): Promise<void> {\n const fullRestore = options?.fullRestore ?? false;\n const rules = options?.rules ?? new VCSRules({ internalDirName: \".vcs\" });\n const trackedPaths = new Set(options?.trackedPaths ?? []);\n const scopePatterns = options?.paths ?? null;\n const scopedEntries = Object.entries(manifest)\n .filter(([relPath]) => isPathInScope(relPath, scopePatterns))\n .sort(([a], [b]) => a.localeCompare(b));\n const targetPaths = new Set(scopedEntries.map(([relPath]) => relPath));\n const requiredDirectories = collectRequiredDirectories(scopedEntries);\n const shouldDeleteExtras = fullRestore || scopePatterns !== null;\n\n if (shouldDeleteExtras) {\n const currentEntries = await walkTreeEntries(fs, rootPath, {\n enterDirectory: (relPath) => !rules.isInternalPath(relPath),\n includeFile: (relPath) => rules.shouldIncludeRestoreScanFile(relPath),\n includeDirectory: () => true,\n });\n\n for (const current of currentEntries) {\n if (current.kind !== \"file\") continue;\n if (!isPathInScope(current.path, scopePatterns)) continue;\n if (targetPaths.has(current.path)) continue;\n if (rules.shouldPreserveUntrackedIgnored(current.path, trackedPaths)) continue;\n await fs.rm(fs.resolve(rootPath, current.path));\n }\n }\n\n for (const directory of [...requiredDirectories].sort(comparePathDepth)) {\n await ensureDirectoryExists(fs, fs.resolve(rootPath, directory));\n }\n\n for (const [relPath, entry] of scopedEntries) {\n if (isDirectoryEntry(entry)) {\n await ensureDirectoryExists(fs, fs.resolve(rootPath, relPath));\n continue;\n }\n await writeFileFromEntry(fs, rootPath, relPath, entry, objectStore);\n }\n\n if (shouldDeleteExtras) {\n const currentEntries = await walkTreeEntries(fs, rootPath, {\n enterDirectory: (relPath) => !rules.isInternalPath(relPath),\n includeFile: () => true,\n includeDirectory: () => true,\n });\n\n const directories = currentEntries\n .filter((entry) => entry.kind === \"directory\")\n .map((entry) => entry.path)\n .filter((relPath) => isPathInScope(relPath, scopePatterns))\n .sort((a, b) => comparePathDepth(b, a));\n\n for (const relPath of directories) {\n if (requiredDirectories.has(relPath)) continue;\n if (rules.shouldPreserveUntrackedIgnored(relPath, trackedPaths)) continue;\n const fullPath = fs.resolve(rootPath, relPath);\n if (await isEmptyDirectory(fs, fullPath)) {\n await fs.rm(fullPath);\n }\n }\n }\n}\n\nexport async function rebuildIndexForManifest(\n fs: VirtualFS,\n rootPath: string,\n manifest: TreeManifest,\n objectStore: VCSObjectStore,\n): Promise<Record<string, VCSIndexEntry>> {\n const entries: Record<string, VCSIndexEntry> = {};\n\n for (const [relPath, entry] of Object.entries(manifest)) {\n if (isDirectoryEntry(entry)) {\n continue;\n }\n\n const fullPath = fs.resolve(rootPath, relPath);\n const stat = await fs.stat(fullPath);\n entries[relPath] = {\n blobId: entry.blobId,\n size: entry.size,\n mtimeMs: stat.mtimeMs,\n binary: await objectStore.isBinaryBlob(entry.blobId),\n sampleHash: hashSample(await readStreamSample(fs.readStream(fullPath))),\n };\n }\n\n return entries;\n}\n\nexport async function updateIndexForScopedPaths(\n fs: VirtualFS,\n rootPath: string,\n manifest: TreeManifest,\n objectStore: VCSObjectStore,\n existingIndex: Record<string, VCSIndexEntry>,\n patterns: string[],\n): Promise<Record<string, VCSIndexEntry>> {\n const nextIndex = { ...existingIndex };\n\n for (const relPath of Object.keys(existingIndex)) {\n if (isPathInScope(relPath, patterns) && !manifest[relPath]) {\n delete nextIndex[relPath];\n }\n }\n\n for (const [relPath, entry] of Object.entries(manifest)) {\n if (!isPathInScope(relPath, patterns) || isDirectoryEntry(entry)) {\n continue;\n }\n\n const fullPath = fs.resolve(rootPath, relPath);\n if (!(await fs.exists(fullPath))) {\n delete nextIndex[relPath];\n continue;\n }\n const stat = await fs.stat(fullPath);\n nextIndex[relPath] = {\n blobId: entry.blobId,\n size: entry.size,\n mtimeMs: stat.mtimeMs,\n binary: await objectStore.isBinaryBlob(entry.blobId),\n sampleHash: hashSample(await readStreamSample(fs.readStream(fullPath))),\n };\n }\n\n return nextIndex;\n}\n\nasync function writeFileFromEntry(\n fs: VirtualFS,\n rootPath: string,\n relPath: string,\n entry: FileEntry,\n objectStore: VCSObjectStore,\n): Promise<void> {\n const fullPath = fs.resolve(rootPath, relPath);\n await ensureDirectoryExists(fs, fs.dirname(fullPath));\n await removeDirectoryAtPath(fs, fullPath);\n\n const writer = await fs.writeStream(fullPath);\n try {\n for await (const chunk of objectStore.readBlobStream(entry.blobId)) {\n await writer.write(chunk);\n }\n await writer.close();\n } catch (error) {\n await writer.abort?.(error);\n throw error;\n }\n}\n\nfunction isDirectoryEntry(entry: TreeEntry): entry is Extract<TreeEntry, { kind: \"directory\" }> {\n return entry.kind === \"directory\";\n}\n\nfunction isPathInScope(relPath: string, patterns: string[] | null): boolean {\n if (!patterns || patterns.length === 0) return true;\n return patterns.some((pattern) => matchVCSPath(pattern, relPath));\n}\n\nfunction collectRequiredDirectories(entries: Array<[string, TreeEntry]>): Set<string> {\n const directories = new Set<string>();\n\n for (const [relPath, entry] of entries) {\n if (isDirectoryEntry(entry)) {\n directories.add(relPath);\n }\n for (const parent of parentDirectories(relPath)) {\n directories.add(parent);\n }\n }\n\n return directories;\n}\n\nfunction parentDirectories(relPath: string): string[] {\n const parts = relPath.split(\"/\").filter(Boolean);\n const parents: string[] = [];\n\n for (let i = 1; i < parts.length; i++) {\n parents.push(parts.slice(0, i).join(\"/\"));\n }\n\n return parents;\n}\n\nfunction comparePathDepth(a: string, b: string): number {\n const depthA = a.split(\"/\").filter(Boolean).length;\n const depthB = b.split(\"/\").filter(Boolean).length;\n if (depthA !== depthB) return depthA - depthB;\n return a.localeCompare(b);\n}\n\nasync function ensureDirectoryExists(fs: VirtualFS, dirPath: string): Promise<void> {\n const parent = fs.dirname(dirPath);\n if (parent !== dirPath) {\n await ensureDirectoryExists(fs, parent);\n }\n\n if (await fs.exists(dirPath)) {\n const stat = await fs.stat(dirPath);\n if (stat.isDirectory()) return;\n await fs.rm(dirPath, { recursive: true, force: true });\n }\n\n await fs.mkdir(dirPath, { recursive: true });\n}\n\nasync function removeDirectoryAtPath(fs: VirtualFS, path: string): Promise<void> {\n if (!(await fs.exists(path))) return;\n const stat = await fs.stat(path);\n if (stat.isDirectory()) {\n await fs.rm(path, { recursive: true, force: true });\n }\n}\n\nasync function isEmptyDirectory(fs: VirtualFS, dirPath: string): Promise<boolean> {\n try {\n const entries = await fs.readdir(dirPath);\n return entries.length === 0;\n } catch {\n return false;\n }\n}\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEuC,IAAvC;AACgC,IAAhC;AAKA,eAAsB,iBAAiB,CACrC,IACA,UACA,SAIuB;AAAA,EACvB,MAAM,WAAyB,CAAC;AAAA,EAChC,MAAM,QAAQ,SAAS,SAAS,IAAI,sBAAS,EAAE,iBAAiB,OAAO,CAAC;AAAA,EACxE,MAAM,eAAe,IAAI,IAAI,SAAS,gBAAgB,CAAC,CAAC;AAAA,EACxD,MAAM,UAAU,MAAM,4BAAgB,IAAI,UAAU;AAAA,IAClD,gBAAgB,CAAC,YAAY,MAAM,qBAAqB,SAAS,YAAY;AAAA,IAC7E,aAAa,CAAC,YAAY,MAAM,yBAAyB,SAAS,YAAY;AAAA,IAC9E,kBAAkB,CAAC,SAAS,SAC1B,KAAK,SAAS,MAAM,4BAA4B,SAAS,YAAY;AAAA,EACzE,CAAC;AAAA,EAED,WAAW,SAAS,SAAS;AAAA,IAC3B,IAAI,MAAM,SAAS,aAAa;AAAA,MAC9B,SAAS,MAAM,QAAQ,EAAE,MAAM,aAAa,MAAM,EAAE;AAAA,MACpD;AAAA,IACF;AAAA,IAEA,MAAM,WAAW,GAAG,QAAQ,UAAU,MAAM,IAAI;AAAA,IAChD,MAAM,UAAU,MAAM,GAAG,SAAS,QAAQ;AAAA,IAC1C,MAAM,MAAM,OAAO,KAAK,OAAO;AAAA,IAC/B,SAAS,MAAM,QAAQ;AAAA,MACrB,MAAM;AAAA,MACN,SAAS,IAAI,SAAS,QAAQ;AAAA,MAC9B,MAAM,IAAI;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,OAAO;AAAA;AAMT,eAAsB,oBAAoB,CACxC,IACA,UACA,OACuB;AAAA,EACvB,MAAM,WAAyB,CAAC;AAAA,EAEhC,WAAW,WAAW,OAAO;AAAA,IAC3B,MAAM,WAAW,GAAG,QAAQ,UAAU,OAAO;AAAA,IAC7C,IAAI,CAAE,MAAM,GAAG,OAAO,QAAQ;AAAA,MAAI;AAAA,IAClC,MAAM,OAAO,MAAM,GAAG,KAAK,QAAQ;AAAA,IACnC,IAAI,KAAK,YAAY,GAAG;AAAA,MACtB,MAAM,UAAU,MAAM,GAAG,QAAQ,QAAQ;AAAA,MACzC,IAAI,QAAQ,WAAW,GAAG;AAAA,QACxB,SAAS,WAAW,EAAE,MAAM,aAAa,MAAM,EAAE;AAAA,MACnD;AAAA,MACA;AAAA,IACF;AAAA,IACA,IAAI,CAAC,KAAK,OAAO;AAAA,MAAG;AAAA,IAEpB,MAAM,UAAU,MAAM,GAAG,SAAS,QAAQ;AAAA,IAC1C,MAAM,MAAM,OAAO,KAAK,OAAO;AAAA,IAC/B,SAAS,WAAW;AAAA,MAClB,MAAM;AAAA,MACN,SAAS,IAAI,SAAS,QAAQ;AAAA,MAC9B,MAAM,IAAI;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,OAAO;AAAA;AAST,eAAsB,WAAW,CAC/B,IACA,UACA,UACA,SAMe;AAAA,EACf,MAAM,cAAc,SAAS,eAAe;AAAA,EAC5C,MAAM,QAAQ,SAAS,SAAS,IAAI,sBAAS,EAAE,iBAAiB,OAAO,CAAC;AAAA,EACxE,MAAM,eAAe,IAAI,IAAI,SAAS,gBAAgB,CAAC,CAAC;AAAA,EACxD,MAAM,gBAAgB,SAAS,SAAS;AAAA,EACxC,MAAM,gBAAgB,OAAO,QAAQ,QAAQ,EAC1C,OAAO,EAAE,aAAa,cAAc,SAAS,aAAa,CAAC,EAC3D,KAAK,EAAE,KAAK,OAAO,EAAE,cAAc,CAAC,CAAC;AAAA,EACxC,MAAM,cAAc,IAAI,IAAI,cAAc,IAAI,EAAE,aAAa,OAAO,CAAC;AAAA,EACrE,MAAM,sBAAsB,2BAA2B,aAAa;AAAA,EACpE,MAAM,qBAAqB,eAAe,kBAAkB;AAAA,EAE5D,IAAI,oBAAoB;AAAA,IACtB,MAAM,iBAAiB,MAAM,4BAAgB,IAAI,UAAU;AAAA,MACzD,gBAAgB,CAAC,YAAY,CAAC,MAAM,eAAe,OAAO;AAAA,MAC1D,aAAa,CAAC,YAAY,MAAM,6BAA6B,OAAO;AAAA,MACpE,kBAAkB,MAAM;AAAA,IAC1B,CAAC;AAAA,IAED,WAAW,WAAW,gBAAgB;AAAA,MACpC,IAAI,QAAQ,SAAS;AAAA,QAAQ;AAAA,MAC7B,IAAI,CAAC,cAAc,QAAQ,MAAM,aAAa;AAAA,QAAG;AAAA,MACjD,IAAI,YAAY,IAAI,QAAQ,IAAI;AAAA,QAAG;AAAA,MACnC,IAAI,MAAM,+BAA+B,QAAQ,MAAM,YAAY;AAAA,QAAG;AAAA,MACtE,MAAM,GAAG,GAAG,GAAG,QAAQ,UAAU,QAAQ,IAAI,CAAC;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,WAAW,aAAa,CAAC,GAAG,mBAAmB,EAAE,KAAK,gBAAgB,GAAG;AAAA,IACvE,MAAM,sBAAsB,IAAI,GAAG,QAAQ,UAAU,SAAS,CAAC;AAAA,EACjE;AAAA,EAEA,YAAY,SAAS,UAAU,eAAe;AAAA,IAC5C,IAAI,iBAAiB,KAAK,GAAG;AAAA,MAC3B,MAAM,sBAAsB,IAAI,GAAG,QAAQ,UAAU,OAAO,CAAC;AAAA,MAC7D;AAAA,IACF;AAAA,IACA,MAAM,mBAAmB,IAAI,UAAU,SAAS,KAAK;AAAA,EACvD;AAAA,EAEA,IAAI,oBAAoB;AAAA,IACtB,MAAM,iBAAiB,MAAM,4BAAgB,IAAI,UAAU;AAAA,MACzD,gBAAgB,CAAC,YAAY,CAAC,MAAM,eAAe,OAAO;AAAA,MAC1D,aAAa,MAAM;AAAA,MACnB,kBAAkB,MAAM;AAAA,IAC1B,CAAC;AAAA,IAED,MAAM,cAAc,eACjB,OAAO,CAAC,UAAU,MAAM,SAAS,WAAW,EAC5C,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,OAAO,CAAC,YAAY,cAAc,SAAS,aAAa,CAAC,EACzD,KAAK,CAAC,GAAG,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAAA,IAExC,WAAW,WAAW,aAAa;AAAA,MACjC,IAAI,oBAAoB,IAAI,OAAO;AAAA,QAAG;AAAA,MACtC,IAAI,MAAM,+BAA+B,SAAS,YAAY;AAAA,QAAG;AAAA,MACjE,MAAM,WAAW,GAAG,QAAQ,UAAU,OAAO;AAAA,MAC7C,IAAI,MAAM,iBAAiB,IAAI,QAAQ,GAAG;AAAA,QACxC,MAAM,GAAG,GAAG,QAAQ;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA;AAGF,eAAe,kBAAkB,CAC/B,IACA,UACA,SACA,OACe;AAAA,EACf,MAAM,WAAW,GAAG,QAAQ,UAAU,OAAO;AAAA,EAC7C,MAAM,sBAAsB,IAAI,GAAG,QAAQ,QAAQ,CAAC;AAAA,EACpD,MAAM,sBAAsB,IAAI,QAAQ;AAAA,EAExC,MAAM,MAAM,OAAO,KAAK,MAAM,SAAS,QAAQ;AAAA,EAC/C,MAAM,GAAG,UAAU,UAAU,GAAG;AAAA;AAGlC,SAAS,gBAAgB,CAAC,OAAsE;AAAA,EAC9F,OAAO,MAAM,SAAS;AAAA;AAGxB,SAAS,aAAa,CAAC,SAAiB,UAAoC;AAAA,EAC1E,IAAI,CAAC,YAAY,SAAS,WAAW;AAAA,IAAG,OAAO;AAAA,EAC/C,OAAO,SAAS,KAAK,CAAC,YAAY,0BAAa,SAAS,OAAO,CAAC;AAAA;AAGlE,SAAS,0BAA0B,CAAC,SAAkD;AAAA,EACpF,MAAM,cAAc,IAAI;AAAA,EAExB,YAAY,SAAS,UAAU,SAAS;AAAA,IACtC,IAAI,iBAAiB,KAAK,GAAG;AAAA,MAC3B,YAAY,IAAI,OAAO;AAAA,IACzB;AAAA,IACA,WAAW,UAAU,kBAAkB,OAAO,GAAG;AAAA,MAC/C,YAAY,IAAI,MAAM;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,OAAO;AAAA;AAGT,SAAS,iBAAiB,CAAC,SAA2B;AAAA,EACpD,MAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE,OAAO,OAAO;AAAA,EAC/C,MAAM,UAAoB,CAAC;AAAA,EAE3B,SAAS,IAAI,EAAG,IAAI,MAAM,QAAQ,KAAK;AAAA,IACrC,QAAQ,KAAK,MAAM,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,EAC1C;AAAA,EAEA,OAAO;AAAA;AAGT,SAAS,gBAAgB,CAAC,GAAW,GAAmB;AAAA,EACtD,MAAM,SAAS,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE;AAAA,EAC5C,MAAM,SAAS,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE;AAAA,EAC5C,IAAI,WAAW;AAAA,IAAQ,OAAO,SAAS;AAAA,EACvC,OAAO,EAAE,cAAc,CAAC;AAAA;AAG1B,eAAe,qBAAqB,CAAC,IAAe,SAAgC;AAAA,EAClF,MAAM,SAAS,GAAG,QAAQ,OAAO;AAAA,EACjC,IAAI,WAAW,SAAS;AAAA,IACtB,MAAM,sBAAsB,IAAI,MAAM;AAAA,EACxC;AAAA,EAEA,IAAI,MAAM,GAAG,OAAO,OAAO,GAAG;AAAA,IAC5B,MAAM,OAAO,MAAM,GAAG,KAAK,OAAO;AAAA,IAClC,IAAI,KAAK,YAAY;AAAA,MAAG;AAAA,IACxB,MAAM,GAAG,GAAG,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACvD;AAAA,EAEA,MAAM,GAAG,MAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA;AAG7C,eAAe,qBAAqB,CAAC,IAAe,MAA6B;AAAA,EAC/E,IAAI,CAAE,MAAM,GAAG,OAAO,IAAI;AAAA,IAAI;AAAA,EAC9B,MAAM,OAAO,MAAM,GAAG,KAAK,IAAI;AAAA,EAC/B,IAAI,KAAK,YAAY,GAAG;AAAA,IACtB,MAAM,GAAG,GAAG,MAAM,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACpD;AAAA;AAGF,eAAe,gBAAgB,CAAC,IAAe,SAAmC;AAAA,EAChF,IAAI;AAAA,IACF,MAAM,UAAU,MAAM,GAAG,QAAQ,OAAO;AAAA,IACxC,OAAO,QAAQ,WAAW;AAAA,IAC1B,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;",
8
- "debugId": "7E15F1F035E56CC164756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "7BF3A16CF496D89964756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -42,6 +42,7 @@ __export(exports_storage, {
42
42
  VCSStorage: () => VCSStorage
43
43
  });
44
44
  module.exports = __toCommonJS(exports_storage);
45
+ var VCS_FORMAT_VERSION = 2;
45
46
 
46
47
  class VCSStorage {
47
48
  fs;
@@ -60,9 +61,18 @@ class VCSStorage {
60
61
  await this.fs.mkdir(this.basePath, { recursive: true });
61
62
  await this.fs.mkdir(this.path("refs", "heads"), { recursive: true });
62
63
  await this.fs.mkdir(this.path("revisions"), { recursive: true });
64
+ await this.fs.mkdir(this.path("objects", "blobs"), { recursive: true });
65
+ await this.fs.mkdir(this.path("tmp"), { recursive: true });
63
66
  await this.writeHead({ ref: `refs/heads/${defaultBranch}` });
64
67
  await this.writeJSON(["counter.json"], { next: 1 });
65
- await this.writeJSON(["config.json"], { defaultBranch });
68
+ await this.writeJSON(["config.json"], {
69
+ version: VCS_FORMAT_VERSION,
70
+ defaultBranch
71
+ });
72
+ await this.writeJSON(["index.json"], {
73
+ version: VCS_FORMAT_VERSION,
74
+ entries: {}
75
+ });
66
76
  }
67
77
  async readHead() {
68
78
  return this.readJSON("HEAD");
@@ -104,7 +114,33 @@ class VCSStorage {
104
114
  return id;
105
115
  }
106
116
  async readConfig() {
107
- return this.readJSON("config.json");
117
+ const config = await this.readJSON("config.json");
118
+ this.assertSupportedVersion(config.version);
119
+ return config;
120
+ }
121
+ async assertSupportedFormat() {
122
+ await this.readConfig();
123
+ }
124
+ async readIndex() {
125
+ const indexPath = this.path("index.json");
126
+ if (!await this.fs.exists(indexPath)) {
127
+ return {};
128
+ }
129
+ const index = await this.readJSON("index.json");
130
+ this.assertSupportedVersion(index.version);
131
+ return index.entries;
132
+ }
133
+ async writeIndex(entries) {
134
+ await this.writeJSON(["index.json"], {
135
+ version: VCS_FORMAT_VERSION,
136
+ entries
137
+ });
138
+ }
139
+ resolve(...segments) {
140
+ return this.path(...segments);
141
+ }
142
+ get fileSystem() {
143
+ return this.fs;
108
144
  }
109
145
  async readJSON(...segments) {
110
146
  const filePath = this.path(...segments);
@@ -115,6 +151,11 @@ class VCSStorage {
115
151
  const filePath = this.path(...segments);
116
152
  await this.fs.writeFile(filePath, JSON.stringify(data, null, 2));
117
153
  }
154
+ assertSupportedVersion(version) {
155
+ if (version !== VCS_FORMAT_VERSION) {
156
+ throw new Error(`unsupported VCS format version ${version ?? "unknown"}; reinitialize the repository`);
157
+ }
158
+ }
118
159
  }
119
160
 
120
- //# debugId=E27DBCC23E16A74564756E2164756E21
161
+ //# debugId=CD15C789E175086364756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/vcs/storage.ts"],
4
4
  "sourcesContent": [
5
- "import type { VirtualFS } from \"../types.cjs\";\nimport type {\n HeadRef,\n BranchRef,\n RevisionCounter,\n VCSConfigFile,\n Revision,\n} from \"./types.cjs\";\n\nexport class VCSStorage {\n constructor(\n private readonly fs: VirtualFS,\n private readonly basePath: string,\n ) {}\n\n private path(...segments: string[]): string {\n return this.fs.resolve(this.basePath, ...segments);\n }\n\n // --- Init ---\n\n async isInitialized(): Promise<boolean> {\n return this.fs.exists(this.path(\"HEAD\"));\n }\n\n async initialize(defaultBranch: string = \"main\"): Promise<void> {\n await this.fs.mkdir(this.basePath, { recursive: true });\n await this.fs.mkdir(this.path(\"refs\", \"heads\"), { recursive: true });\n await this.fs.mkdir(this.path(\"revisions\"), { recursive: true });\n\n await this.writeHead({ ref: `refs/heads/${defaultBranch}` });\n await this.writeJSON([\"counter.json\"], { next: 1 } satisfies RevisionCounter);\n await this.writeJSON([\"config.json\"], { defaultBranch } satisfies VCSConfigFile);\n }\n\n // --- HEAD ---\n\n async readHead(): Promise<HeadRef> {\n return this.readJSON<HeadRef>(\"HEAD\");\n }\n\n async writeHead(head: HeadRef): Promise<void> {\n await this.writeJSON([\"HEAD\"], head);\n }\n\n // --- Branches ---\n\n async readBranch(name: string): Promise<BranchRef | null> {\n const branchPath = this.path(\"refs\", \"heads\", name);\n if (!(await this.fs.exists(branchPath))) return null;\n return this.readJSON<BranchRef>(\"refs\", \"heads\", name);\n }\n\n async writeBranch(name: string, ref: BranchRef): Promise<void> {\n await this.writeJSON([\"refs\", \"heads\", name], ref);\n }\n\n async deleteBranch(name: string): Promise<void> {\n const branchPath = this.path(\"refs\", \"heads\", name);\n await this.fs.rm(branchPath);\n }\n\n async listBranches(): Promise<string[]> {\n const headsPath = this.path(\"refs\", \"heads\");\n try {\n return await this.fs.readdir(headsPath);\n } catch {\n return [];\n }\n }\n\n // --- Revisions ---\n\n async readRevision(id: number): Promise<Revision> {\n return this.readJSON<Revision>(\"revisions\", `${id}.json`);\n }\n\n async writeRevision(rev: Revision): Promise<void> {\n await this.writeJSON([\"revisions\", `${rev.id}.json`], rev);\n }\n\n // --- Counter ---\n\n async nextRevisionId(): Promise<number> {\n const counter = await this.readJSON<RevisionCounter>(\"counter.json\");\n const id = counter.next;\n await this.writeJSON([\"counter.json\"], { next: id + 1 } satisfies RevisionCounter);\n return id;\n }\n\n // --- Config ---\n\n async readConfig(): Promise<VCSConfigFile> {\n return this.readJSON<VCSConfigFile>(\"config.json\");\n }\n\n // --- Helpers ---\n\n private async readJSON<T>(...segments: string[]): Promise<T> {\n const filePath = this.path(...segments);\n const content = await this.fs.readFile(filePath, \"utf8\");\n return JSON.parse(content) as T;\n }\n\n private async writeJSON(segments: string[], data: unknown): Promise<void> {\n const filePath = this.path(...segments);\n await this.fs.writeFile(filePath, JSON.stringify(data, null, 2));\n }\n}\n"
5
+ "import type { VirtualFS } from \"../types.cjs\";\nimport type {\n HeadRef,\n BranchRef,\n RevisionCounter,\n VCSConfigFile,\n VCSIndexEntry,\n VCSIndexFile,\n Revision,\n} from \"./types.cjs\";\n\nconst VCS_FORMAT_VERSION = 2;\n\nexport class VCSStorage {\n constructor(\n private readonly fs: VirtualFS,\n private readonly basePath: string,\n ) {}\n\n private path(...segments: string[]): string {\n return this.fs.resolve(this.basePath, ...segments);\n }\n\n // --- Init ---\n\n async isInitialized(): Promise<boolean> {\n return this.fs.exists(this.path(\"HEAD\"));\n }\n\n async initialize(defaultBranch: string = \"main\"): Promise<void> {\n await this.fs.mkdir(this.basePath, { recursive: true });\n await this.fs.mkdir(this.path(\"refs\", \"heads\"), { recursive: true });\n await this.fs.mkdir(this.path(\"revisions\"), { recursive: true });\n await this.fs.mkdir(this.path(\"objects\", \"blobs\"), { recursive: true });\n await this.fs.mkdir(this.path(\"tmp\"), { recursive: true });\n\n await this.writeHead({ ref: `refs/heads/${defaultBranch}` });\n await this.writeJSON([\"counter.json\"], { next: 1 } satisfies RevisionCounter);\n await this.writeJSON([\"config.json\"], {\n version: VCS_FORMAT_VERSION,\n defaultBranch,\n } satisfies VCSConfigFile);\n await this.writeJSON([\"index.json\"], {\n version: VCS_FORMAT_VERSION,\n entries: {},\n } satisfies VCSIndexFile);\n }\n\n // --- HEAD ---\n\n async readHead(): Promise<HeadRef> {\n return this.readJSON<HeadRef>(\"HEAD\");\n }\n\n async writeHead(head: HeadRef): Promise<void> {\n await this.writeJSON([\"HEAD\"], head);\n }\n\n // --- Branches ---\n\n async readBranch(name: string): Promise<BranchRef | null> {\n const branchPath = this.path(\"refs\", \"heads\", name);\n if (!(await this.fs.exists(branchPath))) return null;\n return this.readJSON<BranchRef>(\"refs\", \"heads\", name);\n }\n\n async writeBranch(name: string, ref: BranchRef): Promise<void> {\n await this.writeJSON([\"refs\", \"heads\", name], ref);\n }\n\n async deleteBranch(name: string): Promise<void> {\n const branchPath = this.path(\"refs\", \"heads\", name);\n await this.fs.rm(branchPath);\n }\n\n async listBranches(): Promise<string[]> {\n const headsPath = this.path(\"refs\", \"heads\");\n try {\n return await this.fs.readdir(headsPath);\n } catch {\n return [];\n }\n }\n\n // --- Revisions ---\n\n async readRevision(id: number): Promise<Revision> {\n return this.readJSON<Revision>(\"revisions\", `${id}.json`);\n }\n\n async writeRevision(rev: Revision): Promise<void> {\n await this.writeJSON([\"revisions\", `${rev.id}.json`], rev);\n }\n\n // --- Counter ---\n\n async nextRevisionId(): Promise<number> {\n const counter = await this.readJSON<RevisionCounter>(\"counter.json\");\n const id = counter.next;\n await this.writeJSON([\"counter.json\"], { next: id + 1 } satisfies RevisionCounter);\n return id;\n }\n\n // --- Config ---\n\n async readConfig(): Promise<VCSConfigFile> {\n const config = await this.readJSON<VCSConfigFile>(\"config.json\");\n this.assertSupportedVersion(config.version);\n return config;\n }\n\n async assertSupportedFormat(): Promise<void> {\n await this.readConfig();\n }\n\n async readIndex(): Promise<Record<string, VCSIndexEntry>> {\n const indexPath = this.path(\"index.json\");\n if (!(await this.fs.exists(indexPath))) {\n return {};\n }\n const index = await this.readJSON<VCSIndexFile>(\"index.json\");\n this.assertSupportedVersion(index.version);\n return index.entries;\n }\n\n async writeIndex(entries: Record<string, VCSIndexEntry>): Promise<void> {\n await this.writeJSON([\"index.json\"], {\n version: VCS_FORMAT_VERSION,\n entries,\n } satisfies VCSIndexFile);\n }\n\n resolve(...segments: string[]): string {\n return this.path(...segments);\n }\n\n get fileSystem(): VirtualFS {\n return this.fs;\n }\n\n // --- Helpers ---\n\n private async readJSON<T>(...segments: string[]): Promise<T> {\n const filePath = this.path(...segments);\n const content = await this.fs.readFile(filePath, \"utf8\");\n return JSON.parse(content) as T;\n }\n\n private async writeJSON(segments: string[], data: unknown): Promise<void> {\n const filePath = this.path(...segments);\n await this.fs.writeFile(filePath, JSON.stringify(data, null, 2));\n }\n\n private assertSupportedVersion(version: number | undefined): void {\n if (version !== VCS_FORMAT_VERSION) {\n throw new Error(\n `unsupported VCS format version ${version ?? \"unknown\"}; reinitialize the repository`,\n );\n }\n }\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "E27DBCC23E16A74564756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "CD15C789E175086364756E2164756E21",
9
9
  "names": []
10
10
  }