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.
- package/README.md +460 -0
- package/dist/browser/browser-sync-engine.d.ts +64 -0
- package/dist/browser/browser-sync-engine.d.ts.map +1 -0
- package/dist/browser/browser-sync-engine.js +303 -0
- package/dist/browser/browser-sync-engine.js.map +1 -0
- package/dist/browser/filesystem-adapter.d.ts +84 -0
- package/dist/browser/filesystem-adapter.d.ts.map +1 -0
- package/dist/browser/filesystem-adapter.js +413 -0
- package/dist/browser/filesystem-adapter.js.map +1 -0
- package/dist/browser/index.d.ts +36 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +90 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/types.d.ts +70 -0
- package/dist/browser/types.d.ts.map +1 -0
- package/dist/browser/types.js +6 -0
- package/dist/browser/types.js.map +1 -0
- package/dist/cli/commands.d.ts +71 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +794 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +19 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +199 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/index.d.ts +71 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +314 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/change-detection.d.ts +78 -0
- package/dist/core/change-detection.d.ts.map +1 -0
- package/dist/core/change-detection.js +370 -0
- package/dist/core/change-detection.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +22 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/isomorphic-snapshot.d.ts +58 -0
- package/dist/core/isomorphic-snapshot.d.ts.map +1 -0
- package/dist/core/isomorphic-snapshot.js +204 -0
- package/dist/core/isomorphic-snapshot.js.map +1 -0
- package/dist/core/move-detection.d.ts +72 -0
- package/dist/core/move-detection.d.ts.map +1 -0
- package/dist/core/move-detection.js +200 -0
- package/dist/core/move-detection.js.map +1 -0
- package/dist/core/snapshot.d.ts +109 -0
- package/dist/core/snapshot.d.ts.map +1 -0
- package/dist/core/snapshot.js +263 -0
- package/dist/core/snapshot.js.map +1 -0
- package/dist/core/sync-engine.d.ts +110 -0
- package/dist/core/sync-engine.d.ts.map +1 -0
- package/dist/core/sync-engine.js +817 -0
- package/dist/core/sync-engine.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/platform/browser-filesystem.d.ts +26 -0
- package/dist/platform/browser-filesystem.d.ts.map +1 -0
- package/dist/platform/browser-filesystem.js +91 -0
- package/dist/platform/browser-filesystem.js.map +1 -0
- package/dist/platform/filesystem.d.ts +29 -0
- package/dist/platform/filesystem.d.ts.map +1 -0
- package/dist/platform/filesystem.js +65 -0
- package/dist/platform/filesystem.js.map +1 -0
- package/dist/platform/node-filesystem.d.ts +21 -0
- package/dist/platform/node-filesystem.d.ts.map +1 -0
- package/dist/platform/node-filesystem.js +93 -0
- package/dist/platform/node-filesystem.js.map +1 -0
- package/dist/types/config.d.ts +119 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +3 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/documents.d.ts +70 -0
- package/dist/types/documents.d.ts.map +1 -0
- package/dist/types/documents.js +23 -0
- package/dist/types/documents.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/snapshot.d.ts +81 -0
- package/dist/types/snapshot.d.ts.map +1 -0
- package/dist/types/snapshot.js +17 -0
- package/dist/types/snapshot.js.map +1 -0
- package/dist/utils/content-similarity.d.ts +53 -0
- package/dist/utils/content-similarity.d.ts.map +1 -0
- package/dist/utils/content-similarity.js +155 -0
- package/dist/utils/content-similarity.js.map +1 -0
- package/dist/utils/content.d.ts +5 -0
- package/dist/utils/content.d.ts.map +1 -0
- package/dist/utils/content.js +30 -0
- package/dist/utils/content.js.map +1 -0
- package/dist/utils/fs-browser.d.ts +57 -0
- package/dist/utils/fs-browser.d.ts.map +1 -0
- package/dist/utils/fs-browser.js +311 -0
- package/dist/utils/fs-browser.js.map +1 -0
- package/dist/utils/fs-node.d.ts +53 -0
- package/dist/utils/fs-node.d.ts.map +1 -0
- package/dist/utils/fs-node.js +220 -0
- package/dist/utils/fs-node.js.map +1 -0
- package/dist/utils/fs.d.ts +62 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +293 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +23 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/isomorphic.d.ts +29 -0
- package/dist/utils/isomorphic.d.ts.map +1 -0
- package/dist/utils/isomorphic.js +139 -0
- package/dist/utils/isomorphic.js.map +1 -0
- package/dist/utils/mime-types.d.ts +13 -0
- package/dist/utils/mime-types.d.ts.map +1 -0
- package/dist/utils/mime-types.js +240 -0
- package/dist/utils/mime-types.js.map +1 -0
- package/dist/utils/network-sync.d.ts +12 -0
- package/dist/utils/network-sync.d.ts.map +1 -0
- package/dist/utils/network-sync.js +149 -0
- package/dist/utils/network-sync.js.map +1 -0
- package/dist/utils/pure.d.ts +25 -0
- package/dist/utils/pure.d.ts.map +1 -0
- package/dist/utils/pure.js +112 -0
- package/dist/utils/pure.js.map +1 -0
- package/dist/utils/repo-factory.d.ts +11 -0
- package/dist/utils/repo-factory.d.ts.map +1 -0
- package/dist/utils/repo-factory.js +77 -0
- package/dist/utils/repo-factory.js.map +1 -0
- package/package.json +83 -0
- package/src/cli/commands.ts +1053 -0
- package/src/cli/index.ts +2 -0
- package/src/cli.ts +287 -0
- package/src/config/index.ts +334 -0
- package/src/core/change-detection.ts +484 -0
- package/src/core/index.ts +5 -0
- package/src/core/move-detection.ts +269 -0
- package/src/core/snapshot.ts +285 -0
- package/src/core/sync-engine.ts +1167 -0
- package/src/index.ts +14 -0
- package/src/types/config.ts +130 -0
- package/src/types/documents.ts +72 -0
- package/src/types/index.ts +8 -0
- package/src/types/snapshot.ts +88 -0
- package/src/utils/content-similarity.ts +194 -0
- package/src/utils/content.ts +28 -0
- package/src/utils/fs.ts +289 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/mime-types.ts +236 -0
- package/src/utils/network-sync.ts +153 -0
- package/src/utils/repo-factory.ts +58 -0
- package/test/README-TESTING-GAPS.md +174 -0
- package/test/integration/README.md +328 -0
- package/test/integration/clone-test.sh +310 -0
- package/test/integration/conflict-resolution-test.sh +309 -0
- package/test/integration/deletion-behavior-test.sh +487 -0
- package/test/integration/deletion-sync-test-simple.sh +193 -0
- package/test/integration/deletion-sync-test.sh +297 -0
- package/test/integration/exclude-patterns.test.ts +152 -0
- package/test/integration/full-integration-test.sh +363 -0
- package/test/integration/sync-deletion.test.ts +339 -0
- package/test/integration/sync-flow.test.ts +309 -0
- package/test/run-tests.sh +225 -0
- package/test/unit/content-similarity.test.ts +236 -0
- package/test/unit/deletion-behavior.test.ts +260 -0
- package/test/unit/enhanced-mime-detection.test.ts +266 -0
- package/test/unit/snapshot.test.ts +431 -0
- package/test/unit/sync-timing.test.ts +178 -0
- package/test/unit/utils.test.ts +368 -0
- package/tools/browser-sync/README.md +116 -0
- package/tools/browser-sync/package.json +44 -0
- package/tools/browser-sync/patchwork.json +1 -0
- package/tools/browser-sync/pnpm-lock.yaml +4202 -0
- package/tools/browser-sync/src/components/BrowserSyncTool.tsx +599 -0
- package/tools/browser-sync/src/index.ts +20 -0
- package/tools/browser-sync/src/polyfills.ts +31 -0
- package/tools/browser-sync/src/styles.css +290 -0
- package/tools/browser-sync/src/types.ts +27 -0
- package/tools/browser-sync/vite.config.ts +25 -0
- 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 "$@"
|