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,309 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ import * as tmp from "tmp";
4
+ import { ConfigManager } from "../../src/config";
5
+ import { DirectoryConfig } from "../../src/types";
6
+
7
+ describe("Sync Flow Integration", () => {
8
+ let tmpDir: string;
9
+ let cleanup: () => void;
10
+
11
+ beforeEach(() => {
12
+ const tmpObj = tmp.dirSync({ unsafeCleanup: true });
13
+ tmpDir = tmpObj.name;
14
+ cleanup = tmpObj.removeCallback;
15
+ });
16
+
17
+ afterEach(() => {
18
+ cleanup();
19
+ });
20
+
21
+ describe("Configuration Management", () => {
22
+ it("should create and load configuration", async () => {
23
+ const configManager = new ConfigManager(tmpDir);
24
+
25
+ // Create test config
26
+ const testConfig: DirectoryConfig = {
27
+ sync_server: "wss://test.server.com",
28
+ sync_enabled: true,
29
+ defaults: {
30
+ exclude_patterns: [".git", "*.tmp"],
31
+ large_file_threshold: "1MB",
32
+ },
33
+ diff: {
34
+ show_binary: false,
35
+ },
36
+ sync: {
37
+ move_detection_threshold: 0.8,
38
+ prompt_threshold: 0.5,
39
+ auto_sync: false,
40
+ parallel_operations: 2,
41
+ },
42
+ };
43
+
44
+ await configManager.save(testConfig);
45
+
46
+ const loadedConfig = await configManager.load();
47
+ expect(loadedConfig).toEqual(testConfig);
48
+ });
49
+
50
+ it("should merge global and local configurations", async () => {
51
+ const configManager = new ConfigManager(tmpDir);
52
+
53
+ // Create default global config
54
+ await configManager.createDefaultGlobal();
55
+
56
+ // Test directory config
57
+ const localConfig: DirectoryConfig = {
58
+ sync_server: "wss://local.server.com",
59
+ sync_enabled: true,
60
+ defaults: {
61
+ exclude_patterns: [".git", "*.tmp"],
62
+ large_file_threshold: "5MB",
63
+ },
64
+ diff: {
65
+ show_binary: true,
66
+ },
67
+ sync: {
68
+ move_detection_threshold: 0.9,
69
+ prompt_threshold: 0.6,
70
+ auto_sync: true,
71
+ parallel_operations: 1,
72
+ },
73
+ };
74
+
75
+ await configManager.save(localConfig);
76
+
77
+ // Verify merged config
78
+ const mergedConfig = await configManager.getMerged();
79
+ expect(mergedConfig.sync_server).toBe("wss://local.server.com");
80
+ expect(mergedConfig.defaults?.exclude_patterns).toContain(".git");
81
+ expect(mergedConfig.defaults?.large_file_threshold).toBe("5MB");
82
+ expect(mergedConfig.diff?.show_binary).toBe(true);
83
+ expect(mergedConfig.sync?.move_detection_threshold).toBe(0.9);
84
+ });
85
+ });
86
+
87
+ describe("File System Operations", () => {
88
+ it("should handle file creation and modification", async () => {
89
+ // Create initial file structure
90
+ await fs.mkdir(path.join(tmpDir, "subdir"));
91
+ await fs.writeFile(path.join(tmpDir, "file1.txt"), "Initial content");
92
+ await fs.writeFile(
93
+ path.join(tmpDir, "subdir", "file2.txt"),
94
+ "Nested content"
95
+ );
96
+
97
+ // Modify files
98
+ await fs.writeFile(path.join(tmpDir, "file1.txt"), "Modified content");
99
+ await fs.writeFile(path.join(tmpDir, "new-file.txt"), "New file content");
100
+
101
+ // Delete file
102
+ await fs.unlink(path.join(tmpDir, "subdir", "file2.txt"));
103
+
104
+ // Verify final state
105
+ const file1Content = await fs.readFile(
106
+ path.join(tmpDir, "file1.txt"),
107
+ "utf8"
108
+ );
109
+ expect(file1Content).toBe("Modified content");
110
+
111
+ const newFileContent = await fs.readFile(
112
+ path.join(tmpDir, "new-file.txt"),
113
+ "utf8"
114
+ );
115
+ expect(newFileContent).toBe("New file content");
116
+
117
+ try {
118
+ await fs.access(path.join(tmpDir, "subdir", "file2.txt"));
119
+ throw new Error("Deleted file should not exist");
120
+ } catch (error: any) {
121
+ // Expected - file should not exist
122
+ expect(error.code).toBe("ENOENT");
123
+ }
124
+ });
125
+
126
+ it("should handle binary files", async () => {
127
+ const binaryData = new Uint8Array([
128
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
129
+ ]); // PNG header
130
+
131
+ await fs.writeFile(path.join(tmpDir, "image.png"), binaryData);
132
+
133
+ const readData = await fs.readFile(path.join(tmpDir, "image.png"));
134
+ expect(Array.from(readData)).toEqual(Array.from(binaryData));
135
+ });
136
+ });
137
+
138
+ describe("Directory Structure Scenarios", () => {
139
+ it("should handle complex directory structures", async () => {
140
+ const structure = {
141
+ src: {
142
+ components: {
143
+ "Button.tsx": "export const Button = () => <button />",
144
+ "Input.tsx": "export const Input = () => <input />",
145
+ },
146
+ utils: {
147
+ "helpers.ts": "export const helper = () => {}",
148
+ "constants.ts": 'export const API_URL = "http://localhost"',
149
+ },
150
+ "index.ts": 'export * from "./components"',
151
+ },
152
+ "package.json": '{"name": "test-project", "version": "1.0.0"}',
153
+ "README.md": "# Test Project\n\nThis is a test project.",
154
+ };
155
+
156
+ await createDirectoryStructure(tmpDir, structure);
157
+
158
+ // Verify structure was created
159
+ const srcExists = await pathExists(path.join(tmpDir, "src"));
160
+ expect(srcExists).toBe(true);
161
+
162
+ const buttonExists = await pathExists(
163
+ path.join(tmpDir, "src", "components", "Button.tsx")
164
+ );
165
+ expect(buttonExists).toBe(true);
166
+
167
+ const packageContent = await fs.readFile(
168
+ path.join(tmpDir, "package.json"),
169
+ "utf8"
170
+ );
171
+ expect(JSON.parse(packageContent).name).toBe("test-project");
172
+ });
173
+
174
+ it("should handle file moves and renames", async () => {
175
+ // Create initial files
176
+ await fs.writeFile(path.join(tmpDir, "old-name.txt"), "File content");
177
+ await fs.mkdir(path.join(tmpDir, "new-dir"));
178
+
179
+ // Simulate move operation
180
+ await fs.rename(
181
+ path.join(tmpDir, "old-name.txt"),
182
+ path.join(tmpDir, "new-dir", "new-name.txt")
183
+ );
184
+
185
+ // Verify move
186
+ const movedFileExists = await pathExists(
187
+ path.join(tmpDir, "new-dir", "new-name.txt")
188
+ );
189
+ expect(movedFileExists).toBe(true);
190
+
191
+ const oldFileExists = await pathExists(path.join(tmpDir, "old-name.txt"));
192
+ expect(oldFileExists).toBe(false);
193
+
194
+ const content = await fs.readFile(
195
+ path.join(tmpDir, "new-dir", "new-name.txt"),
196
+ "utf8"
197
+ );
198
+ expect(content).toBe("File content");
199
+ });
200
+ });
201
+
202
+ describe("Error Handling", () => {
203
+ it("should handle permission errors gracefully", async () => {
204
+ // Create a file
205
+ const filePath = path.join(tmpDir, "restricted.txt");
206
+ await fs.writeFile(filePath, "content");
207
+
208
+ // Make it read-only (if supported by filesystem)
209
+ try {
210
+ await fs.chmod(filePath, 0o444);
211
+
212
+ // Try to write - should handle error gracefully
213
+ try {
214
+ await fs.writeFile(filePath, "new content");
215
+ // If this succeeds, the filesystem doesn't enforce permissions
216
+ } catch (error) {
217
+ expect(error).toBeDefined();
218
+ // This is expected behavior
219
+ }
220
+ } catch {
221
+ // chmod may not be supported on all filesystems
222
+ console.log(
223
+ "Permission test skipped - filesystem does not support chmod"
224
+ );
225
+ }
226
+ });
227
+
228
+ it("should handle corrupted snapshot files", async () => {
229
+ const configManager = new ConfigManager(tmpDir);
230
+
231
+ // Create .pushwork directory
232
+ const syncToolDir = path.join(tmpDir, ".pushwork");
233
+ await fs.mkdir(syncToolDir);
234
+
235
+ // Write corrupted snapshot
236
+ const snapshotPath = path.join(syncToolDir, "snapshot.json");
237
+ await fs.writeFile(snapshotPath, '{"invalid": json}');
238
+
239
+ // Should handle gracefully
240
+ const config = await configManager.load();
241
+ expect(config).toBeNull();
242
+ });
243
+ });
244
+
245
+ describe("Performance Scenarios", () => {
246
+ it("should handle many small files", async () => {
247
+ const fileCount = 100;
248
+ const promises: Promise<void>[] = [];
249
+
250
+ for (let i = 0; i < fileCount; i++) {
251
+ const filePath = path.join(tmpDir, `file-${i}.txt`);
252
+ promises.push(fs.writeFile(filePath, `Content of file ${i}`));
253
+ }
254
+
255
+ await Promise.all(promises);
256
+
257
+ // Verify all files were created
258
+ const files = await fs.readdir(tmpDir);
259
+ const textFiles = files.filter((f) => f.endsWith(".txt"));
260
+ expect(textFiles.length).toBe(fileCount);
261
+ });
262
+
263
+ it("should handle large files efficiently", async () => {
264
+ const largeContent = "x".repeat(1024 * 1024); // 1MB of data
265
+ const filePath = path.join(tmpDir, "large-file.txt");
266
+
267
+ const startTime = Date.now();
268
+ await fs.writeFile(filePath, largeContent);
269
+ const writeTime = Date.now() - startTime;
270
+
271
+ const readStartTime = Date.now();
272
+ const readContent = await fs.readFile(filePath, "utf8");
273
+ const readTime = Date.now() - readStartTime;
274
+
275
+ expect(readContent.length).toBe(largeContent.length);
276
+ expect(writeTime).toBeLessThan(5000); // Should complete within 5 seconds
277
+ expect(readTime).toBeLessThan(5000); // Should complete within 5 seconds
278
+ }, 10000); // 10 second timeout
279
+ });
280
+ });
281
+
282
+ // Helper functions
283
+
284
+ async function pathExists(filePath: string): Promise<boolean> {
285
+ try {
286
+ await fs.access(filePath);
287
+ return true;
288
+ } catch {
289
+ return false;
290
+ }
291
+ }
292
+
293
+ async function createDirectoryStructure(
294
+ basePath: string,
295
+ structure: any
296
+ ): Promise<void> {
297
+ for (const [name, content] of Object.entries(structure)) {
298
+ const fullPath = path.join(basePath, name);
299
+
300
+ if (typeof content === "string") {
301
+ // It's a file
302
+ await fs.writeFile(fullPath, content);
303
+ } else {
304
+ // It's a directory
305
+ await fs.mkdir(fullPath, { recursive: true });
306
+ await createDirectoryStructure(fullPath, content);
307
+ }
308
+ }
309
+ }
@@ -0,0 +1,225 @@
1
+ #!/bin/bash
2
+
3
+ # Pushwork Test Runner
4
+ # Provides options to run different test suites
5
+
6
+ set -e
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'
14
+
15
+ # Helper functions
16
+ log_info() {
17
+ echo -e "${BLUE}[INFO]${NC} $1"
18
+ }
19
+
20
+ log_success() {
21
+ echo -e "${GREEN}[SUCCESS]${NC} $1"
22
+ }
23
+
24
+ log_error() {
25
+ echo -e "${RED}[ERROR]${NC} $1"
26
+ }
27
+
28
+ log_warning() {
29
+ echo -e "${YELLOW}[WARNING]${NC} $1"
30
+ }
31
+
32
+ # Show usage
33
+ show_usage() {
34
+ echo "Pushwork Test Runner"
35
+ echo ""
36
+ echo "Usage: $0 [test-type]"
37
+ echo ""
38
+ echo "Test Types:"
39
+ echo " full Run comprehensive integration tests (default)"
40
+ echo " clone Run focused clone functionality tests"
41
+ echo " conflict Run CRDT conflict resolution tests"
42
+ echo " deletion Run deletion sync behavior tests"
43
+ echo " deletion-behavior Run comprehensive deletion behavior tests"
44
+ echo " unit Run unit tests"
45
+ echo " help Show this help message"
46
+ echo ""
47
+ echo "Examples:"
48
+ echo " $0 # Run full integration tests"
49
+ echo " $0 full # Run full integration tests"
50
+ echo " $0 clone # Run clone-specific tests"
51
+ echo " $0 conflict # Run CRDT conflict resolution tests"
52
+ echo " $0 unit # Run unit tests"
53
+ echo ""
54
+ }
55
+
56
+ # Check dependencies
57
+ check_dependencies() {
58
+ log_info "Checking dependencies..."
59
+
60
+ # Check if Node.js is available
61
+ if ! command -v node &> /dev/null; then
62
+ log_error "Node.js is not installed"
63
+ echo "Please install Node.js from https://nodejs.org/"
64
+ exit 1
65
+ fi
66
+
67
+ # Check if npm is available
68
+ if ! command -v npm &> /dev/null; then
69
+ log_error "npm is not installed"
70
+ echo "Please install npm"
71
+ exit 1
72
+ fi
73
+
74
+ # Check if jq is available (optional for some tests)
75
+ if ! command -v jq &> /dev/null; then
76
+ log_warning "jq is not installed - some configuration tests may be skipped"
77
+ echo "To install jq (optional):"
78
+ echo " macOS: brew install jq"
79
+ echo " Ubuntu/Debian: apt-get install jq"
80
+ echo " Other: https://stedolan.github.io/jq/download/"
81
+ echo ""
82
+ fi
83
+
84
+ log_success "Dependencies check complete"
85
+ }
86
+
87
+ # Run unit tests
88
+ run_unit_tests() {
89
+ log_info "Running unit tests..."
90
+
91
+ if [ -f "package.json" ] && grep -q "\"test\":" package.json; then
92
+ npm test
93
+ else
94
+ log_warning "No unit tests configured in package.json"
95
+
96
+ # Check for individual test files
97
+ if ls test/unit/*.test.* 1> /dev/null 2>&1; then
98
+ log_info "Found unit test files, running with jest/vitest..."
99
+ if command -v jest &> /dev/null; then
100
+ jest test/unit/
101
+ elif command -v vitest &> /dev/null; then
102
+ vitest run test/unit/
103
+ else
104
+ log_error "No test runner found (jest/vitest)"
105
+ exit 1
106
+ fi
107
+ else
108
+ log_warning "No unit test files found"
109
+ fi
110
+ fi
111
+ }
112
+
113
+ # Run clone tests
114
+ run_clone_tests() {
115
+ log_info "Running clone functionality tests..."
116
+
117
+ if [ -f "test/integration/clone-test.sh" ]; then
118
+ ./test/integration/clone-test.sh
119
+ else
120
+ log_error "Clone test script not found"
121
+ exit 1
122
+ fi
123
+ }
124
+
125
+ # Run conflict resolution tests
126
+ run_conflict_tests() {
127
+ log_info "Running conflict resolution tests..."
128
+
129
+ if [ -f "test/integration/conflict-resolution-test.sh" ]; then
130
+ ./test/integration/conflict-resolution-test.sh
131
+ else
132
+ log_error "Conflict resolution test script not found"
133
+ exit 1
134
+ fi
135
+ }
136
+
137
+ # Run deletion sync tests
138
+ run_deletion_tests() {
139
+ log_info "Running deletion sync behavior tests..."
140
+
141
+ if [ -f "test/integration/deletion-sync-test-simple.sh" ]; then
142
+ ./test/integration/deletion-sync-test-simple.sh
143
+ else
144
+ log_error "Deletion sync test script not found"
145
+ exit 1
146
+ fi
147
+ }
148
+
149
+ # Run comprehensive deletion behavior tests
150
+ run_deletion_behavior_tests() {
151
+ log_info "Running comprehensive deletion behavior tests..."
152
+
153
+ if [ -f "test/integration/deletion-behavior-test.sh" ]; then
154
+ ./test/integration/deletion-behavior-test.sh
155
+ else
156
+ log_error "Deletion behavior test script not found"
157
+ exit 1
158
+ fi
159
+ }
160
+
161
+ # Run full integration tests
162
+ run_full_tests() {
163
+ log_info "Running full integration tests..."
164
+
165
+ if [ -f "test/integration/full-integration-test.sh" ]; then
166
+ ./test/integration/full-integration-test.sh
167
+ else
168
+ log_error "Full integration test script not found"
169
+ exit 1
170
+ fi
171
+ }
172
+
173
+ # Main function
174
+ main() {
175
+ local test_type="${1:-full}"
176
+
177
+ echo "======================================"
178
+ echo "Pushwork Test Runner"
179
+ echo "======================================"
180
+
181
+ # Change to project directory if not already there
182
+ if [ ! -f "package.json" ]; then
183
+ log_error "Not in project directory (package.json not found)"
184
+ echo "Please run this script from the project root directory"
185
+ exit 1
186
+ fi
187
+
188
+ check_dependencies
189
+
190
+ case "$test_type" in
191
+ "help"|"-h"|"--help")
192
+ show_usage
193
+ exit 0
194
+ ;;
195
+ "unit")
196
+ run_unit_tests
197
+ ;;
198
+ "clone")
199
+ run_clone_tests
200
+ ;;
201
+ "conflict")
202
+ run_conflict_tests
203
+ ;;
204
+ "deletion")
205
+ run_deletion_tests
206
+ ;;
207
+ "deletion-behavior")
208
+ run_deletion_behavior_tests
209
+ ;;
210
+ "full")
211
+ run_full_tests
212
+ ;;
213
+ *)
214
+ log_error "Unknown test type: $test_type"
215
+ echo ""
216
+ show_usage
217
+ exit 1
218
+ ;;
219
+ esac
220
+
221
+ log_success "Test run complete!"
222
+ }
223
+
224
+ # Run main function with all arguments
225
+ main "$@"