pushwork 2.0.0-a.sub.1 → 2.0.0-preview.2

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 (234) hide show
  1. package/dist/branches.d.ts +20 -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 +245 -270
  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 +35 -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 +129 -0
  30. package/dist/pushwork.d.ts.map +1 -0
  31. package/dist/pushwork.js +1062 -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 +38 -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 +92 -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/dist/version.d.ts +11 -0
  66. package/dist/version.d.ts.map +1 -0
  67. package/dist/version.js +93 -0
  68. package/dist/version.js.map +1 -0
  69. package/package.json +19 -48
  70. package/patches/@automerge__automerge-repo@2.6.0-subduction.15.patch +26 -0
  71. package/.prettierrc +0 -9
  72. package/ARCHITECTURE-ACCORDING-TO-CLAUDE.md +0 -248
  73. package/CLAUDE.md +0 -141
  74. package/README.md +0 -221
  75. package/babel.config.js +0 -5
  76. package/dist/cli/commands.d.ts +0 -71
  77. package/dist/cli/commands.d.ts.map +0 -1
  78. package/dist/cli/commands.js +0 -794
  79. package/dist/cli/commands.js.map +0 -1
  80. package/dist/cli/index.d.ts +0 -2
  81. package/dist/cli/index.d.ts.map +0 -1
  82. package/dist/cli/index.js +0 -19
  83. package/dist/cli/index.js.map +0 -1
  84. package/dist/commands.d.ts +0 -61
  85. package/dist/commands.d.ts.map +0 -1
  86. package/dist/commands.js +0 -861
  87. package/dist/commands.js.map +0 -1
  88. package/dist/config/index.d.ts +0 -71
  89. package/dist/config/index.d.ts.map +0 -1
  90. package/dist/config/index.js +0 -314
  91. package/dist/config/index.js.map +0 -1
  92. package/dist/core/change-detection.d.ts +0 -80
  93. package/dist/core/change-detection.d.ts.map +0 -1
  94. package/dist/core/change-detection.js +0 -523
  95. package/dist/core/change-detection.js.map +0 -1
  96. package/dist/core/config.d.ts +0 -81
  97. package/dist/core/config.d.ts.map +0 -1
  98. package/dist/core/config.js +0 -258
  99. package/dist/core/config.js.map +0 -1
  100. package/dist/core/index.d.ts +0 -6
  101. package/dist/core/index.d.ts.map +0 -1
  102. package/dist/core/index.js +0 -6
  103. package/dist/core/index.js.map +0 -1
  104. package/dist/core/move-detection.d.ts +0 -34
  105. package/dist/core/move-detection.d.ts.map +0 -1
  106. package/dist/core/move-detection.js +0 -121
  107. package/dist/core/move-detection.js.map +0 -1
  108. package/dist/core/snapshot.d.ts +0 -105
  109. package/dist/core/snapshot.d.ts.map +0 -1
  110. package/dist/core/snapshot.js +0 -217
  111. package/dist/core/snapshot.js.map +0 -1
  112. package/dist/core/sync-engine.d.ts +0 -157
  113. package/dist/core/sync-engine.d.ts.map +0 -1
  114. package/dist/core/sync-engine.js +0 -1379
  115. package/dist/core/sync-engine.js.map +0 -1
  116. package/dist/types/config.d.ts +0 -99
  117. package/dist/types/config.d.ts.map +0 -1
  118. package/dist/types/config.js +0 -5
  119. package/dist/types/config.js.map +0 -1
  120. package/dist/types/documents.d.ts +0 -88
  121. package/dist/types/documents.d.ts.map +0 -1
  122. package/dist/types/documents.js +0 -20
  123. package/dist/types/documents.js.map +0 -1
  124. package/dist/types/index.d.ts +0 -4
  125. package/dist/types/index.d.ts.map +0 -1
  126. package/dist/types/index.js +0 -4
  127. package/dist/types/index.js.map +0 -1
  128. package/dist/types/snapshot.d.ts +0 -64
  129. package/dist/types/snapshot.d.ts.map +0 -1
  130. package/dist/types/snapshot.js +0 -2
  131. package/dist/types/snapshot.js.map +0 -1
  132. package/dist/utils/content-similarity.d.ts +0 -53
  133. package/dist/utils/content-similarity.d.ts.map +0 -1
  134. package/dist/utils/content-similarity.js +0 -155
  135. package/dist/utils/content-similarity.js.map +0 -1
  136. package/dist/utils/content.d.ts +0 -10
  137. package/dist/utils/content.d.ts.map +0 -1
  138. package/dist/utils/content.js +0 -31
  139. package/dist/utils/content.js.map +0 -1
  140. package/dist/utils/directory.d.ts +0 -24
  141. package/dist/utils/directory.d.ts.map +0 -1
  142. package/dist/utils/directory.js +0 -52
  143. package/dist/utils/directory.js.map +0 -1
  144. package/dist/utils/fs.d.ts +0 -74
  145. package/dist/utils/fs.d.ts.map +0 -1
  146. package/dist/utils/fs.js +0 -248
  147. package/dist/utils/fs.js.map +0 -1
  148. package/dist/utils/index.d.ts +0 -5
  149. package/dist/utils/index.d.ts.map +0 -1
  150. package/dist/utils/index.js +0 -5
  151. package/dist/utils/index.js.map +0 -1
  152. package/dist/utils/mime-types.d.ts +0 -13
  153. package/dist/utils/mime-types.d.ts.map +0 -1
  154. package/dist/utils/mime-types.js +0 -209
  155. package/dist/utils/mime-types.js.map +0 -1
  156. package/dist/utils/network-sync.d.ts +0 -36
  157. package/dist/utils/network-sync.d.ts.map +0 -1
  158. package/dist/utils/network-sync.js +0 -250
  159. package/dist/utils/network-sync.js.map +0 -1
  160. package/dist/utils/node-polyfills.d.ts +0 -9
  161. package/dist/utils/node-polyfills.d.ts.map +0 -1
  162. package/dist/utils/node-polyfills.js +0 -9
  163. package/dist/utils/node-polyfills.js.map +0 -1
  164. package/dist/utils/output.d.ts +0 -129
  165. package/dist/utils/output.d.ts.map +0 -1
  166. package/dist/utils/output.js +0 -368
  167. package/dist/utils/output.js.map +0 -1
  168. package/dist/utils/repo-factory.d.ts +0 -13
  169. package/dist/utils/repo-factory.d.ts.map +0 -1
  170. package/dist/utils/repo-factory.js +0 -46
  171. package/dist/utils/repo-factory.js.map +0 -1
  172. package/dist/utils/string-similarity.d.ts +0 -14
  173. package/dist/utils/string-similarity.d.ts.map +0 -1
  174. package/dist/utils/string-similarity.js +0 -39
  175. package/dist/utils/string-similarity.js.map +0 -1
  176. package/dist/utils/text-diff.d.ts +0 -37
  177. package/dist/utils/text-diff.d.ts.map +0 -1
  178. package/dist/utils/text-diff.js +0 -93
  179. package/dist/utils/text-diff.js.map +0 -1
  180. package/dist/utils/trace.d.ts +0 -19
  181. package/dist/utils/trace.d.ts.map +0 -1
  182. package/dist/utils/trace.js +0 -63
  183. package/dist/utils/trace.js.map +0 -1
  184. package/src/cli.ts +0 -442
  185. package/src/commands.ts +0 -1134
  186. package/src/core/change-detection.ts +0 -712
  187. package/src/core/config.ts +0 -313
  188. package/src/core/index.ts +0 -5
  189. package/src/core/move-detection.ts +0 -169
  190. package/src/core/snapshot.ts +0 -275
  191. package/src/core/sync-engine.ts +0 -1795
  192. package/src/index.ts +0 -4
  193. package/src/types/config.ts +0 -111
  194. package/src/types/documents.ts +0 -91
  195. package/src/types/index.ts +0 -3
  196. package/src/types/snapshot.ts +0 -67
  197. package/src/utils/content.ts +0 -34
  198. package/src/utils/directory.ts +0 -73
  199. package/src/utils/fs.ts +0 -297
  200. package/src/utils/index.ts +0 -4
  201. package/src/utils/mime-types.ts +0 -244
  202. package/src/utils/network-sync.ts +0 -319
  203. package/src/utils/node-polyfills.ts +0 -8
  204. package/src/utils/output.ts +0 -450
  205. package/src/utils/repo-factory.ts +0 -73
  206. package/src/utils/string-similarity.ts +0 -54
  207. package/src/utils/text-diff.ts +0 -101
  208. package/src/utils/trace.ts +0 -70
  209. package/test/integration/README.md +0 -328
  210. package/test/integration/clone-test.sh +0 -310
  211. package/test/integration/conflict-resolution-test.sh +0 -309
  212. package/test/integration/debug-both-nested.sh +0 -74
  213. package/test/integration/debug-concurrent-nested.sh +0 -87
  214. package/test/integration/debug-nested.sh +0 -73
  215. package/test/integration/deletion-behavior-test.sh +0 -487
  216. package/test/integration/deletion-sync-test-simple.sh +0 -193
  217. package/test/integration/deletion-sync-test.sh +0 -297
  218. package/test/integration/exclude-patterns.test.ts +0 -144
  219. package/test/integration/full-integration-test.sh +0 -363
  220. package/test/integration/fuzzer.test.ts +0 -818
  221. package/test/integration/in-memory-sync.test.ts +0 -830
  222. package/test/integration/init-sync.test.ts +0 -89
  223. package/test/integration/manual-sync-test.sh +0 -84
  224. package/test/integration/sync-deletion.test.ts +0 -280
  225. package/test/integration/sync-flow.test.ts +0 -291
  226. package/test/jest.setup.ts +0 -34
  227. package/test/run-tests.sh +0 -225
  228. package/test/unit/deletion-behavior.test.ts +0 -249
  229. package/test/unit/enhanced-mime-detection.test.ts +0 -244
  230. package/test/unit/snapshot.test.ts +0 -404
  231. package/test/unit/sync-convergence.test.ts +0 -298
  232. package/test/unit/sync-timing.test.ts +0 -134
  233. package/test/unit/utils.test.ts +0 -366
  234. package/tsconfig.json +0 -23
@@ -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
- });