shell-dsl 0.0.32 → 0.0.34

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 (54) hide show
  1. package/README.md +130 -0
  2. package/dist/cjs/package.json +1 -1
  3. package/dist/cjs/src/index.cjs +3 -1
  4. package/dist/cjs/src/index.cjs.map +3 -3
  5. package/dist/cjs/src/vcs/diff.cjs +107 -0
  6. package/dist/cjs/src/vcs/diff.cjs.map +10 -0
  7. package/dist/cjs/src/vcs/index.cjs +47 -0
  8. package/dist/cjs/src/vcs/index.cjs.map +10 -0
  9. package/dist/cjs/src/vcs/match.cjs +106 -0
  10. package/dist/cjs/src/vcs/match.cjs.map +10 -0
  11. package/dist/cjs/src/vcs/rules.cjs +180 -0
  12. package/dist/cjs/src/vcs/rules.cjs.map +10 -0
  13. package/dist/cjs/src/vcs/snapshot.cjs +228 -0
  14. package/dist/cjs/src/vcs/snapshot.cjs.map +10 -0
  15. package/dist/cjs/src/vcs/storage.cjs +120 -0
  16. package/dist/cjs/src/vcs/storage.cjs.map +10 -0
  17. package/dist/cjs/src/vcs/types.cjs +30 -0
  18. package/dist/cjs/src/vcs/types.cjs.map +9 -0
  19. package/dist/cjs/src/vcs/vcs.cjs +322 -0
  20. package/dist/cjs/src/vcs/vcs.cjs.map +10 -0
  21. package/dist/cjs/src/vcs/walk.cjs +89 -0
  22. package/dist/cjs/src/vcs/walk.cjs.map +10 -0
  23. package/dist/mjs/package.json +1 -1
  24. package/dist/mjs/src/index.mjs +3 -1
  25. package/dist/mjs/src/index.mjs.map +3 -3
  26. package/dist/mjs/src/vcs/diff.mjs +67 -0
  27. package/dist/mjs/src/vcs/diff.mjs.map +10 -0
  28. package/dist/mjs/src/vcs/index.mjs +7 -0
  29. package/dist/mjs/src/vcs/index.mjs.map +10 -0
  30. package/dist/mjs/src/vcs/match.mjs +66 -0
  31. package/dist/mjs/src/vcs/match.mjs.map +10 -0
  32. package/dist/mjs/src/vcs/rules.mjs +140 -0
  33. package/dist/mjs/src/vcs/rules.mjs.map +10 -0
  34. package/dist/mjs/src/vcs/snapshot.mjs +188 -0
  35. package/dist/mjs/src/vcs/snapshot.mjs.map +10 -0
  36. package/dist/mjs/src/vcs/storage.mjs +79 -0
  37. package/dist/mjs/src/vcs/storage.mjs.map +10 -0
  38. package/dist/mjs/src/vcs/types.mjs +2 -0
  39. package/dist/mjs/src/vcs/types.mjs.map +9 -0
  40. package/dist/mjs/src/vcs/vcs.mjs +282 -0
  41. package/dist/mjs/src/vcs/vcs.mjs.map +10 -0
  42. package/dist/mjs/src/vcs/walk.mjs +49 -0
  43. package/dist/mjs/src/vcs/walk.mjs.map +10 -0
  44. package/dist/types/src/index.d.ts +2 -0
  45. package/dist/types/src/vcs/diff.d.ts +11 -0
  46. package/dist/types/src/vcs/index.d.ts +2 -0
  47. package/dist/types/src/vcs/match.d.ts +5 -0
  48. package/dist/types/src/vcs/rules.d.ts +23 -0
  49. package/dist/types/src/vcs/snapshot.d.ts +26 -0
  50. package/dist/types/src/vcs/storage.d.ts +22 -0
  51. package/dist/types/src/vcs/types.d.ts +99 -0
  52. package/dist/types/src/vcs/vcs.d.ts +35 -0
  53. package/dist/types/src/vcs/walk.d.ts +19 -0
  54. package/package.json +1 -1
@@ -0,0 +1,66 @@
1
+ // src/vcs/match.ts
2
+ function matchGlobPath(pattern, filePath) {
3
+ const p = pattern.startsWith("/") ? pattern.slice(1) : pattern;
4
+ const f = filePath.startsWith("/") ? filePath.slice(1) : filePath;
5
+ const patternParts = p.split("/");
6
+ const pathParts = f.split("/");
7
+ return matchParts(patternParts, pathParts, 0, 0);
8
+ }
9
+ function matchParts(pattern, path, pi, fi) {
10
+ while (pi < pattern.length && fi < path.length) {
11
+ const seg = pattern[pi];
12
+ if (seg === "**") {
13
+ for (let i = fi;i <= path.length; i++) {
14
+ if (matchParts(pattern, path, pi + 1, i))
15
+ return true;
16
+ }
17
+ return false;
18
+ }
19
+ if (!matchSegment(seg, path[fi]))
20
+ return false;
21
+ pi++;
22
+ fi++;
23
+ }
24
+ while (pi < pattern.length && pattern[pi] === "**")
25
+ pi++;
26
+ return pi === pattern.length && fi === path.length;
27
+ }
28
+ function matchSegment(pattern, segment) {
29
+ let regex = "^";
30
+ for (let i = 0;i < pattern.length; i++) {
31
+ const c = pattern[i];
32
+ switch (c) {
33
+ case "*":
34
+ regex += "[^/]*";
35
+ break;
36
+ case "?":
37
+ regex += "[^/]";
38
+ break;
39
+ case ".":
40
+ case "^":
41
+ case "$":
42
+ case "+":
43
+ case "{":
44
+ case "}":
45
+ case "(":
46
+ case ")":
47
+ case "|":
48
+ case "\\":
49
+ regex += "\\" + c;
50
+ break;
51
+ default:
52
+ regex += c;
53
+ }
54
+ }
55
+ regex += "$";
56
+ try {
57
+ return new RegExp(regex).test(segment);
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+ export {
63
+ matchGlobPath
64
+ };
65
+
66
+ //# debugId=D1FF0D8815E8C2D864756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/vcs/match.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Match a file path against a glob pattern.\n * Supports `**` for recursive directory matching, `*` for single segment wildcard.\n */\nexport function matchGlobPath(pattern: string, filePath: string): boolean {\n // Normalize: strip leading slashes\n const p = pattern.startsWith(\"/\") ? pattern.slice(1) : pattern;\n const f = filePath.startsWith(\"/\") ? filePath.slice(1) : filePath;\n\n const patternParts = p.split(\"/\");\n const pathParts = f.split(\"/\");\n\n return matchParts(patternParts, pathParts, 0, 0);\n}\n\nfunction matchParts(\n pattern: string[],\n path: string[],\n pi: number,\n fi: number,\n): boolean {\n while (pi < pattern.length && fi < path.length) {\n const seg = pattern[pi]!;\n\n if (seg === \"**\") {\n // ** matches zero or more path segments\n // Try matching rest of pattern against current position and all subsequent positions\n for (let i = fi; i <= path.length; i++) {\n if (matchParts(pattern, path, pi + 1, i)) return true;\n }\n return false;\n }\n\n if (!matchSegment(seg, path[fi]!)) return false;\n pi++;\n fi++;\n }\n\n // Skip trailing ** patterns\n while (pi < pattern.length && pattern[pi] === \"**\") pi++;\n\n return pi === pattern.length && fi === path.length;\n}\n\nfunction matchSegment(pattern: string, segment: string): boolean {\n // Convert glob segment to regex\n let regex = \"^\";\n for (let i = 0; i < pattern.length; i++) {\n const c = pattern[i]!;\n switch (c) {\n case \"*\":\n regex += \"[^/]*\";\n break;\n case \"?\":\n regex += \"[^/]\";\n break;\n case \".\":\n case \"^\":\n case \"$\":\n case \"+\":\n case \"{\":\n case \"}\":\n case \"(\":\n case \")\":\n case \"|\":\n case \"\\\\\":\n regex += \"\\\\\" + c;\n break;\n default:\n regex += c;\n }\n }\n regex += \"$\";\n\n try {\n return new RegExp(regex).test(segment);\n } catch {\n return false;\n }\n}\n"
6
+ ],
7
+ "mappings": ";AAIO,SAAS,aAAa,CAAC,SAAiB,UAA2B;AAAA,EAExE,MAAM,IAAI,QAAQ,WAAW,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAI;AAAA,EACvD,MAAM,IAAI,SAAS,WAAW,GAAG,IAAI,SAAS,MAAM,CAAC,IAAI;AAAA,EAEzD,MAAM,eAAe,EAAE,MAAM,GAAG;AAAA,EAChC,MAAM,YAAY,EAAE,MAAM,GAAG;AAAA,EAE7B,OAAO,WAAW,cAAc,WAAW,GAAG,CAAC;AAAA;AAGjD,SAAS,UAAU,CACjB,SACA,MACA,IACA,IACS;AAAA,EACT,OAAO,KAAK,QAAQ,UAAU,KAAK,KAAK,QAAQ;AAAA,IAC9C,MAAM,MAAM,QAAQ;AAAA,IAEpB,IAAI,QAAQ,MAAM;AAAA,MAGhB,SAAS,IAAI,GAAI,KAAK,KAAK,QAAQ,KAAK;AAAA,QACtC,IAAI,WAAW,SAAS,MAAM,KAAK,GAAG,CAAC;AAAA,UAAG,OAAO;AAAA,MACnD;AAAA,MACA,OAAO;AAAA,IACT;AAAA,IAEA,IAAI,CAAC,aAAa,KAAK,KAAK,GAAI;AAAA,MAAG,OAAO;AAAA,IAC1C;AAAA,IACA;AAAA,EACF;AAAA,EAGA,OAAO,KAAK,QAAQ,UAAU,QAAQ,QAAQ;AAAA,IAAM;AAAA,EAEpD,OAAO,OAAO,QAAQ,UAAU,OAAO,KAAK;AAAA;AAG9C,SAAS,YAAY,CAAC,SAAiB,SAA0B;AAAA,EAE/D,IAAI,QAAQ;AAAA,EACZ,SAAS,IAAI,EAAG,IAAI,QAAQ,QAAQ,KAAK;AAAA,IACvC,MAAM,IAAI,QAAQ;AAAA,IAClB,QAAQ;AAAA,WACD;AAAA,QACH,SAAS;AAAA,QACT;AAAA,WACG;AAAA,QACH,SAAS;AAAA,QACT;AAAA,WACG;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,QACH,SAAS,OAAO;AAAA,QAChB;AAAA;AAAA,QAEA,SAAS;AAAA;AAAA,EAEf;AAAA,EACA,SAAS;AAAA,EAET,IAAI;AAAA,IACF,OAAO,IAAI,OAAO,KAAK,EAAE,KAAK,OAAO;AAAA,IACrC,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;",
8
+ "debugId": "D1FF0D8815E8C2D864756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,140 @@
1
+ // src/vcs/rules.ts
2
+ import { matchGlobPath } from "./match.mjs";
3
+
4
+ class VCSRules {
5
+ internalPath;
6
+ ignorePatterns;
7
+ attributeRules;
8
+ constructor(config = {}) {
9
+ this.internalPath = normalizePath(config.internalPath ?? config.internalDirName ?? "");
10
+ this.ignorePatterns = [...config.ignore ?? []];
11
+ this.attributeRules = [...config.attributes ?? []];
12
+ }
13
+ isInternalPath(relPath) {
14
+ if (!this.internalPath)
15
+ return false;
16
+ const normalizedPath = normalizePath(relPath);
17
+ if (!normalizedPath)
18
+ return false;
19
+ return normalizedPath === this.internalPath || normalizedPath.startsWith(`${this.internalPath}/`);
20
+ }
21
+ isIgnored(relPath) {
22
+ if (this.isInternalPath(relPath))
23
+ return true;
24
+ return this.ignorePatterns.some((pattern) => matchVCSPath(pattern, relPath));
25
+ }
26
+ shouldEnterDirectory(relPath, trackedPaths) {
27
+ if (this.isInternalPath(relPath))
28
+ return false;
29
+ if (!this.isIgnored(relPath))
30
+ return true;
31
+ return hasTrackedPathAtOrBelow(relPath, trackedPaths);
32
+ }
33
+ shouldIncludeWorkingFile(relPath, trackedPaths) {
34
+ if (this.isInternalPath(relPath))
35
+ return false;
36
+ if (trackedPaths.has(relPath))
37
+ return true;
38
+ return !this.isIgnored(relPath);
39
+ }
40
+ shouldIncludeEmptyDirectory(relPath, trackedPaths) {
41
+ if (this.isInternalPath(relPath))
42
+ return false;
43
+ if (trackedPaths.has(relPath))
44
+ return true;
45
+ return !this.isIgnored(relPath);
46
+ }
47
+ shouldIncludeRestoreScanFile(relPath) {
48
+ return !this.isInternalPath(relPath);
49
+ }
50
+ shouldPreserveUntrackedIgnored(relPath, trackedPaths) {
51
+ if (trackedPaths.has(relPath))
52
+ return false;
53
+ return this.isIgnored(relPath);
54
+ }
55
+ resolveAttributes(relPath) {
56
+ let binary = false;
57
+ let diff = "text";
58
+ for (const rule of this.attributeRules) {
59
+ if (!matchVCSPath(rule.pattern, relPath))
60
+ continue;
61
+ if (rule.binary !== undefined) {
62
+ binary = rule.binary;
63
+ }
64
+ if (rule.diff !== undefined) {
65
+ diff = rule.diff;
66
+ }
67
+ }
68
+ if (diff === "binary") {
69
+ binary = true;
70
+ }
71
+ return { binary, diff };
72
+ }
73
+ }
74
+ function matchVCSPath(pattern, relPath) {
75
+ const normalizedPattern = normalizePattern(pattern);
76
+ const normalizedPath = normalizePath(relPath);
77
+ if (!normalizedPattern || !normalizedPath)
78
+ return false;
79
+ if (normalizedPattern.mode === "root-path") {
80
+ return matchGlobPath(normalizedPattern.pattern, normalizedPath);
81
+ }
82
+ if (normalizedPattern.mode === "root-segment") {
83
+ const [firstSegment] = normalizedPath.split("/");
84
+ return firstSegment ? matchGlobPath(normalizedPattern.pattern, firstSegment) : false;
85
+ }
86
+ if (normalizedPattern.mode === "root-prefix") {
87
+ return matchGlobPath(normalizedPattern.pattern, normalizedPath) || matchGlobPath(`${normalizedPattern.pattern}/**`, normalizedPath);
88
+ }
89
+ const segments = normalizedPath.split("/");
90
+ return segments.some((segment) => matchGlobPath(normalizedPattern.pattern, segment));
91
+ }
92
+ function normalizePath(relPath) {
93
+ return relPath.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
94
+ }
95
+ function normalizePattern(pattern) {
96
+ let normalized = pattern.trim();
97
+ if (!normalized)
98
+ return null;
99
+ normalized = normalized.replace(/\\/g, "/");
100
+ const anchored = normalized.startsWith("/");
101
+ const directoryOnly = normalized.endsWith("/");
102
+ normalized = normalized.replace(/^\/+/, "").replace(/\/+$/, "");
103
+ if (!normalized)
104
+ return null;
105
+ if (normalized.includes("/")) {
106
+ return {
107
+ pattern: normalized,
108
+ mode: directoryOnly ? "root-prefix" : "root-path"
109
+ };
110
+ }
111
+ if (anchored) {
112
+ return {
113
+ pattern: normalized,
114
+ mode: "root-segment"
115
+ };
116
+ }
117
+ return {
118
+ pattern: normalized,
119
+ mode: "segment"
120
+ };
121
+ }
122
+ function hasTrackedPathAtOrBelow(relPath, trackedPaths) {
123
+ const normalizedPath = normalizePath(relPath);
124
+ if (!normalizedPath)
125
+ return false;
126
+ const prefix = normalizedPath + "/";
127
+ for (const trackedPath of trackedPaths) {
128
+ const normalizedTrackedPath = normalizePath(trackedPath);
129
+ if (normalizedTrackedPath === normalizedPath || normalizedTrackedPath.startsWith(prefix)) {
130
+ return true;
131
+ }
132
+ }
133
+ return false;
134
+ }
135
+ export {
136
+ matchVCSPath,
137
+ VCSRules
138
+ };
139
+
140
+ //# debugId=42FBA1E942F9F1FC64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/vcs/rules.ts"],
4
+ "sourcesContent": [
5
+ "import { matchGlobPath } from \"./match.mjs\";\nimport type { VCSAttributeRule, VCSResolvedAttributes } from \"./types.mjs\";\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"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "42FBA1E942F9F1FC64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,188 @@
1
+ // src/vcs/snapshot.ts
2
+ import { matchVCSPath, VCSRules } from "./rules.mjs";
3
+ import { walkTreeEntries } from "./walk.mjs";
4
+ async function buildTreeManifest(fs, rootPath, options) {
5
+ const manifest = {};
6
+ const rules = options?.rules ?? new VCSRules({ internalDirName: ".vcs" });
7
+ const trackedPaths = new Set(options?.trackedPaths ?? []);
8
+ const entries = await walkTreeEntries(fs, rootPath, {
9
+ enterDirectory: (relPath) => rules.shouldEnterDirectory(relPath, trackedPaths),
10
+ includeFile: (relPath) => rules.shouldIncludeWorkingFile(relPath, trackedPaths),
11
+ includeDirectory: (relPath, info) => info.empty && rules.shouldIncludeEmptyDirectory(relPath, trackedPaths)
12
+ });
13
+ for (const entry of entries) {
14
+ if (entry.kind === "directory") {
15
+ manifest[entry.path] = { kind: "directory", size: 0 };
16
+ continue;
17
+ }
18
+ const fullPath = fs.resolve(rootPath, entry.path);
19
+ const content = await fs.readFile(fullPath);
20
+ const buf = Buffer.from(content);
21
+ manifest[entry.path] = {
22
+ kind: "file",
23
+ content: buf.toString("base64"),
24
+ size: buf.length
25
+ };
26
+ }
27
+ return manifest;
28
+ }
29
+ async function buildPartialManifest(fs, rootPath, paths) {
30
+ const manifest = {};
31
+ for (const relPath of paths) {
32
+ const fullPath = fs.resolve(rootPath, relPath);
33
+ if (!await fs.exists(fullPath))
34
+ continue;
35
+ const stat = await fs.stat(fullPath);
36
+ if (stat.isDirectory()) {
37
+ const entries = await fs.readdir(fullPath);
38
+ if (entries.length === 0) {
39
+ manifest[relPath] = { kind: "directory", size: 0 };
40
+ }
41
+ continue;
42
+ }
43
+ if (!stat.isFile())
44
+ continue;
45
+ const content = await fs.readFile(fullPath);
46
+ const buf = Buffer.from(content);
47
+ manifest[relPath] = {
48
+ kind: "file",
49
+ content: buf.toString("base64"),
50
+ size: buf.length
51
+ };
52
+ }
53
+ return manifest;
54
+ }
55
+ async function restoreTree(fs, rootPath, manifest, options) {
56
+ const fullRestore = options?.fullRestore ?? false;
57
+ const rules = options?.rules ?? new VCSRules({ internalDirName: ".vcs" });
58
+ const trackedPaths = new Set(options?.trackedPaths ?? []);
59
+ const scopePatterns = options?.paths ?? null;
60
+ const scopedEntries = Object.entries(manifest).filter(([relPath]) => isPathInScope(relPath, scopePatterns)).sort(([a], [b]) => a.localeCompare(b));
61
+ const targetPaths = new Set(scopedEntries.map(([relPath]) => relPath));
62
+ const requiredDirectories = collectRequiredDirectories(scopedEntries);
63
+ const shouldDeleteExtras = fullRestore || scopePatterns !== null;
64
+ if (shouldDeleteExtras) {
65
+ const currentEntries = await walkTreeEntries(fs, rootPath, {
66
+ enterDirectory: (relPath) => !rules.isInternalPath(relPath),
67
+ includeFile: (relPath) => rules.shouldIncludeRestoreScanFile(relPath),
68
+ includeDirectory: () => true
69
+ });
70
+ for (const current of currentEntries) {
71
+ if (current.kind !== "file")
72
+ continue;
73
+ if (!isPathInScope(current.path, scopePatterns))
74
+ continue;
75
+ if (targetPaths.has(current.path))
76
+ continue;
77
+ if (rules.shouldPreserveUntrackedIgnored(current.path, trackedPaths))
78
+ continue;
79
+ await fs.rm(fs.resolve(rootPath, current.path));
80
+ }
81
+ }
82
+ for (const directory of [...requiredDirectories].sort(comparePathDepth)) {
83
+ await ensureDirectoryExists(fs, fs.resolve(rootPath, directory));
84
+ }
85
+ for (const [relPath, entry] of scopedEntries) {
86
+ if (isDirectoryEntry(entry)) {
87
+ await ensureDirectoryExists(fs, fs.resolve(rootPath, relPath));
88
+ continue;
89
+ }
90
+ await writeFileFromEntry(fs, rootPath, relPath, entry);
91
+ }
92
+ if (shouldDeleteExtras) {
93
+ const currentEntries = await walkTreeEntries(fs, rootPath, {
94
+ enterDirectory: (relPath) => !rules.isInternalPath(relPath),
95
+ includeFile: () => true,
96
+ includeDirectory: () => true
97
+ });
98
+ const directories = currentEntries.filter((entry) => entry.kind === "directory").map((entry) => entry.path).filter((relPath) => isPathInScope(relPath, scopePatterns)).sort((a, b) => comparePathDepth(b, a));
99
+ for (const relPath of directories) {
100
+ if (requiredDirectories.has(relPath))
101
+ continue;
102
+ if (rules.shouldPreserveUntrackedIgnored(relPath, trackedPaths))
103
+ continue;
104
+ const fullPath = fs.resolve(rootPath, relPath);
105
+ if (await isEmptyDirectory(fs, fullPath)) {
106
+ await fs.rm(fullPath);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ async function writeFileFromEntry(fs, rootPath, relPath, entry) {
112
+ const fullPath = fs.resolve(rootPath, relPath);
113
+ await ensureDirectoryExists(fs, fs.dirname(fullPath));
114
+ await removeDirectoryAtPath(fs, fullPath);
115
+ const buf = Buffer.from(entry.content, "base64");
116
+ await fs.writeFile(fullPath, buf);
117
+ }
118
+ function isDirectoryEntry(entry) {
119
+ return entry.kind === "directory";
120
+ }
121
+ function isPathInScope(relPath, patterns) {
122
+ if (!patterns || patterns.length === 0)
123
+ return true;
124
+ return patterns.some((pattern) => matchVCSPath(pattern, relPath));
125
+ }
126
+ function collectRequiredDirectories(entries) {
127
+ const directories = new Set;
128
+ for (const [relPath, entry] of entries) {
129
+ if (isDirectoryEntry(entry)) {
130
+ directories.add(relPath);
131
+ }
132
+ for (const parent of parentDirectories(relPath)) {
133
+ directories.add(parent);
134
+ }
135
+ }
136
+ return directories;
137
+ }
138
+ function parentDirectories(relPath) {
139
+ const parts = relPath.split("/").filter(Boolean);
140
+ const parents = [];
141
+ for (let i = 1;i < parts.length; i++) {
142
+ parents.push(parts.slice(0, i).join("/"));
143
+ }
144
+ return parents;
145
+ }
146
+ function comparePathDepth(a, b) {
147
+ const depthA = a.split("/").filter(Boolean).length;
148
+ const depthB = b.split("/").filter(Boolean).length;
149
+ if (depthA !== depthB)
150
+ return depthA - depthB;
151
+ return a.localeCompare(b);
152
+ }
153
+ async function ensureDirectoryExists(fs, dirPath) {
154
+ const parent = fs.dirname(dirPath);
155
+ if (parent !== dirPath) {
156
+ await ensureDirectoryExists(fs, parent);
157
+ }
158
+ if (await fs.exists(dirPath)) {
159
+ const stat = await fs.stat(dirPath);
160
+ if (stat.isDirectory())
161
+ return;
162
+ await fs.rm(dirPath, { recursive: true, force: true });
163
+ }
164
+ await fs.mkdir(dirPath, { recursive: true });
165
+ }
166
+ async function removeDirectoryAtPath(fs, path) {
167
+ if (!await fs.exists(path))
168
+ return;
169
+ const stat = await fs.stat(path);
170
+ if (stat.isDirectory()) {
171
+ await fs.rm(path, { recursive: true, force: true });
172
+ }
173
+ }
174
+ async function isEmptyDirectory(fs, dirPath) {
175
+ try {
176
+ const entries = await fs.readdir(dirPath);
177
+ return entries.length === 0;
178
+ } catch {
179
+ return false;
180
+ }
181
+ }
182
+ export {
183
+ restoreTree,
184
+ buildTreeManifest,
185
+ buildPartialManifest
186
+ };
187
+
188
+ //# debugId=254AE8D9DB8E9AE464756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/vcs/snapshot.ts"],
4
+ "sourcesContent": [
5
+ "import type { VirtualFS } from \"../types.mjs\";\nimport type { TreeManifest, FileEntry, TreeEntry } from \"./types.mjs\";\nimport { matchVCSPath, VCSRules } from \"./rules.mjs\";\nimport { walkTreeEntries } from \"./walk.mjs\";\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"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "254AE8D9DB8E9AE464756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,79 @@
1
+ // src/vcs/storage.ts
2
+ class VCSStorage {
3
+ fs;
4
+ basePath;
5
+ constructor(fs, basePath) {
6
+ this.fs = fs;
7
+ this.basePath = basePath;
8
+ }
9
+ path(...segments) {
10
+ return this.fs.resolve(this.basePath, ...segments);
11
+ }
12
+ async isInitialized() {
13
+ return this.fs.exists(this.path("HEAD"));
14
+ }
15
+ async initialize(defaultBranch = "main") {
16
+ await this.fs.mkdir(this.basePath, { recursive: true });
17
+ await this.fs.mkdir(this.path("refs", "heads"), { recursive: true });
18
+ await this.fs.mkdir(this.path("revisions"), { recursive: true });
19
+ await this.writeHead({ ref: `refs/heads/${defaultBranch}` });
20
+ await this.writeJSON(["counter.json"], { next: 1 });
21
+ await this.writeJSON(["config.json"], { defaultBranch });
22
+ }
23
+ async readHead() {
24
+ return this.readJSON("HEAD");
25
+ }
26
+ async writeHead(head) {
27
+ await this.writeJSON(["HEAD"], head);
28
+ }
29
+ async readBranch(name) {
30
+ const branchPath = this.path("refs", "heads", name);
31
+ if (!await this.fs.exists(branchPath))
32
+ return null;
33
+ return this.readJSON("refs", "heads", name);
34
+ }
35
+ async writeBranch(name, ref) {
36
+ await this.writeJSON(["refs", "heads", name], ref);
37
+ }
38
+ async deleteBranch(name) {
39
+ const branchPath = this.path("refs", "heads", name);
40
+ await this.fs.rm(branchPath);
41
+ }
42
+ async listBranches() {
43
+ const headsPath = this.path("refs", "heads");
44
+ try {
45
+ return await this.fs.readdir(headsPath);
46
+ } catch {
47
+ return [];
48
+ }
49
+ }
50
+ async readRevision(id) {
51
+ return this.readJSON("revisions", `${id}.json`);
52
+ }
53
+ async writeRevision(rev) {
54
+ await this.writeJSON(["revisions", `${rev.id}.json`], rev);
55
+ }
56
+ async nextRevisionId() {
57
+ const counter = await this.readJSON("counter.json");
58
+ const id = counter.next;
59
+ await this.writeJSON(["counter.json"], { next: id + 1 });
60
+ return id;
61
+ }
62
+ async readConfig() {
63
+ return this.readJSON("config.json");
64
+ }
65
+ async readJSON(...segments) {
66
+ const filePath = this.path(...segments);
67
+ const content = await this.fs.readFile(filePath, "utf8");
68
+ return JSON.parse(content);
69
+ }
70
+ async writeJSON(segments, data) {
71
+ const filePath = this.path(...segments);
72
+ await this.fs.writeFile(filePath, JSON.stringify(data, null, 2));
73
+ }
74
+ }
75
+ export {
76
+ VCSStorage
77
+ };
78
+
79
+ //# debugId=63A4490666B1400E64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/vcs/storage.ts"],
4
+ "sourcesContent": [
5
+ "import type { VirtualFS } from \"../types.mjs\";\nimport type {\n HeadRef,\n BranchRef,\n RevisionCounter,\n VCSConfigFile,\n Revision,\n} from \"./types.mjs\";\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"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "63A4490666B1400E64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,2 @@
1
+
2
+ //# debugId=26C529951542DBDE64756E2164756E21
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [
5
+ ],
6
+ "mappings": "",
7
+ "debugId": "26C529951542DBDE64756E2164756E21",
8
+ "names": []
9
+ }