pushwork 2.0.0-a.sub.0 → 2.0.0-preview

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 (251) hide show
  1. package/dist/branches.d.ts +19 -0
  2. package/dist/branches.d.ts.map +1 -0
  3. package/dist/branches.js +111 -0
  4. package/dist/branches.js.map +1 -0
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +238 -272
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config.d.ts +17 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +84 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/fs-tree.d.ts +6 -0
  14. package/dist/fs-tree.d.ts.map +1 -0
  15. package/dist/fs-tree.js +99 -0
  16. package/dist/fs-tree.js.map +1 -0
  17. package/dist/ignore.d.ts +6 -0
  18. package/dist/ignore.d.ts.map +1 -0
  19. package/dist/ignore.js +74 -0
  20. package/dist/ignore.js.map +1 -0
  21. package/dist/index.d.ts +8 -4
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +34 -4
  24. package/dist/index.js.map +1 -1
  25. package/dist/log.d.ts +3 -0
  26. package/dist/log.d.ts.map +1 -0
  27. package/dist/log.js +14 -0
  28. package/dist/log.js.map +1 -0
  29. package/dist/pushwork.d.ts +115 -0
  30. package/dist/pushwork.d.ts.map +1 -0
  31. package/dist/pushwork.js +918 -0
  32. package/dist/pushwork.js.map +1 -0
  33. package/dist/repo.d.ts +14 -0
  34. package/dist/repo.d.ts.map +1 -0
  35. package/dist/repo.js +60 -0
  36. package/dist/repo.js.map +1 -0
  37. package/dist/shapes/custom.d.ts +3 -0
  38. package/dist/shapes/custom.d.ts.map +1 -0
  39. package/dist/shapes/custom.js +57 -0
  40. package/dist/shapes/custom.js.map +1 -0
  41. package/dist/shapes/file.d.ts +20 -0
  42. package/dist/shapes/file.d.ts.map +1 -0
  43. package/dist/shapes/file.js +140 -0
  44. package/dist/shapes/file.js.map +1 -0
  45. package/dist/shapes/index.d.ts +10 -0
  46. package/dist/shapes/index.d.ts.map +1 -0
  47. package/dist/shapes/index.js +35 -0
  48. package/dist/shapes/index.js.map +1 -0
  49. package/dist/shapes/patchwork-folder.d.ts +3 -0
  50. package/dist/shapes/patchwork-folder.d.ts.map +1 -0
  51. package/dist/shapes/patchwork-folder.js +160 -0
  52. package/dist/shapes/patchwork-folder.js.map +1 -0
  53. package/dist/shapes/types.d.ts +37 -0
  54. package/dist/shapes/types.d.ts.map +1 -0
  55. package/dist/shapes/types.js +52 -0
  56. package/dist/shapes/types.js.map +1 -0
  57. package/dist/shapes/vfs.d.ts +3 -0
  58. package/dist/shapes/vfs.d.ts.map +1 -0
  59. package/dist/shapes/vfs.js +88 -0
  60. package/dist/shapes/vfs.js.map +1 -0
  61. package/dist/stash.d.ts +23 -0
  62. package/dist/stash.d.ts.map +1 -0
  63. package/dist/stash.js +118 -0
  64. package/dist/stash.js.map +1 -0
  65. package/flake.lock +128 -0
  66. package/flake.nix +66 -0
  67. package/package.json +15 -48
  68. package/patches/@automerge__automerge-repo@2.6.0-subduction.15.patch +26 -0
  69. package/pnpm-workspace.yaml +5 -0
  70. package/src/branches.ts +93 -0
  71. package/src/cli.ts +258 -408
  72. package/src/config.ts +64 -0
  73. package/src/fs-tree.ts +70 -0
  74. package/src/ignore.ts +33 -0
  75. package/src/index.ts +38 -4
  76. package/src/log.ts +8 -0
  77. package/src/pushwork.ts +1055 -0
  78. package/src/repo.ts +76 -0
  79. package/src/shapes/custom.ts +29 -0
  80. package/src/shapes/file.ts +115 -0
  81. package/src/shapes/index.ts +19 -0
  82. package/src/shapes/patchwork-folder.ts +156 -0
  83. package/src/shapes/types.ts +79 -0
  84. package/src/shapes/vfs.ts +93 -0
  85. package/src/stash.ts +106 -0
  86. package/test/integration/branches.test.ts +389 -0
  87. package/test/integration/pushwork.test.ts +547 -0
  88. package/test/setup.ts +29 -0
  89. package/test/unit/doc-shape.test.ts +612 -0
  90. package/tsconfig.json +2 -3
  91. package/vitest.config.ts +14 -0
  92. package/ARCHITECTURE-ACCORDING-TO-CLAUDE.md +0 -248
  93. package/CLAUDE.md +0 -141
  94. package/README.md +0 -221
  95. package/babel.config.js +0 -5
  96. package/dist/cli/commands.d.ts +0 -71
  97. package/dist/cli/commands.d.ts.map +0 -1
  98. package/dist/cli/commands.js +0 -794
  99. package/dist/cli/commands.js.map +0 -1
  100. package/dist/cli/index.d.ts +0 -2
  101. package/dist/cli/index.d.ts.map +0 -1
  102. package/dist/cli/index.js +0 -19
  103. package/dist/cli/index.js.map +0 -1
  104. package/dist/commands.d.ts +0 -61
  105. package/dist/commands.d.ts.map +0 -1
  106. package/dist/commands.js +0 -861
  107. package/dist/commands.js.map +0 -1
  108. package/dist/config/index.d.ts +0 -71
  109. package/dist/config/index.d.ts.map +0 -1
  110. package/dist/config/index.js +0 -314
  111. package/dist/config/index.js.map +0 -1
  112. package/dist/core/change-detection.d.ts +0 -80
  113. package/dist/core/change-detection.d.ts.map +0 -1
  114. package/dist/core/change-detection.js +0 -523
  115. package/dist/core/change-detection.js.map +0 -1
  116. package/dist/core/config.d.ts +0 -81
  117. package/dist/core/config.d.ts.map +0 -1
  118. package/dist/core/config.js +0 -258
  119. package/dist/core/config.js.map +0 -1
  120. package/dist/core/index.d.ts +0 -6
  121. package/dist/core/index.d.ts.map +0 -1
  122. package/dist/core/index.js +0 -6
  123. package/dist/core/index.js.map +0 -1
  124. package/dist/core/move-detection.d.ts +0 -34
  125. package/dist/core/move-detection.d.ts.map +0 -1
  126. package/dist/core/move-detection.js +0 -121
  127. package/dist/core/move-detection.js.map +0 -1
  128. package/dist/core/snapshot.d.ts +0 -105
  129. package/dist/core/snapshot.d.ts.map +0 -1
  130. package/dist/core/snapshot.js +0 -217
  131. package/dist/core/snapshot.js.map +0 -1
  132. package/dist/core/sync-engine.d.ts +0 -151
  133. package/dist/core/sync-engine.d.ts.map +0 -1
  134. package/dist/core/sync-engine.js +0 -1346
  135. package/dist/core/sync-engine.js.map +0 -1
  136. package/dist/types/config.d.ts +0 -99
  137. package/dist/types/config.d.ts.map +0 -1
  138. package/dist/types/config.js +0 -5
  139. package/dist/types/config.js.map +0 -1
  140. package/dist/types/documents.d.ts +0 -88
  141. package/dist/types/documents.d.ts.map +0 -1
  142. package/dist/types/documents.js +0 -20
  143. package/dist/types/documents.js.map +0 -1
  144. package/dist/types/index.d.ts +0 -4
  145. package/dist/types/index.d.ts.map +0 -1
  146. package/dist/types/index.js +0 -4
  147. package/dist/types/index.js.map +0 -1
  148. package/dist/types/snapshot.d.ts +0 -64
  149. package/dist/types/snapshot.d.ts.map +0 -1
  150. package/dist/types/snapshot.js +0 -2
  151. package/dist/types/snapshot.js.map +0 -1
  152. package/dist/utils/content-similarity.d.ts +0 -53
  153. package/dist/utils/content-similarity.d.ts.map +0 -1
  154. package/dist/utils/content-similarity.js +0 -155
  155. package/dist/utils/content-similarity.js.map +0 -1
  156. package/dist/utils/content.d.ts +0 -10
  157. package/dist/utils/content.d.ts.map +0 -1
  158. package/dist/utils/content.js +0 -31
  159. package/dist/utils/content.js.map +0 -1
  160. package/dist/utils/directory.d.ts +0 -24
  161. package/dist/utils/directory.d.ts.map +0 -1
  162. package/dist/utils/directory.js +0 -52
  163. package/dist/utils/directory.js.map +0 -1
  164. package/dist/utils/fs.d.ts +0 -74
  165. package/dist/utils/fs.d.ts.map +0 -1
  166. package/dist/utils/fs.js +0 -248
  167. package/dist/utils/fs.js.map +0 -1
  168. package/dist/utils/index.d.ts +0 -5
  169. package/dist/utils/index.d.ts.map +0 -1
  170. package/dist/utils/index.js +0 -5
  171. package/dist/utils/index.js.map +0 -1
  172. package/dist/utils/mime-types.d.ts +0 -13
  173. package/dist/utils/mime-types.d.ts.map +0 -1
  174. package/dist/utils/mime-types.js +0 -209
  175. package/dist/utils/mime-types.js.map +0 -1
  176. package/dist/utils/network-sync.d.ts +0 -36
  177. package/dist/utils/network-sync.d.ts.map +0 -1
  178. package/dist/utils/network-sync.js +0 -250
  179. package/dist/utils/network-sync.js.map +0 -1
  180. package/dist/utils/node-polyfills.d.ts +0 -9
  181. package/dist/utils/node-polyfills.d.ts.map +0 -1
  182. package/dist/utils/node-polyfills.js +0 -9
  183. package/dist/utils/node-polyfills.js.map +0 -1
  184. package/dist/utils/output.d.ts +0 -129
  185. package/dist/utils/output.d.ts.map +0 -1
  186. package/dist/utils/output.js +0 -368
  187. package/dist/utils/output.js.map +0 -1
  188. package/dist/utils/repo-factory.d.ts +0 -13
  189. package/dist/utils/repo-factory.d.ts.map +0 -1
  190. package/dist/utils/repo-factory.js +0 -46
  191. package/dist/utils/repo-factory.js.map +0 -1
  192. package/dist/utils/string-similarity.d.ts +0 -14
  193. package/dist/utils/string-similarity.d.ts.map +0 -1
  194. package/dist/utils/string-similarity.js +0 -39
  195. package/dist/utils/string-similarity.js.map +0 -1
  196. package/dist/utils/text-diff.d.ts +0 -37
  197. package/dist/utils/text-diff.d.ts.map +0 -1
  198. package/dist/utils/text-diff.js +0 -93
  199. package/dist/utils/text-diff.js.map +0 -1
  200. package/dist/utils/trace.d.ts +0 -19
  201. package/dist/utils/trace.d.ts.map +0 -1
  202. package/dist/utils/trace.js +0 -63
  203. package/dist/utils/trace.js.map +0 -1
  204. package/src/commands.ts +0 -1134
  205. package/src/core/change-detection.ts +0 -712
  206. package/src/core/config.ts +0 -313
  207. package/src/core/index.ts +0 -5
  208. package/src/core/move-detection.ts +0 -169
  209. package/src/core/snapshot.ts +0 -275
  210. package/src/core/sync-engine.ts +0 -1758
  211. package/src/types/config.ts +0 -111
  212. package/src/types/documents.ts +0 -91
  213. package/src/types/index.ts +0 -3
  214. package/src/types/snapshot.ts +0 -67
  215. package/src/utils/content.ts +0 -34
  216. package/src/utils/directory.ts +0 -73
  217. package/src/utils/fs.ts +0 -297
  218. package/src/utils/index.ts +0 -4
  219. package/src/utils/mime-types.ts +0 -244
  220. package/src/utils/network-sync.ts +0 -319
  221. package/src/utils/node-polyfills.ts +0 -8
  222. package/src/utils/output.ts +0 -450
  223. package/src/utils/repo-factory.ts +0 -73
  224. package/src/utils/string-similarity.ts +0 -54
  225. package/src/utils/text-diff.ts +0 -101
  226. package/src/utils/trace.ts +0 -70
  227. package/test/integration/README.md +0 -328
  228. package/test/integration/clone-test.sh +0 -310
  229. package/test/integration/conflict-resolution-test.sh +0 -309
  230. package/test/integration/debug-both-nested.sh +0 -74
  231. package/test/integration/debug-concurrent-nested.sh +0 -87
  232. package/test/integration/debug-nested.sh +0 -73
  233. package/test/integration/deletion-behavior-test.sh +0 -487
  234. package/test/integration/deletion-sync-test-simple.sh +0 -193
  235. package/test/integration/deletion-sync-test.sh +0 -297
  236. package/test/integration/exclude-patterns.test.ts +0 -144
  237. package/test/integration/full-integration-test.sh +0 -363
  238. package/test/integration/fuzzer.test.ts +0 -818
  239. package/test/integration/in-memory-sync.test.ts +0 -830
  240. package/test/integration/init-sync.test.ts +0 -89
  241. package/test/integration/manual-sync-test.sh +0 -84
  242. package/test/integration/sync-deletion.test.ts +0 -280
  243. package/test/integration/sync-flow.test.ts +0 -291
  244. package/test/jest.setup.ts +0 -34
  245. package/test/run-tests.sh +0 -225
  246. package/test/unit/deletion-behavior.test.ts +0 -249
  247. package/test/unit/enhanced-mime-detection.test.ts +0 -244
  248. package/test/unit/snapshot.test.ts +0 -404
  249. package/test/unit/sync-convergence.test.ts +0 -298
  250. package/test/unit/sync-timing.test.ts +0 -134
  251. package/test/unit/utils.test.ts +0 -366
@@ -1,89 +0,0 @@
1
- import * as fs from "fs/promises";
2
- import * as path from "path";
3
- import * as tmp from "tmp";
4
- import { execSync } from "child_process";
5
- import { SnapshotManager } from "../../src/core";
6
-
7
- describe("Init Command Integration", () => {
8
- let tmpDir: string;
9
- let cleanup: () => void;
10
- const pushworkCmd = `node "${path.join(__dirname, "../../dist/cli.js")}"`;
11
-
12
- beforeAll(() => {
13
- // Build the project before running tests
14
- execSync("pnpm build", { cwd: path.join(__dirname, "../.."), stdio: "pipe" });
15
- });
16
-
17
- beforeEach(() => {
18
- const tmpObj = tmp.dirSync({ unsafeCleanup: true });
19
- tmpDir = tmpObj.name;
20
- cleanup = tmpObj.removeCallback;
21
- });
22
-
23
- afterEach(async () => {
24
- cleanup();
25
- });
26
-
27
- describe("Initial Sync", () => {
28
- it("should sync existing files during init", async () => {
29
- // Create some files before initializing
30
- await fs.writeFile(path.join(tmpDir, "file1.txt"), "Hello, World!");
31
- await fs.writeFile(path.join(tmpDir, "file2.txt"), "Another file");
32
- await fs.mkdir(path.join(tmpDir, "subdir"));
33
- await fs.writeFile(
34
- path.join(tmpDir, "subdir", "nested.txt"),
35
- "Nested content"
36
- );
37
-
38
- // Run pushwork init
39
- execSync(`${pushworkCmd} init "${tmpDir}"`, { stdio: "pipe" });
40
-
41
- // Verify snapshot was created with file entries
42
- const snapshotManager = new SnapshotManager(tmpDir);
43
- const snapshot = await snapshotManager.load();
44
- expect(snapshot).not.toBeNull();
45
- expect(snapshot!.files.size).toBeGreaterThanOrEqual(3);
46
- expect(snapshot!.files.has("file1.txt")).toBe(true);
47
- expect(snapshot!.files.has("file2.txt")).toBe(true);
48
- expect(snapshot!.files.has("subdir/nested.txt")).toBe(true);
49
- });
50
-
51
- it("should handle empty directory during init", async () => {
52
- // Run pushwork init on empty directory
53
- execSync(`${pushworkCmd} init "${tmpDir}"`, { stdio: "pipe" });
54
-
55
- // Verify snapshot was created (even if empty)
56
- const snapshotManager = new SnapshotManager(tmpDir);
57
- const snapshot = await snapshotManager.load();
58
- expect(snapshot).not.toBeNull();
59
- expect(snapshot!.files.size).toBe(0);
60
- });
61
-
62
- it("should respect exclude patterns during initial sync", async () => {
63
- // Create files, including some that should be excluded by default
64
- await fs.writeFile(path.join(tmpDir, "included.txt"), "Include me");
65
- await fs.mkdir(path.join(tmpDir, "node_modules"));
66
- await fs.writeFile(
67
- path.join(tmpDir, "node_modules", "package.json"),
68
- "{}"
69
- );
70
- await fs.mkdir(path.join(tmpDir, ".git"));
71
- await fs.writeFile(
72
- path.join(tmpDir, ".git", "config"),
73
- "[core]"
74
- );
75
-
76
- // Run pushwork init
77
- execSync(`${pushworkCmd} init "${tmpDir}"`, { stdio: "pipe" });
78
-
79
- // Verify snapshot only contains included file
80
- const snapshotManager = new SnapshotManager(tmpDir);
81
- const snapshot = await snapshotManager.load();
82
- expect(snapshot).not.toBeNull();
83
- expect(snapshot!.files.has("included.txt")).toBe(true);
84
- // node_modules and .git should be excluded by default
85
- expect(snapshot!.files.has("node_modules/package.json")).toBe(false);
86
- expect(snapshot!.files.has(".git/config")).toBe(false);
87
- });
88
- });
89
- });
@@ -1,84 +0,0 @@
1
- #!/bin/bash
2
- set -x # Print commands as they execute
3
- set -e # Exit on error
4
-
5
- # Get absolute path to pushwork CLI
6
- PUSHWORK_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
7
- PUSHWORK_CLI="$PUSHWORK_ROOT/dist/cli.js"
8
-
9
- echo "Pushwork CLI: $PUSHWORK_CLI"
10
-
11
- # Create temp directory
12
- TESTDIR=$(mktemp -d)
13
- echo "Test directory: $TESTDIR"
14
-
15
- REPO_A="$TESTDIR/repo-a"
16
- REPO_B="$TESTDIR/repo-b"
17
-
18
- mkdir -p "$REPO_A"
19
- mkdir -p "$REPO_B"
20
-
21
- # Step 1: Create initial file in repo A
22
- echo "=== Step 1: Creating initial file in repo A ==="
23
- echo "initial content" > "$REPO_A/test.txt"
24
- cat "$REPO_A/test.txt"
25
-
26
- # Step 2: Initialize repo A
27
- echo "=== Step 2: Initializing repo A ==="
28
- cd "$REPO_A"
29
- node "$PUSHWORK_CLI" init .
30
- sleep 1
31
-
32
- # Step 3: Get root URL
33
- echo "=== Step 3: Getting root URL from repo A ==="
34
- ROOT_URL=$(node "$PUSHWORK_CLI" url)
35
- echo "Root URL: $ROOT_URL"
36
-
37
- # Step 4: Clone to repo B
38
- echo "=== Step 4: Cloning to repo B ==="
39
- cd "$TESTDIR"
40
- node "$PUSHWORK_CLI" clone "$ROOT_URL" "$REPO_B"
41
- sleep 1
42
-
43
- # Step 5: Verify initial state
44
- echo "=== Step 5: Verifying initial state ==="
45
- echo "Content in A:"
46
- cat "$REPO_A/test.txt"
47
- echo "Content in B:"
48
- cat "$REPO_B/test.txt"
49
-
50
- # Step 6: Modify file in repo A
51
- echo "=== Step 6: Modifying file in repo A ==="
52
- echo "modified content" > "$REPO_A/test.txt"
53
- echo "New content in A:"
54
- cat "$REPO_A/test.txt"
55
-
56
- # Step 7: Sync repo A (THIS IS WHERE IT MIGHT HANG)
57
- echo "=== Step 7: Syncing repo A ==="
58
- cd "$REPO_A"
59
- echo "Running sync in A at $(date)..."
60
- timeout 10 node "$PUSHWORK_CLI" sync || echo "SYNC A TIMED OUT!"
61
- echo "Sync A completed at $(date)"
62
- sleep 1
63
-
64
- # Step 8: Sync repo B
65
- echo "=== Step 8: Syncing repo B ==="
66
- cd "$REPO_B"
67
- echo "Running sync in B at $(date)..."
68
- timeout 10 node "$PUSHWORK_CLI" sync || echo "SYNC B TIMED OUT!"
69
- echo "Sync B completed at $(date)"
70
- sleep 1
71
-
72
- # Step 9: Verify final state
73
- echo "=== Step 9: Verifying final state ==="
74
- echo "Final content in A:"
75
- cat "$REPO_A/test.txt"
76
- echo "Final content in B:"
77
- cat "$REPO_B/test.txt"
78
-
79
- # Cleanup
80
- echo "=== Cleanup ==="
81
- echo "Test directory: $TESTDIR"
82
- echo "To inspect manually: cd $TESTDIR"
83
- # rm -rf "$TESTDIR"
84
-
@@ -1,280 +0,0 @@
1
- import * as fs from "fs/promises";
2
- import * as path from "path";
3
- import { tmpdir } from "os";
4
- import {
5
- readFileContent,
6
- writeFileContent,
7
- removePath,
8
- pathExists,
9
- } from "../../src/utils";
10
- import { SnapshotManager } from "../../src/core/snapshot";
11
-
12
- describe("Sync Engine Deletion Integration", () => {
13
- let testDir: string;
14
- let snapshotManager: SnapshotManager;
15
-
16
- beforeEach(async () => {
17
- testDir = await fs.mkdtemp(path.join(tmpdir(), "sync-deletion-test-"));
18
- snapshotManager = new SnapshotManager(testDir);
19
- });
20
-
21
- afterEach(async () => {
22
- await fs.rm(testDir, { recursive: true, force: true });
23
- });
24
-
25
- describe("Deletion Detection Logic", () => {
26
- it("should properly detect local file deletions", async () => {
27
- // Create initial state
28
- const filePath = path.join(testDir, "will-be-deleted.ts");
29
- const content = "interface ToDelete { id: number; }";
30
- await writeFileContent(filePath, content);
31
-
32
- // Create snapshot representing the "before" state
33
- const snapshot = snapshotManager.createEmpty();
34
- snapshotManager.updateFileEntry(snapshot, "will-be-deleted.ts", {
35
- path: filePath,
36
- url: "automerge:deletion-test" as any,
37
- head: ["before-deletion"] as any,
38
- extension: "ts",
39
- mimeType: "text/typescript",
40
- });
41
-
42
- // Verify initial state
43
- expect(await pathExists(filePath)).toBe(true);
44
- expect(snapshot.files.has("will-be-deleted.ts")).toBe(true);
45
-
46
- // Simulate user deleting the file
47
- await removePath(filePath);
48
-
49
- // File should be gone from filesystem but still in snapshot
50
- expect(await pathExists(filePath)).toBe(false);
51
- expect(snapshot.files.has("will-be-deleted.ts")).toBe(true);
52
- });
53
-
54
- it("should handle multiple file deletions correctly", async () => {
55
- const testFiles = [
56
- { name: "delete1.ts", content: "interface One { x: number; }" },
57
- { name: "delete2.js", content: "const two = 'value';" },
58
- { name: "delete3.json", content: '{"three": true}' },
59
- ];
60
-
61
- const snapshot = snapshotManager.createEmpty();
62
-
63
- // Create all files and add to snapshot
64
- for (const file of testFiles) {
65
- const filePath = path.join(testDir, file.name);
66
- await writeFileContent(filePath, file.content);
67
-
68
- snapshotManager.updateFileEntry(snapshot, file.name, {
69
- path: filePath,
70
- url: `automerge:${file.name}` as any,
71
- head: [`head-${file.name}`] as any,
72
- extension: path.extname(file.name).slice(1),
73
- mimeType: file.name.endsWith(".ts")
74
- ? "text/typescript"
75
- : "text/plain",
76
- });
77
- }
78
-
79
- expect(snapshot.files.size).toBe(3);
80
-
81
- // Delete all files
82
- for (const file of testFiles) {
83
- const filePath = path.join(testDir, file.name);
84
- await removePath(filePath);
85
- }
86
-
87
- // Verify all files are gone from filesystem
88
- for (const file of testFiles) {
89
- const filePath = path.join(testDir, file.name);
90
- expect(await pathExists(filePath)).toBe(false);
91
- }
92
-
93
- // Snapshot should still have entries (until sync processes them)
94
- expect(snapshot.files.size).toBe(3);
95
-
96
- // Simulate sync engine processing the deletions
97
- for (const file of testFiles) {
98
- snapshotManager.removeFileEntry(snapshot, file.name);
99
- }
100
-
101
- expect(snapshot.files.size).toBe(0);
102
- });
103
- });
104
-
105
- describe("Deletion Timing and Race Conditions", () => {
106
- it("should handle rapid create-modify-delete sequences", async () => {
107
- const filePath = path.join(testDir, "rapid-changes.ts");
108
- const snapshot = snapshotManager.createEmpty();
109
-
110
- for (let i = 0; i < 3; i++) {
111
- // Create
112
- const content = `interface Cycle${i} { value: ${i}; }`;
113
- await writeFileContent(filePath, content);
114
-
115
- // Add to snapshot
116
- snapshotManager.updateFileEntry(snapshot, "rapid-changes.ts", {
117
- path: filePath,
118
- url: `automerge:cycle-${i}` as any,
119
- head: [`head-${i}`] as any,
120
- extension: "ts",
121
- mimeType: "text/typescript",
122
- });
123
-
124
- // Modify
125
- const modifiedContent = content + `\n// Modified in cycle ${i}`;
126
- await writeFileContent(filePath, modifiedContent);
127
-
128
- // Delete
129
- await removePath(filePath);
130
-
131
- // Verify deletion
132
- expect(await pathExists(filePath)).toBe(false);
133
-
134
- // Clean up snapshot
135
- snapshotManager.removeFileEntry(snapshot, "rapid-changes.ts");
136
- }
137
- });
138
-
139
- it("should handle deletion during content modification attempts", async () => {
140
- const filePath = path.join(testDir, "modify-delete-race.ts");
141
- const initialContent = "interface Race { test: boolean; }";
142
-
143
- // Create initial file
144
- await writeFileContent(filePath, initialContent);
145
-
146
- // Start modification and deletion concurrently
147
- const modifyPromise = writeFileContent(
148
- filePath,
149
- initialContent + "\n// Modified"
150
- );
151
- const deletePromise = (async () => {
152
- // Small delay to let modification start
153
- await new Promise((resolve) => setTimeout(resolve, 1));
154
- await removePath(filePath);
155
- })();
156
-
157
- // Wait for both operations to complete
158
- await Promise.allSettled([modifyPromise, deletePromise]);
159
-
160
- // File should be deleted regardless of modification timing
161
- expect(await pathExists(filePath)).toBe(false);
162
- });
163
- });
164
-
165
- describe("Directory Structure Impact", () => {
166
- it("should handle deletion of files in nested directories", async () => {
167
- // Create nested structure
168
- const nestedDir = path.join(testDir, "src", "components");
169
- const filePath = path.join(nestedDir, "Button.tsx");
170
- const content = "export const Button = () => <button>Click</button>;";
171
-
172
- await fs.mkdir(nestedDir, { recursive: true });
173
- await writeFileContent(filePath, content);
174
-
175
- const snapshot = snapshotManager.createEmpty();
176
- snapshotManager.updateFileEntry(snapshot, "src/components/Button.tsx", {
177
- path: filePath,
178
- url: "automerge:nested-button" as any,
179
- head: ["nested-head"] as any,
180
- extension: "tsx",
181
- mimeType: "text/tsx",
182
- });
183
-
184
- // Delete just the file (not the directories)
185
- await removePath(filePath);
186
-
187
- // File should be gone, directories should remain
188
- expect(await pathExists(filePath)).toBe(false);
189
- expect(await pathExists(nestedDir)).toBe(true);
190
- expect(await pathExists(path.join(testDir, "src"))).toBe(true);
191
-
192
- // Simulate snapshot cleanup
193
- snapshotManager.removeFileEntry(snapshot, "src/components/Button.tsx");
194
- expect(snapshot.files.size).toBe(0);
195
- });
196
-
197
- it("should handle deletion of entire directory trees", async () => {
198
- // Create multiple files in nested structure
199
- const testStructure = [
200
- "src/utils/helpers.ts",
201
- "src/utils/constants.ts",
202
- "src/components/Button.tsx",
203
- "src/components/Input.tsx",
204
- "src/types/index.ts",
205
- ];
206
-
207
- const snapshot = snapshotManager.createEmpty();
208
-
209
- for (const relativePath of testStructure) {
210
- const fullPath = path.join(testDir, relativePath);
211
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
212
- await writeFileContent(fullPath, `// Content for ${relativePath}`);
213
-
214
- snapshotManager.updateFileEntry(snapshot, relativePath, {
215
- path: fullPath,
216
- url: `automerge:${relativePath.replace(/[\/\.]/g, "-")}` as any,
217
- head: [`head-${relativePath}`] as any,
218
- extension: path.extname(relativePath).slice(1),
219
- mimeType: "text/typescript",
220
- });
221
- }
222
-
223
- expect(snapshot.files.size).toBe(5);
224
-
225
- // Delete entire src directory
226
- await removePath(path.join(testDir, "src"));
227
-
228
- // Verify all files and directories are gone
229
- for (const relativePath of testStructure) {
230
- const fullPath = path.join(testDir, relativePath);
231
- expect(await pathExists(fullPath)).toBe(false);
232
- }
233
- expect(await pathExists(path.join(testDir, "src"))).toBe(false);
234
-
235
- // Simulate snapshot cleanup for all files
236
- for (const relativePath of testStructure) {
237
- snapshotManager.removeFileEntry(snapshot, relativePath);
238
- }
239
-
240
- expect(snapshot.files.size).toBe(0);
241
- });
242
- });
243
-
244
- describe("Error Recovery and Edge Cases", () => {
245
- it("should handle deletion of non-existent files gracefully", async () => {
246
- const nonExistentPath = path.join(testDir, "never-existed.ts");
247
-
248
- // Attempt to delete non-existent file (should not throw)
249
- await expect(removePath(nonExistentPath)).resolves.not.toThrow();
250
-
251
- // Attempt to remove from snapshot (should not throw)
252
- const snapshot = snapshotManager.createEmpty();
253
- expect(() => {
254
- snapshotManager.removeFileEntry(snapshot, "never-existed.ts");
255
- }).not.toThrow();
256
- });
257
-
258
- it("should provide debugging info for deletion failures", async () => {
259
- const debugFilePath = path.join(testDir, "debug-deletion.ts");
260
- const content = "interface Debug { info: string; }";
261
-
262
- try {
263
- // Create file
264
- await writeFileContent(debugFilePath, content);
265
-
266
- // Verify file exists and is readable
267
- const readBack = await readFileContent(debugFilePath);
268
- expect(readBack).toBe(content);
269
-
270
- // Delete file
271
- await removePath(debugFilePath);
272
-
273
- expect(await pathExists(debugFilePath)).toBe(false);
274
- } catch (error) {
275
- console.error(`❌ Deletion test failed:`, error);
276
- throw error;
277
- }
278
- });
279
- });
280
- });