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,297 @@
1
+ #!/bin/bash
2
+
3
+ # Deletion Sync Test: Bob deletes, Alice receives deletion
4
+ # Tests end-to-end deletion propagation through sync
5
+
6
+ set -e # Exit on any error
7
+
8
+ # Colors for output
9
+ RED='\033[0;31m'
10
+ GREEN='\033[0;32m'
11
+ BLUE='\033[0;34m'
12
+ YELLOW='\033[1;33m'
13
+ NC='\033[0m' # No Color
14
+
15
+ # Test configuration
16
+ TEST_DIR="/tmp/pushwork-deletion-test-$$"
17
+ BOB_DIR="$TEST_DIR/bob"
18
+ ALICE_DIR="$TEST_DIR/alice"
19
+ PUSHWORK_CMD="npm run start --silent --"
20
+ SYNC_SERVER="ws://localhost:3030"
21
+ STORAGE_ID="deletion-test-$(date +%s)"
22
+ TEST_FILE="shared-document.ts"
23
+ TEST_CONTENT="interface SharedInterface { id: number; name: string; }"
24
+
25
+ # Logging functions
26
+ log_info() {
27
+ echo -e "${BLUE}[INFO]${NC} $1"
28
+ }
29
+
30
+ log_success() {
31
+ echo -e "${GREEN}[PASS]${NC} $1"
32
+ }
33
+
34
+ log_error() {
35
+ echo -e "${RED}[FAIL]${NC} $1"
36
+ }
37
+
38
+ log_test() {
39
+ echo -e "${YELLOW}[TEST]${NC} $1"
40
+ }
41
+
42
+ log_warning() {
43
+ echo -e "${YELLOW}[WARN]${NC} $1"
44
+ }
45
+
46
+ # Cleanup function
47
+ cleanup() {
48
+ if [ -d "$TEST_DIR" ]; then
49
+ log_info "Cleaning up test directories..."
50
+ rm -rf "$TEST_DIR"
51
+ fi
52
+ }
53
+
54
+ # Error handler
55
+ handle_error() {
56
+ log_error "Test failed at line $1"
57
+ log_info "Bob's directory contents:"
58
+ if [ -d "$BOB_DIR" ]; then
59
+ ls -la "$BOB_DIR" || true
60
+ fi
61
+ log_info "Alice's directory contents:"
62
+ if [ -d "$ALICE_DIR" ]; then
63
+ ls -la "$ALICE_DIR" || true
64
+ fi
65
+ cleanup
66
+ exit 1
67
+ }
68
+
69
+ # Set up error handling
70
+ trap 'handle_error $LINENO' ERR
71
+ trap cleanup EXIT
72
+
73
+ # Helper function to run pushwork commands
74
+ run_pushwork() {
75
+ local dir="$1"
76
+ local cmd="$2"
77
+ local user="$3"
78
+
79
+ log_test "${user}: Running 'pushwork $cmd'"
80
+ cd "$dir"
81
+
82
+ # Capture both stdout and stderr
83
+ if output=$(eval "$PUSHWORK_CMD $cmd" 2>&1); then
84
+ if [ -n "$output" ]; then
85
+ echo " Output: $output"
86
+ fi
87
+ return 0
88
+ else
89
+ log_error "${user}: Command failed: $output"
90
+ return 1
91
+ fi
92
+ }
93
+
94
+ # Helper function to check if file exists
95
+ check_file_exists() {
96
+ local dir="$1"
97
+ local file="$2"
98
+ local user="$3"
99
+ local should_exist="$4"
100
+
101
+ if [ -f "$dir/$file" ]; then
102
+ if [ "$should_exist" = "true" ]; then
103
+ log_success "${user}: File '$file' exists (expected)"
104
+ return 0
105
+ else
106
+ log_error "${user}: File '$file' exists (should be deleted)"
107
+ return 1
108
+ fi
109
+ else
110
+ if [ "$should_exist" = "false" ]; then
111
+ log_success "${user}: File '$file' is deleted (expected)"
112
+ return 0
113
+ else
114
+ log_error "${user}: File '$file' is missing (should exist)"
115
+ return 1
116
+ fi
117
+ fi
118
+ }
119
+
120
+ # Helper function to show directory contents
121
+ show_directory_contents() {
122
+ local dir="$1"
123
+ local user="$2"
124
+
125
+ log_info "${user}'s directory contents:"
126
+ cd "$dir"
127
+ if [ "$(ls -A .)" ]; then
128
+ ls -la . | grep -v "^total" | tail -n +2 | while read line; do
129
+ echo " $line"
130
+ done
131
+ else
132
+ echo " (empty directory)"
133
+ fi
134
+ }
135
+
136
+ # Main test function
137
+ main() {
138
+ echo "======================================"
139
+ echo "Pushwork Deletion Sync Test"
140
+ echo "======================================"
141
+ echo "Testing: Bob deletes file → sync → Alice loses file"
142
+ echo ""
143
+
144
+ # Setup
145
+ log_info "Setting up test environment..."
146
+ mkdir -p "$BOB_DIR" "$ALICE_DIR"
147
+
148
+ log_info "Test configuration:"
149
+ echo " Bob's directory: $BOB_DIR"
150
+ echo " Alice's directory: $ALICE_DIR"
151
+ echo " Sync server: $SYNC_SERVER"
152
+ echo " Storage ID: $STORAGE_ID"
153
+ echo " Test file: $TEST_FILE"
154
+ echo ""
155
+
156
+ # Phase 1: Initialize both repositories
157
+ log_info "=== Phase 1: Initialize Repositories ==="
158
+
159
+ log_test "Initializing Bob's repository..."
160
+ run_pushwork "$BOB_DIR" "init . --sync-server '$SYNC_SERVER' --storage-id '$STORAGE_ID'" "Bob"
161
+
162
+ log_test "Initializing Alice's repository..."
163
+ run_pushwork "$ALICE_DIR" "clone --sync-server '$SYNC_SERVER' --storage-id '$STORAGE_ID' ." "Alice"
164
+
165
+ # Phase 2: Create initial shared file
166
+ log_info "=== Phase 2: Create Shared File ==="
167
+
168
+ log_test "Bob creates the shared file..."
169
+ echo "$TEST_CONTENT" > "$BOB_DIR/$TEST_FILE"
170
+ check_file_exists "$BOB_DIR" "$TEST_FILE" "Bob" "true"
171
+
172
+ log_test "Bob commits the file..."
173
+ run_pushwork "$BOB_DIR" "commit ." "Bob"
174
+
175
+ log_test "Bob syncs to share the file..."
176
+ run_pushwork "$BOB_DIR" "sync" "Bob"
177
+
178
+ log_test "Alice syncs to receive the file..."
179
+ run_pushwork "$ALICE_DIR" "sync" "Alice"
180
+
181
+ # Verify both have the file
182
+ check_file_exists "$BOB_DIR" "$TEST_FILE" "Bob" "true"
183
+ check_file_exists "$ALICE_DIR" "$TEST_FILE" "Alice" "true"
184
+
185
+ log_success "Phase 2: Both users have the shared file"
186
+ echo ""
187
+
188
+ # Phase 3: Bob deletes the file
189
+ log_info "=== Phase 3: Bob Deletes File ==="
190
+
191
+ show_directory_contents "$BOB_DIR" "Bob (before deletion)"
192
+
193
+ log_test "Bob deletes the shared file..."
194
+ rm "$BOB_DIR/$TEST_FILE"
195
+ check_file_exists "$BOB_DIR" "$TEST_FILE" "Bob" "false"
196
+
197
+ show_directory_contents "$BOB_DIR" "Bob (after deletion)"
198
+
199
+ log_test "Bob commits the deletion..."
200
+ run_pushwork "$BOB_DIR" "commit ." "Bob"
201
+
202
+ # Verify file is still gone on Bob's side
203
+ check_file_exists "$BOB_DIR" "$TEST_FILE" "Bob" "false"
204
+
205
+ log_success "Phase 3: Bob successfully deleted and committed"
206
+ echo ""
207
+
208
+ # Phase 4: Bob syncs the deletion
209
+ log_info "=== Phase 4: Bob Syncs Deletion ==="
210
+
211
+ log_test "Bob syncs to propagate the deletion..."
212
+ run_pushwork "$BOB_DIR" "sync" "Bob"
213
+
214
+ # Verify file is still gone on Bob's side after sync
215
+ check_file_exists "$BOB_DIR" "$TEST_FILE" "Bob" "false"
216
+
217
+ log_test "Bob checks status after sync..."
218
+ run_pushwork "$BOB_DIR" "status" "Bob"
219
+
220
+ log_success "Phase 4: Bob's deletion synced successfully"
221
+ echo ""
222
+
223
+ # Phase 5: Alice syncs to receive the deletion
224
+ log_info "=== Phase 5: Alice Syncs to Receive Deletion ==="
225
+
226
+ show_directory_contents "$ALICE_DIR" "Alice (before sync)"
227
+
228
+ # Alice should still have the file before syncing
229
+ check_file_exists "$ALICE_DIR" "$TEST_FILE" "Alice" "true"
230
+
231
+ log_test "Alice syncs to receive Bob's deletion..."
232
+ run_pushwork "$ALICE_DIR" "sync" "Alice"
233
+
234
+ show_directory_contents "$ALICE_DIR" "Alice (after sync)"
235
+
236
+ # Critical test: Alice should now have the file deleted
237
+ check_file_exists "$ALICE_DIR" "$TEST_FILE" "Alice" "false"
238
+
239
+ log_test "Alice checks status after sync..."
240
+ run_pushwork "$ALICE_DIR" "status" "Alice"
241
+
242
+ log_success "Phase 5: Alice received the deletion successfully"
243
+ echo ""
244
+
245
+ # Phase 6: Verification
246
+ log_info "=== Phase 6: Final Verification ==="
247
+
248
+ log_test "Final verification of both repositories..."
249
+
250
+ # Both should have no trace of the deleted file
251
+ check_file_exists "$BOB_DIR" "$TEST_FILE" "Bob" "false"
252
+ check_file_exists "$ALICE_DIR" "$TEST_FILE" "Alice" "false"
253
+
254
+ # Check for any unexpected files
255
+ show_directory_contents "$BOB_DIR" "Bob (final)"
256
+ show_directory_contents "$ALICE_DIR" "Alice (final)"
257
+
258
+ # Run final status checks
259
+ log_test "Bob's final status:"
260
+ run_pushwork "$BOB_DIR" "status" "Bob"
261
+
262
+ log_test "Alice's final status:"
263
+ run_pushwork "$ALICE_DIR" "status" "Alice"
264
+
265
+ log_success "Phase 6: All verifications passed"
266
+ echo ""
267
+
268
+ # Success!
269
+ echo "======================================"
270
+ echo "🎉 DELETION SYNC TEST PASSED! 🎉"
271
+ echo "======================================"
272
+ echo "✅ Bob deleted file successfully"
273
+ echo "✅ Bob's sync propagated deletion"
274
+ echo "✅ Alice received deletion correctly"
275
+ echo "✅ Both repositories in sync"
276
+ echo ""
277
+ echo "This test validates that file deletions"
278
+ echo "propagate correctly through the sync engine!"
279
+ }
280
+
281
+ # Check if we're in the right directory
282
+ if [ ! -f "package.json" ] || ! grep -q "pushwork" package.json; then
283
+ log_error "This script must be run from the pushwork project root directory"
284
+ exit 1
285
+ fi
286
+
287
+ # Check if dependencies are available
288
+ if ! command -v npm &> /dev/null; then
289
+ log_error "npm is not installed or not in PATH"
290
+ exit 1
291
+ fi
292
+
293
+ # Run the test
294
+ main
295
+
296
+ echo ""
297
+ echo "Test completed successfully! 🚀"
@@ -0,0 +1,152 @@
1
+ import * as path from "path";
2
+ import * as fs from "fs/promises";
3
+ import { tmpdir } from "os";
4
+ import { ConfigManager } from "../../src/config";
5
+ import { DirectoryConfig } from "../../src/types";
6
+ import {
7
+ ensureDirectoryExists,
8
+ writeFileContent,
9
+ listDirectory,
10
+ } from "../../src/utils";
11
+
12
+ describe("Exclude Patterns", () => {
13
+ let tmpDir: string;
14
+ let syncToolDir: string;
15
+ let configManager: ConfigManager;
16
+
17
+ beforeEach(async () => {
18
+ tmpDir = await fs.mkdtemp(path.join(tmpdir(), "sync-test-"));
19
+ syncToolDir = path.join(tmpDir, ".pushwork");
20
+ await ensureDirectoryExists(syncToolDir);
21
+ await ensureDirectoryExists(path.join(syncToolDir, "automerge"));
22
+
23
+ configManager = new ConfigManager(tmpDir);
24
+ });
25
+
26
+ afterEach(async () => {
27
+ await fs.rm(tmpDir, { recursive: true, force: true });
28
+ });
29
+
30
+ it("should exclude .pushwork directory from filesystem listing", async () => {
31
+ // Create files both inside and outside .pushwork directory
32
+ await writeFileContent(
33
+ path.join(tmpDir, "regular-file.txt"),
34
+ "regular content"
35
+ );
36
+ await writeFileContent(
37
+ path.join(tmpDir, "another-file.md"),
38
+ "markdown content"
39
+ );
40
+ await writeFileContent(
41
+ path.join(syncToolDir, "snapshot.json"),
42
+ '{"timestamp": 123}'
43
+ );
44
+ await writeFileContent(
45
+ path.join(syncToolDir, "config.json"),
46
+ '{"test": true}'
47
+ );
48
+
49
+ // Create nested directory with file inside .pushwork
50
+ const nestedDir = path.join(syncToolDir, "nested");
51
+ await ensureDirectoryExists(nestedDir);
52
+ await writeFileContent(
53
+ path.join(nestedDir, "internal.log"),
54
+ "internal log data"
55
+ );
56
+
57
+ // Test listDirectory with exclude patterns
58
+ const excludePatterns = [".pushwork"];
59
+ const entries = await listDirectory(tmpDir, true, excludePatterns);
60
+
61
+ // Verify that .pushwork files are excluded
62
+ const filePaths = entries.map((entry) => path.relative(tmpDir, entry.path));
63
+
64
+ expect(filePaths).toContain("regular-file.txt");
65
+ expect(filePaths).toContain("another-file.md");
66
+ expect(filePaths).not.toContain(".pushwork/snapshot.json");
67
+ expect(filePaths).not.toContain(".pushwork/config.json");
68
+ expect(filePaths).not.toContain(".pushwork/nested/internal.log");
69
+ });
70
+
71
+ it("should exclude files matching glob patterns", async () => {
72
+ // Create files that should and shouldn't be excluded
73
+ await writeFileContent(path.join(tmpDir, "include.txt"), "include me");
74
+ await writeFileContent(path.join(tmpDir, "exclude.tmp"), "exclude me");
75
+ await writeFileContent(path.join(tmpDir, "debug.log"), "exclude me too");
76
+ await writeFileContent(path.join(tmpDir, "readme.md"), "include me");
77
+
78
+ // Create node_modules directory with files
79
+ const nodeModulesDir = path.join(tmpDir, "node_modules");
80
+ await ensureDirectoryExists(nodeModulesDir);
81
+ await writeFileContent(
82
+ path.join(nodeModulesDir, "package.json"),
83
+ "exclude me"
84
+ );
85
+
86
+ // Test listDirectory with various exclude patterns
87
+ const excludePatterns = ["*.tmp", "*.log", "node_modules", ".pushwork"];
88
+ const entries = await listDirectory(tmpDir, true, excludePatterns);
89
+
90
+ // Verify correct files are included/excluded
91
+ const filePaths = entries.map((entry) => path.relative(tmpDir, entry.path));
92
+
93
+ expect(filePaths).toContain("include.txt");
94
+ expect(filePaths).toContain("readme.md");
95
+ expect(filePaths).not.toContain("exclude.tmp");
96
+ expect(filePaths).not.toContain("debug.log");
97
+ expect(filePaths).not.toContain("node_modules/package.json");
98
+ });
99
+
100
+ it("should use merged configuration exclude patterns", async () => {
101
+ // Create global config
102
+ await configManager.createDefaultGlobal();
103
+
104
+ // Create local config with additional exclude patterns
105
+ const localConfig: DirectoryConfig = {
106
+ sync_server: "wss://test.server.com",
107
+ sync_enabled: true,
108
+ defaults: {
109
+ exclude_patterns: [".git", "*.tmp", ".pushwork", "*.env"],
110
+ large_file_threshold: "100MB",
111
+ },
112
+ diff: {
113
+ show_binary: false,
114
+ },
115
+ sync: {
116
+ move_detection_threshold: 0.8,
117
+ prompt_threshold: 0.5,
118
+ auto_sync: false,
119
+ parallel_operations: 4,
120
+ },
121
+ };
122
+ await configManager.save(localConfig);
123
+
124
+ // Get merged config
125
+ const mergedConfig = await configManager.getMerged();
126
+
127
+ // Verify .pushwork is in the exclude patterns
128
+ expect(mergedConfig.defaults.exclude_patterns).toContain(".pushwork");
129
+ expect(mergedConfig.defaults.exclude_patterns).toContain("*.env");
130
+ expect(mergedConfig.defaults.exclude_patterns).toContain(".git");
131
+
132
+ // Create test files
133
+ await writeFileContent(path.join(tmpDir, "include.txt"), "include me");
134
+ await writeFileContent(path.join(tmpDir, "secret.env"), "exclude me");
135
+ await writeFileContent(
136
+ path.join(syncToolDir, "snapshot.json"),
137
+ "exclude me"
138
+ );
139
+
140
+ // Test with merged exclude patterns
141
+ const entries = await listDirectory(
142
+ tmpDir,
143
+ true,
144
+ mergedConfig.defaults.exclude_patterns
145
+ );
146
+ const filePaths = entries.map((entry) => path.relative(tmpDir, entry.path));
147
+
148
+ expect(filePaths).toContain("include.txt");
149
+ expect(filePaths).not.toContain("secret.env");
150
+ expect(filePaths).not.toContain(".pushwork/snapshot.json");
151
+ });
152
+ });