pushwork 1.0.0

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 (184) hide show
  1. package/README.md +460 -0
  2. package/dist/browser/browser-sync-engine.d.ts +64 -0
  3. package/dist/browser/browser-sync-engine.d.ts.map +1 -0
  4. package/dist/browser/browser-sync-engine.js +303 -0
  5. package/dist/browser/browser-sync-engine.js.map +1 -0
  6. package/dist/browser/filesystem-adapter.d.ts +84 -0
  7. package/dist/browser/filesystem-adapter.d.ts.map +1 -0
  8. package/dist/browser/filesystem-adapter.js +413 -0
  9. package/dist/browser/filesystem-adapter.js.map +1 -0
  10. package/dist/browser/index.d.ts +36 -0
  11. package/dist/browser/index.d.ts.map +1 -0
  12. package/dist/browser/index.js +90 -0
  13. package/dist/browser/index.js.map +1 -0
  14. package/dist/browser/types.d.ts +70 -0
  15. package/dist/browser/types.d.ts.map +1 -0
  16. package/dist/browser/types.js +6 -0
  17. package/dist/browser/types.js.map +1 -0
  18. package/dist/cli/commands.d.ts +71 -0
  19. package/dist/cli/commands.d.ts.map +1 -0
  20. package/dist/cli/commands.js +794 -0
  21. package/dist/cli/commands.js.map +1 -0
  22. package/dist/cli/index.d.ts +2 -0
  23. package/dist/cli/index.d.ts.map +1 -0
  24. package/dist/cli/index.js +19 -0
  25. package/dist/cli/index.js.map +1 -0
  26. package/dist/cli.d.ts +3 -0
  27. package/dist/cli.d.ts.map +1 -0
  28. package/dist/cli.js +199 -0
  29. package/dist/cli.js.map +1 -0
  30. package/dist/config/index.d.ts +71 -0
  31. package/dist/config/index.d.ts.map +1 -0
  32. package/dist/config/index.js +314 -0
  33. package/dist/config/index.js.map +1 -0
  34. package/dist/core/change-detection.d.ts +78 -0
  35. package/dist/core/change-detection.d.ts.map +1 -0
  36. package/dist/core/change-detection.js +370 -0
  37. package/dist/core/change-detection.js.map +1 -0
  38. package/dist/core/index.d.ts +5 -0
  39. package/dist/core/index.d.ts.map +1 -0
  40. package/dist/core/index.js +22 -0
  41. package/dist/core/index.js.map +1 -0
  42. package/dist/core/isomorphic-snapshot.d.ts +58 -0
  43. package/dist/core/isomorphic-snapshot.d.ts.map +1 -0
  44. package/dist/core/isomorphic-snapshot.js +204 -0
  45. package/dist/core/isomorphic-snapshot.js.map +1 -0
  46. package/dist/core/move-detection.d.ts +72 -0
  47. package/dist/core/move-detection.d.ts.map +1 -0
  48. package/dist/core/move-detection.js +200 -0
  49. package/dist/core/move-detection.js.map +1 -0
  50. package/dist/core/snapshot.d.ts +109 -0
  51. package/dist/core/snapshot.d.ts.map +1 -0
  52. package/dist/core/snapshot.js +263 -0
  53. package/dist/core/snapshot.js.map +1 -0
  54. package/dist/core/sync-engine.d.ts +110 -0
  55. package/dist/core/sync-engine.d.ts.map +1 -0
  56. package/dist/core/sync-engine.js +817 -0
  57. package/dist/core/sync-engine.js.map +1 -0
  58. package/dist/index.d.ts +6 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +27 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/platform/browser-filesystem.d.ts +26 -0
  63. package/dist/platform/browser-filesystem.d.ts.map +1 -0
  64. package/dist/platform/browser-filesystem.js +91 -0
  65. package/dist/platform/browser-filesystem.js.map +1 -0
  66. package/dist/platform/filesystem.d.ts +29 -0
  67. package/dist/platform/filesystem.d.ts.map +1 -0
  68. package/dist/platform/filesystem.js +65 -0
  69. package/dist/platform/filesystem.js.map +1 -0
  70. package/dist/platform/node-filesystem.d.ts +21 -0
  71. package/dist/platform/node-filesystem.d.ts.map +1 -0
  72. package/dist/platform/node-filesystem.js +93 -0
  73. package/dist/platform/node-filesystem.js.map +1 -0
  74. package/dist/types/config.d.ts +119 -0
  75. package/dist/types/config.d.ts.map +1 -0
  76. package/dist/types/config.js +3 -0
  77. package/dist/types/config.js.map +1 -0
  78. package/dist/types/documents.d.ts +70 -0
  79. package/dist/types/documents.d.ts.map +1 -0
  80. package/dist/types/documents.js +23 -0
  81. package/dist/types/documents.js.map +1 -0
  82. package/dist/types/index.d.ts +4 -0
  83. package/dist/types/index.d.ts.map +1 -0
  84. package/dist/types/index.js +23 -0
  85. package/dist/types/index.js.map +1 -0
  86. package/dist/types/snapshot.d.ts +81 -0
  87. package/dist/types/snapshot.d.ts.map +1 -0
  88. package/dist/types/snapshot.js +17 -0
  89. package/dist/types/snapshot.js.map +1 -0
  90. package/dist/utils/content-similarity.d.ts +53 -0
  91. package/dist/utils/content-similarity.d.ts.map +1 -0
  92. package/dist/utils/content-similarity.js +155 -0
  93. package/dist/utils/content-similarity.js.map +1 -0
  94. package/dist/utils/content.d.ts +5 -0
  95. package/dist/utils/content.d.ts.map +1 -0
  96. package/dist/utils/content.js +30 -0
  97. package/dist/utils/content.js.map +1 -0
  98. package/dist/utils/fs-browser.d.ts +57 -0
  99. package/dist/utils/fs-browser.d.ts.map +1 -0
  100. package/dist/utils/fs-browser.js +311 -0
  101. package/dist/utils/fs-browser.js.map +1 -0
  102. package/dist/utils/fs-node.d.ts +53 -0
  103. package/dist/utils/fs-node.d.ts.map +1 -0
  104. package/dist/utils/fs-node.js +220 -0
  105. package/dist/utils/fs-node.js.map +1 -0
  106. package/dist/utils/fs.d.ts +62 -0
  107. package/dist/utils/fs.d.ts.map +1 -0
  108. package/dist/utils/fs.js +293 -0
  109. package/dist/utils/fs.js.map +1 -0
  110. package/dist/utils/index.d.ts +4 -0
  111. package/dist/utils/index.d.ts.map +1 -0
  112. package/dist/utils/index.js +23 -0
  113. package/dist/utils/index.js.map +1 -0
  114. package/dist/utils/isomorphic.d.ts +29 -0
  115. package/dist/utils/isomorphic.d.ts.map +1 -0
  116. package/dist/utils/isomorphic.js +139 -0
  117. package/dist/utils/isomorphic.js.map +1 -0
  118. package/dist/utils/mime-types.d.ts +13 -0
  119. package/dist/utils/mime-types.d.ts.map +1 -0
  120. package/dist/utils/mime-types.js +240 -0
  121. package/dist/utils/mime-types.js.map +1 -0
  122. package/dist/utils/network-sync.d.ts +12 -0
  123. package/dist/utils/network-sync.d.ts.map +1 -0
  124. package/dist/utils/network-sync.js +149 -0
  125. package/dist/utils/network-sync.js.map +1 -0
  126. package/dist/utils/pure.d.ts +25 -0
  127. package/dist/utils/pure.d.ts.map +1 -0
  128. package/dist/utils/pure.js +112 -0
  129. package/dist/utils/pure.js.map +1 -0
  130. package/dist/utils/repo-factory.d.ts +11 -0
  131. package/dist/utils/repo-factory.d.ts.map +1 -0
  132. package/dist/utils/repo-factory.js +77 -0
  133. package/dist/utils/repo-factory.js.map +1 -0
  134. package/package.json +83 -0
  135. package/src/cli/commands.ts +1053 -0
  136. package/src/cli/index.ts +2 -0
  137. package/src/cli.ts +287 -0
  138. package/src/config/index.ts +334 -0
  139. package/src/core/change-detection.ts +484 -0
  140. package/src/core/index.ts +5 -0
  141. package/src/core/move-detection.ts +269 -0
  142. package/src/core/snapshot.ts +285 -0
  143. package/src/core/sync-engine.ts +1167 -0
  144. package/src/index.ts +14 -0
  145. package/src/types/config.ts +130 -0
  146. package/src/types/documents.ts +72 -0
  147. package/src/types/index.ts +8 -0
  148. package/src/types/snapshot.ts +88 -0
  149. package/src/utils/content-similarity.ts +194 -0
  150. package/src/utils/content.ts +28 -0
  151. package/src/utils/fs.ts +289 -0
  152. package/src/utils/index.ts +8 -0
  153. package/src/utils/mime-types.ts +236 -0
  154. package/src/utils/network-sync.ts +153 -0
  155. package/src/utils/repo-factory.ts +58 -0
  156. package/test/README-TESTING-GAPS.md +174 -0
  157. package/test/integration/README.md +328 -0
  158. package/test/integration/clone-test.sh +310 -0
  159. package/test/integration/conflict-resolution-test.sh +309 -0
  160. package/test/integration/deletion-behavior-test.sh +487 -0
  161. package/test/integration/deletion-sync-test-simple.sh +193 -0
  162. package/test/integration/deletion-sync-test.sh +297 -0
  163. package/test/integration/exclude-patterns.test.ts +152 -0
  164. package/test/integration/full-integration-test.sh +363 -0
  165. package/test/integration/sync-deletion.test.ts +339 -0
  166. package/test/integration/sync-flow.test.ts +309 -0
  167. package/test/run-tests.sh +225 -0
  168. package/test/unit/content-similarity.test.ts +236 -0
  169. package/test/unit/deletion-behavior.test.ts +260 -0
  170. package/test/unit/enhanced-mime-detection.test.ts +266 -0
  171. package/test/unit/snapshot.test.ts +431 -0
  172. package/test/unit/sync-timing.test.ts +178 -0
  173. package/test/unit/utils.test.ts +368 -0
  174. package/tools/browser-sync/README.md +116 -0
  175. package/tools/browser-sync/package.json +44 -0
  176. package/tools/browser-sync/patchwork.json +1 -0
  177. package/tools/browser-sync/pnpm-lock.yaml +4202 -0
  178. package/tools/browser-sync/src/components/BrowserSyncTool.tsx +599 -0
  179. package/tools/browser-sync/src/index.ts +20 -0
  180. package/tools/browser-sync/src/polyfills.ts +31 -0
  181. package/tools/browser-sync/src/styles.css +290 -0
  182. package/tools/browser-sync/src/types.ts +27 -0
  183. package/tools/browser-sync/vite.config.ts +25 -0
  184. package/tsconfig.json +22 -0
@@ -0,0 +1,363 @@
1
+ #!/bin/bash
2
+
3
+ # Comprehensive Integration Test for Pushwork
4
+ # Tests all major functionality of the sync tool
5
+
6
+ set -e # Exit on any error
7
+
8
+ # Colors for output
9
+ RED='\033[0;31m'
10
+ GREEN='\033[0;32m'
11
+ YELLOW='\033[1;33m'
12
+ BLUE='\033[0;34m'
13
+ NC='\033[0m' # No Color
14
+
15
+ # Test configuration
16
+ TEST_DIR="/tmp/pushwork-integration-test"
17
+ PUSHWORK_CMD="node $(pwd)/dist/cli.js"
18
+ CUSTOM_SYNC_SERVER="ws://localhost:3030"
19
+ CUSTOM_STORAGE_ID="1d89eba7-f7a4-4e8e-80f2-5f4e2406f507"
20
+
21
+ # Test counters
22
+ TESTS_RUN=0
23
+ TESTS_PASSED=0
24
+ TESTS_FAILED=0
25
+
26
+ # Helper functions
27
+ log_info() {
28
+ echo -e "${BLUE}[INFO]${NC} $1"
29
+ }
30
+
31
+ log_success() {
32
+ echo -e "${GREEN}[PASS]${NC} $1"
33
+ ((TESTS_PASSED++))
34
+ }
35
+
36
+ log_error() {
37
+ echo -e "${RED}[FAIL]${NC} $1"
38
+ ((TESTS_FAILED++))
39
+ }
40
+
41
+ log_warning() {
42
+ echo -e "${YELLOW}[WARN]${NC} $1"
43
+ }
44
+
45
+ log_test() {
46
+ echo -e "${YELLOW}[TEST]${NC} $1"
47
+ ((TESTS_RUN++))
48
+ }
49
+
50
+ # Test wrapper function
51
+ run_test() {
52
+ local test_name="$1"
53
+ local test_cmd="$2"
54
+ local expected_exit_code="${3:-0}"
55
+
56
+ log_test "$test_name"
57
+
58
+ if [ "$expected_exit_code" = "0" ]; then
59
+ if eval "$test_cmd" > /dev/null 2>&1; then
60
+ log_success "$test_name"
61
+ return 0
62
+ else
63
+ log_error "$test_name (command failed)"
64
+ return 1
65
+ fi
66
+ else
67
+ if eval "$test_cmd" > /dev/null 2>&1; then
68
+ log_error "$test_name (expected failure but succeeded)"
69
+ return 1
70
+ else
71
+ log_success "$test_name (correctly failed)"
72
+ return 0
73
+ fi
74
+ fi
75
+ }
76
+
77
+ # Cleanup function
78
+ cleanup() {
79
+ log_info "Cleaning up test directory..."
80
+ rm -rf "$TEST_DIR"
81
+ }
82
+
83
+ # Setup function
84
+ setup() {
85
+ log_info "Setting up integration test environment..."
86
+
87
+ # Build the project first
88
+ log_info "Building pushwork..."
89
+ npm run build
90
+
91
+ # Clean up any existing test directory
92
+ rm -rf "$TEST_DIR"
93
+ mkdir -p "$TEST_DIR"
94
+ cd "$TEST_DIR"
95
+
96
+ log_info "Test directory: $TEST_DIR"
97
+ }
98
+
99
+ # Test functions
100
+
101
+ test_help_commands() {
102
+ log_info "=== Testing Help Commands ==="
103
+
104
+ run_test "pushwork --help" "$PUSHWORK_CMD --help"
105
+ run_test "pushwork init --help" "$PUSHWORK_CMD init --help"
106
+ run_test "pushwork clone --help" "$PUSHWORK_CMD clone --help"
107
+ run_test "pushwork sync --help" "$PUSHWORK_CMD sync --help"
108
+ run_test "pushwork status --help" "$PUSHWORK_CMD status --help"
109
+ run_test "pushwork diff --help" "$PUSHWORK_CMD diff --help"
110
+ run_test "pushwork commit --help" "$PUSHWORK_CMD commit --help"
111
+ }
112
+
113
+ test_init_functionality() {
114
+ log_info "=== Testing Init Functionality ==="
115
+
116
+ # Test init with default settings
117
+ mkdir -p test-init-default
118
+ cd test-init-default
119
+ echo "Hello World" > test.txt
120
+
121
+ run_test "init with default settings" "$PUSHWORK_CMD init ."
122
+ run_test "check .pushwork directory exists" "[ -d .pushwork ]"
123
+ run_test "check config file exists" "[ -f .pushwork/config.json ]"
124
+ run_test "check snapshot file exists" "[ -f .pushwork/snapshot.json ]"
125
+
126
+ cd ..
127
+
128
+ # Test init with custom sync server
129
+ mkdir -p test-init-custom
130
+ cd test-init-custom
131
+ echo "Custom Server Test" > custom.txt
132
+
133
+ run_test "init with custom sync server" "$PUSHWORK_CMD init . --sync-server $CUSTOM_SYNC_SERVER --sync-server-storage-id $CUSTOM_STORAGE_ID"
134
+
135
+ # Verify custom settings in config
136
+ if [ -f .pushwork/config.json ]; then
137
+ if grep -q "$CUSTOM_SYNC_SERVER" .pushwork/config.json; then
138
+ log_success "custom sync server saved in config"
139
+ ((TESTS_PASSED++))
140
+ else
141
+ log_error "custom sync server not found in config"
142
+ ((TESTS_FAILED++))
143
+ fi
144
+ ((TESTS_RUN++))
145
+
146
+ if grep -q "$CUSTOM_STORAGE_ID" .pushwork/config.json; then
147
+ log_success "custom storage ID saved in config"
148
+ ((TESTS_PASSED++))
149
+ else
150
+ log_error "custom storage ID not found in config"
151
+ ((TESTS_FAILED++))
152
+ fi
153
+ ((TESTS_RUN++))
154
+ fi
155
+
156
+ cd ..
157
+
158
+ # Test error cases
159
+ run_test "init already initialized directory" "$PUSHWORK_CMD init test-init-default" 1
160
+ run_test "init with only sync-server (should fail)" "$PUSHWORK_CMD init test-fail --sync-server $CUSTOM_SYNC_SERVER" 1
161
+ run_test "init with only storage-id (should fail)" "$PUSHWORK_CMD init test-fail --sync-server-storage-id $CUSTOM_STORAGE_ID" 1
162
+ }
163
+
164
+ test_status_functionality() {
165
+ log_info "=== Testing Status Functionality ==="
166
+
167
+ cd test-init-default
168
+ run_test "status in initialized directory" "$PUSHWORK_CMD status"
169
+ cd ..
170
+
171
+ run_test "status in non-initialized directory" "$PUSHWORK_CMD status" 1
172
+ }
173
+
174
+ test_commit_functionality() {
175
+ log_info "=== Testing Commit Functionality ==="
176
+
177
+ cd test-init-default
178
+
179
+ # Add some files
180
+ echo "New content" > new-file.txt
181
+ echo "Modified content" >> test.txt
182
+
183
+ run_test "commit with changes" "$PUSHWORK_CMD commit ."
184
+ run_test "commit dry-run" "$PUSHWORK_CMD commit . --dry-run"
185
+
186
+ cd ..
187
+ }
188
+
189
+ test_sync_functionality() {
190
+ log_info "=== Testing Sync Functionality ==="
191
+
192
+ cd test-init-default
193
+
194
+ run_test "sync in initialized directory" "$PUSHWORK_CMD sync"
195
+ run_test "sync dry-run" "$PUSHWORK_CMD sync --dry-run"
196
+ run_test "sync local-only" "$PUSHWORK_CMD sync --local-only"
197
+
198
+ cd ..
199
+ }
200
+
201
+ test_diff_functionality() {
202
+ log_info "=== Testing Diff Functionality ==="
203
+
204
+ cd test-init-default
205
+
206
+ # Make some changes
207
+ echo "Diff test content" > diff-test.txt
208
+
209
+ run_test "diff command" "$PUSHWORK_CMD diff"
210
+ run_test "diff name-only" "$PUSHWORK_CMD diff --name-only"
211
+ run_test "diff local-only" "$PUSHWORK_CMD diff --local-only"
212
+
213
+ cd ..
214
+ }
215
+
216
+ test_clone_functionality() {
217
+ log_info "=== Testing Clone Functionality ==="
218
+
219
+ # First, we need a valid URL to clone from the initialized directory
220
+ cd test-init-default
221
+
222
+ if [ -f .pushwork/snapshot.json ]; then
223
+ ROOT_URL=$($PUSHWORK_CMD url .)
224
+
225
+ if [ -n "$ROOT_URL" ]; then
226
+ cd ..
227
+
228
+ # Test clone with default settings
229
+ run_test "clone with default settings" "$PUSHWORK_CMD clone $ROOT_URL test-clone-default"
230
+
231
+ if [ -d test-clone-default ]; then
232
+ run_test "cloned directory has .pushwork" "[ -d test-clone-default/.pushwork ]"
233
+ run_test "cloned directory has files" "[ -f test-clone-default/test.txt ]"
234
+ fi
235
+
236
+ # Test clone with custom sync server
237
+ run_test "clone with custom sync server" "$PUSHWORK_CMD clone $ROOT_URL test-clone-custom --sync-server $CUSTOM_SYNC_SERVER --sync-server-storage-id $CUSTOM_STORAGE_ID"
238
+
239
+ # Test clone error cases
240
+ mkdir -p existing-dir
241
+ echo "existing" > existing-dir/file.txt
242
+ run_test "clone to non-empty directory (should fail)" "$PUSHWORK_CMD clone $ROOT_URL existing-dir" 1
243
+ run_test "clone with force to non-empty directory" "$PUSHWORK_CMD clone $ROOT_URL existing-dir --force"
244
+
245
+ run_test "clone with only sync-server (should fail)" "$PUSHWORK_CMD clone $ROOT_URL test-fail --sync-server $CUSTOM_SYNC_SERVER" 1
246
+ run_test "clone with only storage-id (should fail)" "$PUSHWORK_CMD clone $ROOT_URL test-fail --sync-server-storage-id $CUSTOM_STORAGE_ID" 1
247
+ else
248
+ log_warning "No valid root URL found, skipping clone tests"
249
+ fi
250
+ else
251
+ log_warning "Snapshot missing - repository not properly initialized, skipping clone tests"
252
+ fi
253
+
254
+ cd ..
255
+ }
256
+
257
+ test_bidirectional_sync() {
258
+ log_info "=== Testing Bidirectional Sync ==="
259
+
260
+ # This test requires both directories to be properly initialized
261
+ if [ -d test-init-default ] && [ -d test-clone-default ]; then
262
+ # Add content in original
263
+ cd test-init-default
264
+ echo "From original" > sync-test.txt
265
+ $PUSHWORK_CMD commit . > /dev/null 2>&1 || true
266
+ cd ..
267
+
268
+ # Add different content in clone
269
+ cd test-clone-default
270
+ echo "From clone" > sync-test-clone.txt
271
+ $PUSHWORK_CMD commit . > /dev/null 2>&1 || true
272
+ cd ..
273
+
274
+ # Try to sync both
275
+ cd test-init-default
276
+ run_test "sync from original" "$PUSHWORK_CMD sync --local-only"
277
+ cd ..
278
+
279
+ cd test-clone-default
280
+ run_test "sync from clone" "$PUSHWORK_CMD sync --local-only"
281
+ cd ..
282
+ else
283
+ log_warning "Clone directories not available, skipping bidirectional sync test"
284
+ fi
285
+ }
286
+
287
+ test_file_operations() {
288
+ log_info "=== Testing File Operations ==="
289
+
290
+ mkdir -p test-file-ops
291
+ cd test-file-ops
292
+
293
+ # Initialize
294
+ $PUSHWORK_CMD init . > /dev/null 2>&1
295
+
296
+ # Test various file operations
297
+ echo "Text file" > text.txt
298
+ echo -e "\x89PNG\r\n\x1a\n" > binary.png # Fake PNG header
299
+ mkdir -p subdir
300
+ echo "Subdirectory file" > subdir/nested.txt
301
+
302
+ run_test "commit various file types" "$PUSHWORK_CMD commit ."
303
+ run_test "status after file operations" "$PUSHWORK_CMD status"
304
+
305
+ # Modify files
306
+ echo "Modified text" >> text.txt
307
+ rm binary.png
308
+ echo "New file" > new.txt
309
+
310
+ run_test "diff after modifications" "$PUSHWORK_CMD diff --name-only"
311
+ run_test "commit modifications" "$PUSHWORK_CMD commit ."
312
+
313
+ cd ..
314
+ }
315
+
316
+ # Main test execution
317
+ main() {
318
+ echo "======================================"
319
+ echo "Pushwork Integration Test Suite"
320
+ echo "======================================"
321
+
322
+ # Trap cleanup on exit
323
+ trap cleanup EXIT
324
+
325
+ # Setup
326
+ setup
327
+
328
+ # Run all tests
329
+ test_help_commands
330
+ test_init_functionality
331
+ test_status_functionality
332
+ test_commit_functionality
333
+ test_sync_functionality
334
+ test_diff_functionality
335
+ test_clone_functionality
336
+ test_bidirectional_sync
337
+ test_file_operations
338
+
339
+ # Summary
340
+ echo ""
341
+ echo "======================================"
342
+ echo "Test Results Summary"
343
+ echo "======================================"
344
+ echo "Tests Run: $TESTS_RUN"
345
+ echo "Tests Passed: $TESTS_PASSED"
346
+ echo "Tests Failed: $TESTS_FAILED"
347
+
348
+ if [ $TESTS_FAILED -eq 0 ]; then
349
+ log_success "All tests passed!"
350
+ exit 0
351
+ else
352
+ log_error "Some tests failed!"
353
+ exit 1
354
+ fi
355
+ }
356
+
357
+ # Check if jq is available (optional dependency)
358
+ if ! command -v jq &> /dev/null; then
359
+ log_warning "jq is not installed - some tests may be skipped"
360
+ fi
361
+
362
+ # Run the tests
363
+ main "$@"
@@ -0,0 +1,339 @@
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
+ import { ChangeDetector } from "../../src/core/change-detection";
12
+ import { ChangeType } from "../../src/types";
13
+
14
+ describe("Sync Engine Deletion Integration", () => {
15
+ let testDir: string;
16
+ let snapshotManager: SnapshotManager;
17
+
18
+ beforeEach(async () => {
19
+ testDir = await fs.mkdtemp(path.join(tmpdir(), "sync-deletion-test-"));
20
+ snapshotManager = new SnapshotManager(testDir);
21
+ });
22
+
23
+ afterEach(async () => {
24
+ await fs.rm(testDir, { recursive: true, force: true });
25
+ });
26
+
27
+ describe("Deletion Detection Logic", () => {
28
+ it("should properly detect local file deletions", async () => {
29
+ console.log("\n๐Ÿงช Testing Local File Deletion Detection");
30
+
31
+ // Create initial state
32
+ const filePath = path.join(testDir, "will-be-deleted.ts");
33
+ const content = "interface ToDelete { id: number; }";
34
+ await writeFileContent(filePath, content);
35
+
36
+ // Create snapshot representing the "before" state
37
+ const snapshot = snapshotManager.createEmpty();
38
+ snapshotManager.updateFileEntry(snapshot, "will-be-deleted.ts", {
39
+ path: filePath,
40
+ url: "automerge:deletion-test" as any,
41
+ head: ["before-deletion"] as any,
42
+ extension: "ts",
43
+ mimeType: "text/typescript",
44
+ });
45
+
46
+ console.log(`๐Ÿ“„ Created file: ${filePath}`);
47
+ console.log(`๐Ÿ“ธ Snapshot has ${snapshot.files.size} files`);
48
+
49
+ // Verify initial state
50
+ expect(await pathExists(filePath)).toBe(true);
51
+ expect(snapshot.files.has("will-be-deleted.ts")).toBe(true);
52
+
53
+ // Simulate user deleting the file
54
+ await removePath(filePath);
55
+ console.log(`๐Ÿ—‘๏ธ File deleted from filesystem`);
56
+
57
+ // File should be gone from filesystem but still in snapshot
58
+ expect(await pathExists(filePath)).toBe(false);
59
+ expect(snapshot.files.has("will-be-deleted.ts")).toBe(true);
60
+
61
+ console.log(`โœ… Deletion properly detected`);
62
+ });
63
+
64
+ it("should handle multiple file deletions correctly", async () => {
65
+ console.log("\n๐Ÿงช Testing Multiple File Deletions");
66
+
67
+ const testFiles = [
68
+ { name: "delete1.ts", content: "interface One { x: number; }" },
69
+ { name: "delete2.js", content: "const two = 'value';" },
70
+ { name: "delete3.json", content: '{"three": true}' },
71
+ ];
72
+
73
+ const snapshot = snapshotManager.createEmpty();
74
+
75
+ // Create all files and add to snapshot
76
+ for (const file of testFiles) {
77
+ const filePath = path.join(testDir, file.name);
78
+ await writeFileContent(filePath, file.content);
79
+
80
+ snapshotManager.updateFileEntry(snapshot, file.name, {
81
+ path: filePath,
82
+ url: `automerge:${file.name}` as any,
83
+ head: [`head-${file.name}`] as any,
84
+ extension: path.extname(file.name).slice(1),
85
+ mimeType: file.name.endsWith(".ts")
86
+ ? "text/typescript"
87
+ : "text/plain",
88
+ });
89
+
90
+ console.log(`๐Ÿ“„ Created: ${file.name}`);
91
+ }
92
+
93
+ expect(snapshot.files.size).toBe(3);
94
+
95
+ // Delete all files
96
+ for (const file of testFiles) {
97
+ const filePath = path.join(testDir, file.name);
98
+ await removePath(filePath);
99
+ console.log(`๐Ÿ—‘๏ธ Deleted: ${file.name}`);
100
+ }
101
+
102
+ // Verify all files are gone from filesystem
103
+ for (const file of testFiles) {
104
+ const filePath = path.join(testDir, file.name);
105
+ expect(await pathExists(filePath)).toBe(false);
106
+ }
107
+
108
+ // Snapshot should still have entries (until sync processes them)
109
+ expect(snapshot.files.size).toBe(3);
110
+
111
+ // Simulate sync engine processing the deletions
112
+ for (const file of testFiles) {
113
+ snapshotManager.removeFileEntry(snapshot, file.name);
114
+ console.log(`๐Ÿ“ธ Removed from snapshot: ${file.name}`);
115
+ }
116
+
117
+ expect(snapshot.files.size).toBe(0);
118
+ console.log(`โœ… Multiple deletions handled correctly`);
119
+ });
120
+ });
121
+
122
+ describe("Deletion Timing and Race Conditions", () => {
123
+ it("should handle rapid create-modify-delete sequences", async () => {
124
+ console.log("\n๐Ÿงช Testing Rapid Create-Modify-Delete Sequences");
125
+
126
+ const filePath = path.join(testDir, "rapid-changes.ts");
127
+ const snapshot = snapshotManager.createEmpty();
128
+
129
+ for (let i = 0; i < 3; i++) {
130
+ console.log(`\n--- Cycle ${i + 1} ---`);
131
+
132
+ // Create
133
+ const content = `interface Cycle${i} { value: ${i}; }`;
134
+ await writeFileContent(filePath, content);
135
+ console.log(`๐Ÿ“„ Created with content: "${content}"`);
136
+
137
+ // Add to snapshot
138
+ snapshotManager.updateFileEntry(snapshot, "rapid-changes.ts", {
139
+ path: filePath,
140
+ url: `automerge:cycle-${i}` as any,
141
+ head: [`head-${i}`] as any,
142
+ extension: "ts",
143
+ mimeType: "text/typescript",
144
+ });
145
+
146
+ // Modify
147
+ const modifiedContent = content + `\n// Modified in cycle ${i}`;
148
+ await writeFileContent(filePath, modifiedContent);
149
+ console.log(`๐Ÿ“ Modified content`);
150
+
151
+ // Delete
152
+ await removePath(filePath);
153
+ console.log(`๐Ÿ—‘๏ธ Deleted`);
154
+
155
+ // Verify deletion
156
+ expect(await pathExists(filePath)).toBe(false);
157
+
158
+ // Clean up snapshot
159
+ snapshotManager.removeFileEntry(snapshot, "rapid-changes.ts");
160
+ }
161
+
162
+ console.log(`โœ… Rapid sequences handled without errors`);
163
+ });
164
+
165
+ it("should handle deletion during content modification attempts", async () => {
166
+ console.log("\n๐Ÿงช Testing Deletion During Modification");
167
+
168
+ const filePath = path.join(testDir, "modify-delete-race.ts");
169
+ const initialContent = "interface Race { test: boolean; }";
170
+
171
+ // Create initial file
172
+ await writeFileContent(filePath, initialContent);
173
+ console.log(`๐Ÿ“„ Created file with initial content`);
174
+
175
+ // Start modification and deletion concurrently
176
+ const modifyPromise = writeFileContent(
177
+ filePath,
178
+ initialContent + "\n// Modified"
179
+ );
180
+ const deletePromise = (async () => {
181
+ // Small delay to let modification start
182
+ await new Promise((resolve) => setTimeout(resolve, 1));
183
+ await removePath(filePath);
184
+ })();
185
+
186
+ // Wait for both operations to complete
187
+ await Promise.allSettled([modifyPromise, deletePromise]);
188
+
189
+ // File should be deleted regardless of modification timing
190
+ expect(await pathExists(filePath)).toBe(false);
191
+ console.log(`โœ… File properly deleted despite concurrent modification`);
192
+ });
193
+ });
194
+
195
+ describe("Directory Structure Impact", () => {
196
+ it("should handle deletion of files in nested directories", async () => {
197
+ console.log("\n๐Ÿงช Testing Nested Directory File Deletion");
198
+
199
+ // Create nested structure
200
+ const nestedDir = path.join(testDir, "src", "components");
201
+ const filePath = path.join(nestedDir, "Button.tsx");
202
+ const content = "export const Button = () => <button>Click</button>;";
203
+
204
+ await fs.mkdir(nestedDir, { recursive: true });
205
+ await writeFileContent(filePath, content);
206
+
207
+ const snapshot = snapshotManager.createEmpty();
208
+ snapshotManager.updateFileEntry(snapshot, "src/components/Button.tsx", {
209
+ path: filePath,
210
+ url: "automerge:nested-button" as any,
211
+ head: ["nested-head"] as any,
212
+ extension: "tsx",
213
+ mimeType: "text/tsx",
214
+ });
215
+
216
+ console.log(`๐Ÿ“ Created nested structure: ${nestedDir}`);
217
+ console.log(`๐Ÿ“„ Created file: src/components/Button.tsx`);
218
+
219
+ // Delete just the file (not the directories)
220
+ await removePath(filePath);
221
+ console.log(`๐Ÿ—‘๏ธ Deleted file, kept directory structure`);
222
+
223
+ // File should be gone, directories should remain
224
+ expect(await pathExists(filePath)).toBe(false);
225
+ expect(await pathExists(nestedDir)).toBe(true);
226
+ expect(await pathExists(path.join(testDir, "src"))).toBe(true);
227
+
228
+ // Simulate snapshot cleanup
229
+ snapshotManager.removeFileEntry(snapshot, "src/components/Button.tsx");
230
+ expect(snapshot.files.size).toBe(0);
231
+
232
+ console.log(`โœ… Nested file deletion handled correctly`);
233
+ });
234
+
235
+ it("should handle deletion of entire directory trees", async () => {
236
+ console.log("\n๐Ÿงช Testing Directory Tree Deletion");
237
+
238
+ // Create multiple files in nested structure
239
+ const testStructure = [
240
+ "src/utils/helpers.ts",
241
+ "src/utils/constants.ts",
242
+ "src/components/Button.tsx",
243
+ "src/components/Input.tsx",
244
+ "src/types/index.ts",
245
+ ];
246
+
247
+ const snapshot = snapshotManager.createEmpty();
248
+
249
+ for (const relativePath of testStructure) {
250
+ const fullPath = path.join(testDir, relativePath);
251
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
252
+ await writeFileContent(fullPath, `// Content for ${relativePath}`);
253
+
254
+ snapshotManager.updateFileEntry(snapshot, relativePath, {
255
+ path: fullPath,
256
+ url: `automerge:${relativePath.replace(/[\/\.]/g, "-")}` as any,
257
+ head: [`head-${relativePath}`] as any,
258
+ extension: path.extname(relativePath).slice(1),
259
+ mimeType: "text/typescript",
260
+ });
261
+
262
+ console.log(`๐Ÿ“„ Created: ${relativePath}`);
263
+ }
264
+
265
+ expect(snapshot.files.size).toBe(5);
266
+
267
+ // Delete entire src directory
268
+ await removePath(path.join(testDir, "src"));
269
+ console.log(`๐Ÿ—‘๏ธ Deleted entire src/ directory tree`);
270
+
271
+ // Verify all files and directories are gone
272
+ for (const relativePath of testStructure) {
273
+ const fullPath = path.join(testDir, relativePath);
274
+ expect(await pathExists(fullPath)).toBe(false);
275
+ }
276
+ expect(await pathExists(path.join(testDir, "src"))).toBe(false);
277
+
278
+ // Simulate snapshot cleanup for all files
279
+ for (const relativePath of testStructure) {
280
+ snapshotManager.removeFileEntry(snapshot, relativePath);
281
+ console.log(`๐Ÿ“ธ Removed from snapshot: ${relativePath}`);
282
+ }
283
+
284
+ expect(snapshot.files.size).toBe(0);
285
+ console.log(`โœ… Directory tree deletion handled correctly`);
286
+ });
287
+ });
288
+
289
+ describe("Error Recovery and Edge Cases", () => {
290
+ it("should handle deletion of non-existent files gracefully", async () => {
291
+ console.log("\n๐Ÿงช Testing Non-Existent File Deletion");
292
+
293
+ const nonExistentPath = path.join(testDir, "never-existed.ts");
294
+
295
+ // Attempt to delete non-existent file (should not throw)
296
+ await expect(removePath(nonExistentPath)).resolves.not.toThrow();
297
+ console.log(`โœ… Non-existent file deletion handled gracefully`);
298
+
299
+ // Attempt to remove from snapshot (should not throw)
300
+ const snapshot = snapshotManager.createEmpty();
301
+ expect(() => {
302
+ snapshotManager.removeFileEntry(snapshot, "never-existed.ts");
303
+ }).not.toThrow();
304
+ console.log(`โœ… Non-existent snapshot entry removal handled gracefully`);
305
+ });
306
+
307
+ it("should provide debugging info for deletion failures", async () => {
308
+ console.log("\n๐Ÿงช Deletion Debugging Information");
309
+
310
+ const debugFilePath = path.join(testDir, "debug-deletion.ts");
311
+ const content = "interface Debug { info: string; }";
312
+
313
+ try {
314
+ // Create file
315
+ await writeFileContent(debugFilePath, content);
316
+ console.log(`๐Ÿ“„ File created: ${debugFilePath}`);
317
+ console.log(`๐Ÿ“ File size: ${content.length} characters`);
318
+
319
+ // Verify file exists and is readable
320
+ const readBack = await readFileContent(debugFilePath);
321
+ console.log(`๐Ÿ“– File readable: ${typeof readBack === "string"}`);
322
+ console.log(`โœ… Content matches: ${readBack === content}`);
323
+
324
+ // Delete file
325
+ const deleteStart = Date.now();
326
+ await removePath(debugFilePath);
327
+ const deleteTime = Date.now() - deleteStart;
328
+
329
+ console.log(`๐Ÿ—‘๏ธ Deletion completed in ${deleteTime}ms`);
330
+ console.log(
331
+ `๐Ÿ” File exists after deletion: ${await pathExists(debugFilePath)}`
332
+ );
333
+ } catch (error) {
334
+ console.error(`โŒ Deletion test failed:`, error);
335
+ throw error;
336
+ }
337
+ });
338
+ });
339
+ });