pushwork 1.0.4 → 1.0.7
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 +87 -328
- package/dist/.pushwork/automerge/3P/Dm3ekE2pmjGnWvDaG3vSR7ww98/snapshot/aa2349c94955ea561f698720142f9d884a6872d9f82dc332d578c216beb0df0e +0 -0
- package/dist/.pushwork/automerge/st/orage-adapter-id +1 -0
- package/dist/.pushwork/config.json +15 -0
- package/dist/.pushwork/snapshot.json +7 -0
- package/dist/cli.js +231 -170
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +51 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +799 -0
- package/dist/commands.js.map +1 -0
- package/dist/core/change-detection.d.ts +6 -19
- package/dist/core/change-detection.d.ts.map +1 -1
- package/dist/core/change-detection.js +101 -80
- package/dist/core/change-detection.js.map +1 -1
- package/dist/{config/index.d.ts → core/config.d.ts} +13 -3
- package/dist/core/config.d.ts.map +1 -0
- package/dist/{config/index.js → core/config.js} +55 -73
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/move-detection.d.ts +12 -50
- package/dist/core/move-detection.d.ts.map +1 -1
- package/dist/core/move-detection.js +58 -139
- package/dist/core/move-detection.js.map +1 -1
- package/dist/core/snapshot.d.ts +0 -4
- package/dist/core/snapshot.d.ts.map +1 -1
- package/dist/core/snapshot.js +2 -11
- package/dist/core/snapshot.js.map +1 -1
- package/dist/core/sync-engine.d.ts +5 -11
- package/dist/core/sync-engine.d.ts.map +1 -1
- package/dist/core/sync-engine.js +220 -362
- package/dist/core/sync-engine.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -6
- package/dist/index.js.map +1 -1
- package/dist/types/config.d.ts +43 -67
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/documents.d.ts +15 -3
- package/dist/types/documents.d.ts.map +1 -1
- package/dist/types/documents.js.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/snapshot.d.ts +3 -21
- package/dist/types/snapshot.d.ts.map +1 -1
- package/dist/types/snapshot.js +0 -14
- package/dist/types/snapshot.js.map +1 -1
- package/dist/utils/content.d.ts.map +1 -1
- package/dist/utils/content.js +2 -6
- package/dist/utils/content.js.map +1 -1
- package/dist/utils/directory.d.ts +10 -0
- package/dist/utils/directory.d.ts.map +1 -0
- package/dist/utils/directory.js +37 -0
- package/dist/utils/directory.js.map +1 -0
- package/dist/utils/fs.d.ts +15 -2
- package/dist/utils/fs.d.ts.map +1 -1
- package/dist/utils/fs.js +63 -53
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/mime-types.d.ts.map +1 -1
- package/dist/utils/mime-types.js +11 -4
- package/dist/utils/mime-types.js.map +1 -1
- package/dist/utils/network-sync.d.ts +0 -6
- package/dist/utils/network-sync.d.ts.map +1 -1
- package/dist/utils/network-sync.js +55 -99
- package/dist/utils/network-sync.js.map +1 -1
- package/dist/utils/output.d.ts +129 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +375 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/repo-factory.d.ts +2 -6
- package/dist/utils/repo-factory.d.ts.map +1 -1
- package/dist/utils/repo-factory.js +8 -22
- package/dist/utils/repo-factory.js.map +1 -1
- package/dist/utils/string-similarity.d.ts +14 -0
- package/dist/utils/string-similarity.d.ts.map +1 -0
- package/dist/utils/string-similarity.js +43 -0
- package/dist/utils/string-similarity.js.map +1 -0
- package/dist/utils/trace.d.ts +19 -0
- package/dist/utils/trace.d.ts.map +1 -0
- package/dist/utils/trace.js +68 -0
- package/dist/utils/trace.js.map +1 -0
- package/package.json +17 -12
- package/src/cli.ts +326 -252
- package/src/commands.ts +988 -0
- package/src/core/change-detection.ts +199 -162
- package/src/{config/index.ts → core/config.ts} +65 -82
- package/src/core/index.ts +1 -1
- package/src/core/move-detection.ts +74 -180
- package/src/core/snapshot.ts +2 -12
- package/src/core/sync-engine.ts +248 -499
- package/src/index.ts +0 -10
- package/src/types/config.ts +50 -72
- package/src/types/documents.ts +16 -3
- package/src/types/index.ts +0 -5
- package/src/types/snapshot.ts +1 -23
- package/src/utils/content.ts +2 -6
- package/src/utils/directory.ts +50 -0
- package/src/utils/fs.ts +67 -56
- package/src/utils/index.ts +1 -6
- package/src/utils/mime-types.ts +12 -4
- package/src/utils/network-sync.ts +79 -137
- package/src/utils/output.ts +450 -0
- package/src/utils/repo-factory.ts +13 -31
- package/src/utils/string-similarity.ts +54 -0
- package/src/utils/trace.ts +70 -0
- package/test/integration/exclude-patterns.test.ts +6 -15
- package/test/integration/fuzzer.test.ts +308 -391
- package/test/integration/init-sync.test.ts +89 -0
- package/test/integration/sync-deletion.test.ts +2 -61
- package/test/integration/sync-flow.test.ts +4 -24
- package/test/jest.setup.ts +34 -0
- package/test/unit/deletion-behavior.test.ts +3 -14
- package/test/unit/enhanced-mime-detection.test.ts +0 -22
- package/test/unit/snapshot.test.ts +2 -29
- package/test/unit/sync-convergence.test.ts +3 -198
- package/test/unit/sync-timing.test.ts +0 -44
- package/test/unit/utils.test.ts +0 -2
- package/tsconfig.json +3 -3
- package/dist/browser/browser-sync-engine.d.ts +0 -64
- package/dist/browser/browser-sync-engine.d.ts.map +0 -1
- package/dist/browser/browser-sync-engine.js +0 -303
- package/dist/browser/browser-sync-engine.js.map +0 -1
- package/dist/browser/filesystem-adapter.d.ts +0 -84
- package/dist/browser/filesystem-adapter.d.ts.map +0 -1
- package/dist/browser/filesystem-adapter.js +0 -413
- package/dist/browser/filesystem-adapter.js.map +0 -1
- package/dist/browser/index.d.ts +0 -36
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/browser/index.js +0 -90
- package/dist/browser/index.js.map +0 -1
- package/dist/browser/types.d.ts +0 -70
- package/dist/browser/types.d.ts.map +0 -1
- package/dist/browser/types.js +0 -6
- package/dist/browser/types.js.map +0 -1
- package/dist/cli/commands.d.ts +0 -77
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -904
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -19
- package/dist/cli/index.js.map +0 -1
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js.map +0 -1
- package/dist/core/isomorphic-snapshot.d.ts +0 -58
- package/dist/core/isomorphic-snapshot.d.ts.map +0 -1
- package/dist/core/isomorphic-snapshot.js +0 -204
- package/dist/core/isomorphic-snapshot.js.map +0 -1
- package/dist/platform/browser-filesystem.d.ts +0 -26
- package/dist/platform/browser-filesystem.d.ts.map +0 -1
- package/dist/platform/browser-filesystem.js +0 -91
- package/dist/platform/browser-filesystem.js.map +0 -1
- package/dist/platform/filesystem.d.ts +0 -29
- package/dist/platform/filesystem.d.ts.map +0 -1
- package/dist/platform/filesystem.js +0 -65
- package/dist/platform/filesystem.js.map +0 -1
- package/dist/platform/node-filesystem.d.ts +0 -21
- package/dist/platform/node-filesystem.d.ts.map +0 -1
- package/dist/platform/node-filesystem.js +0 -93
- package/dist/platform/node-filesystem.js.map +0 -1
- package/dist/utils/content-similarity.d.ts +0 -53
- package/dist/utils/content-similarity.d.ts.map +0 -1
- package/dist/utils/content-similarity.js +0 -155
- package/dist/utils/content-similarity.js.map +0 -1
- package/dist/utils/fs-browser.d.ts +0 -57
- package/dist/utils/fs-browser.d.ts.map +0 -1
- package/dist/utils/fs-browser.js +0 -311
- package/dist/utils/fs-browser.js.map +0 -1
- package/dist/utils/fs-node.d.ts +0 -53
- package/dist/utils/fs-node.d.ts.map +0 -1
- package/dist/utils/fs-node.js +0 -220
- package/dist/utils/fs-node.js.map +0 -1
- package/dist/utils/isomorphic.d.ts +0 -29
- package/dist/utils/isomorphic.d.ts.map +0 -1
- package/dist/utils/isomorphic.js +0 -139
- package/dist/utils/isomorphic.js.map +0 -1
- package/dist/utils/pure.d.ts +0 -25
- package/dist/utils/pure.d.ts.map +0 -1
- package/dist/utils/pure.js +0 -112
- package/dist/utils/pure.js.map +0 -1
- package/src/cli/commands.ts +0 -1207
- package/src/cli/index.ts +0 -2
- package/src/utils/content-similarity.ts +0 -194
- package/test/README-TESTING-GAPS.md +0 -174
- package/test/unit/content-similarity.test.ts +0 -236
|
@@ -5,13 +5,10 @@ import { SnapshotManager } from "../../src/core/snapshot";
|
|
|
5
5
|
import { ChangeDetector } from "../../src/core/change-detection";
|
|
6
6
|
import { MoveDetector } from "../../src/core/move-detection";
|
|
7
7
|
import { writeFileContent, removePath, pathExists } from "../../src/utils";
|
|
8
|
-
import { SyncSnapshot, ChangeType, FileType } from "../../src/types";
|
|
9
8
|
|
|
10
9
|
describe("Sync Convergence Issues", () => {
|
|
11
10
|
let testDir: string;
|
|
12
11
|
let snapshotManager: SnapshotManager;
|
|
13
|
-
let changeDetector: ChangeDetector;
|
|
14
|
-
let moveDetector: MoveDetector;
|
|
15
12
|
|
|
16
13
|
beforeEach(async () => {
|
|
17
14
|
testDir = await fs.mkdtemp(path.join(tmpdir(), "sync-convergence-test-"));
|
|
@@ -19,8 +16,8 @@ describe("Sync Convergence Issues", () => {
|
|
|
19
16
|
|
|
20
17
|
// Create mock repo for change detector - we'll focus on change detection logic
|
|
21
18
|
const mockRepo = {} as any;
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
new ChangeDetector(mockRepo, testDir, []);
|
|
20
|
+
new MoveDetector();
|
|
24
21
|
});
|
|
25
22
|
|
|
26
23
|
afterEach(async () => {
|
|
@@ -29,12 +26,7 @@ describe("Sync Convergence Issues", () => {
|
|
|
29
26
|
|
|
30
27
|
describe("Change Detection Patterns", () => {
|
|
31
28
|
it("should verify that convergence issues are fixed", async () => {
|
|
32
|
-
console.log(
|
|
33
|
-
"\n🧪 Testing That Convergence Issues Are Fixed With Proper Head Tracking"
|
|
34
|
-
);
|
|
35
|
-
|
|
36
29
|
// === SETUP PHASE ===
|
|
37
|
-
console.log("\n--- Setup Phase ---");
|
|
38
30
|
|
|
39
31
|
// Create initial file structure similar to Vite build output
|
|
40
32
|
const initialFiles = [
|
|
@@ -56,7 +48,6 @@ describe("Sync Convergence Issues", () => {
|
|
|
56
48
|
const filePath = path.join(testDir, file.name);
|
|
57
49
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
58
50
|
await writeFileContent(filePath, file.content);
|
|
59
|
-
console.log(`📄 Created: ${file.name}`);
|
|
60
51
|
}
|
|
61
52
|
|
|
62
53
|
// Create initial snapshot representing the "synced" state
|
|
@@ -73,33 +64,22 @@ describe("Sync Convergence Issues", () => {
|
|
|
73
64
|
});
|
|
74
65
|
}
|
|
75
66
|
|
|
76
|
-
console.log(
|
|
77
|
-
`📸 Initial snapshot has ${snapshot.files.size} files tracked`
|
|
78
|
-
);
|
|
79
|
-
|
|
80
67
|
// === SIMULATE BUILD PROCESS ===
|
|
81
|
-
console.log("\n--- Simulating Build Process (like pnpm build) ---");
|
|
82
68
|
|
|
83
69
|
// Delete old file and create new one (simulating Vite's content-based naming)
|
|
84
70
|
await removePath(path.join(testDir, "assets/tool-DhQI94EZ.js"));
|
|
85
|
-
console.log(`🗑️ Deleted: assets/tool-DhQI94EZ.js`);
|
|
86
71
|
|
|
87
72
|
const newToolFile = "assets/tool-CR5n6i_K.js";
|
|
88
73
|
await writeFileContent(
|
|
89
74
|
path.join(testDir, newToolFile),
|
|
90
75
|
"// New tool bundle with different hash\nexport const tool = 'v2';"
|
|
91
76
|
);
|
|
92
|
-
console.log(`➕ Created: ${newToolFile}`);
|
|
93
77
|
|
|
94
78
|
// Update the main file to reference the new bundle
|
|
95
79
|
await writeFileContent(
|
|
96
80
|
path.join(testDir, "index.js"),
|
|
97
81
|
"// Main entry\nimport './assets/tool-CR5n6i_K.js';"
|
|
98
82
|
);
|
|
99
|
-
console.log(`📝 Modified: index.js`);
|
|
100
|
-
|
|
101
|
-
// === CHANGE DETECTION ANALYSIS ===
|
|
102
|
-
console.log("\n--- Change Detection Analysis ---");
|
|
103
83
|
|
|
104
84
|
// This is where we would normally detect changes, but we'll simulate the issue
|
|
105
85
|
// by showing what the change detector would find vs what should happen
|
|
@@ -107,83 +87,28 @@ describe("Sync Convergence Issues", () => {
|
|
|
107
87
|
// Simulate what change detection finds
|
|
108
88
|
const deletedFile = "assets/tool-DhQI94EZ.js";
|
|
109
89
|
const createdFile = "assets/tool-CR5n6i_K.js";
|
|
110
|
-
const modifiedFile = "index.js";
|
|
111
|
-
|
|
112
|
-
console.log(`🔍 Change detection would find:`);
|
|
113
|
-
console.log(` - Deleted: ${deletedFile}`);
|
|
114
|
-
console.log(` - Created: ${createdFile}`);
|
|
115
|
-
console.log(` - Modified: ${modifiedFile}`);
|
|
116
|
-
|
|
117
|
-
// === MOVE DETECTION ANALYSIS ===
|
|
118
|
-
console.log("\n--- Move Detection Analysis ---");
|
|
119
|
-
|
|
120
|
-
// Simulate move detection
|
|
121
|
-
const deletedContent =
|
|
122
|
-
"// Initial tool bundle\nexport const tool = 'v1';";
|
|
123
|
-
const createdContent = await fs.readFile(
|
|
124
|
-
path.join(testDir, createdFile),
|
|
125
|
-
"utf8"
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
// The similarity would be low due to different content/hash
|
|
129
|
-
const mockSimilarity = 0.3; // Low similarity - below auto-apply threshold
|
|
130
|
-
console.log(
|
|
131
|
-
`🔍 Move detection similarity: ${(mockSimilarity * 100).toFixed(1)}%`
|
|
132
|
-
);
|
|
133
|
-
console.log(
|
|
134
|
-
`📊 Below auto-apply threshold (80%) - will be treated as separate delete+create`
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
// === SIMULATE THE CONVERGENCE ISSUE ===
|
|
138
|
-
console.log("\n--- Simulating Convergence Issue ---");
|
|
139
|
-
|
|
140
|
-
// The issue: In a real sync scenario, the deletion might not be properly
|
|
141
|
-
// processed due to stale directory heads, causing repeated attempts
|
|
142
90
|
|
|
143
91
|
// Simulate multiple "sync runs" by checking filesystem state
|
|
144
92
|
let syncRun = 1;
|
|
145
93
|
let changesRemaining = true;
|
|
146
94
|
|
|
147
95
|
while (changesRemaining && syncRun <= 3) {
|
|
148
|
-
console.log(`\n--- Sync Run ${syncRun} ---`);
|
|
149
|
-
|
|
150
96
|
// Check what should be synced
|
|
151
97
|
const fileExists = await pathExists(path.join(testDir, deletedFile));
|
|
152
98
|
const isTrackedInSnapshot = snapshot.files.has(deletedFile);
|
|
153
99
|
|
|
154
|
-
console.log(`📁 ${deletedFile} exists on filesystem: ${fileExists}`);
|
|
155
|
-
console.log(
|
|
156
|
-
`📸 ${deletedFile} tracked in snapshot: ${isTrackedInSnapshot}`
|
|
157
|
-
);
|
|
158
|
-
|
|
159
100
|
if (!fileExists && isTrackedInSnapshot) {
|
|
160
|
-
console.log(
|
|
161
|
-
`🔄 Should delete ${deletedFile} from remote and snapshot`
|
|
162
|
-
);
|
|
163
|
-
|
|
164
101
|
// In a real scenario with the bug, this deletion might not complete properly
|
|
165
102
|
// due to stale directory heads, causing it to remain in the directory document
|
|
166
103
|
|
|
167
104
|
// Simulate partial success - remove from snapshot but directory doc might still reference it
|
|
168
105
|
snapshotManager.removeFileEntry(snapshot, deletedFile);
|
|
169
|
-
console.log(`📸 Removed ${deletedFile} from snapshot`);
|
|
170
|
-
|
|
171
|
-
// The bug: directory document might still contain the file reference
|
|
172
|
-
// because the removal operation used stale heads
|
|
173
|
-
console.log(
|
|
174
|
-
`🐛 SIMULATED BUG: Directory document might still reference ${deletedFile}`
|
|
175
|
-
);
|
|
176
|
-
console.log(
|
|
177
|
-
` This happens when directory removal uses stale heads`
|
|
178
|
-
);
|
|
179
106
|
}
|
|
180
107
|
|
|
181
108
|
const newFileExists = await pathExists(path.join(testDir, createdFile));
|
|
182
109
|
const newFileTracked = snapshot.files.has(createdFile);
|
|
183
110
|
|
|
184
111
|
if (newFileExists && !newFileTracked) {
|
|
185
|
-
console.log(`🔄 Should add ${createdFile} to remote and snapshot`);
|
|
186
|
-
|
|
187
112
|
// Add new file to snapshot
|
|
188
113
|
snapshotManager.updateFileEntry(snapshot, createdFile, {
|
|
189
114
|
path: path.join(testDir, createdFile),
|
|
@@ -192,46 +117,21 @@ describe("Sync Convergence Issues", () => {
|
|
|
192
117
|
extension: "js",
|
|
193
118
|
mimeType: "text/javascript",
|
|
194
119
|
});
|
|
195
|
-
console.log(`📸 Added ${createdFile} to snapshot`);
|
|
196
120
|
}
|
|
197
121
|
|
|
198
122
|
// Check if we still have work to do
|
|
199
123
|
// With the fix: Directory heads are properly updated, so convergence happens in 1 run
|
|
200
124
|
if (syncRun === 1) {
|
|
201
|
-
console.log(
|
|
202
|
-
`✅ FIXED: Directory heads properly updated - converged in 1 run!`
|
|
203
|
-
);
|
|
204
|
-
console.log(
|
|
205
|
-
` No stale directory references remain after proper head tracking`
|
|
206
|
-
);
|
|
207
125
|
changesRemaining = false; // Fixed behavior: converge immediately
|
|
208
126
|
} else {
|
|
209
127
|
// This shouldn't happen with the fix
|
|
210
|
-
console.log(
|
|
211
|
-
`🚨 UNEXPECTED: Required multiple runs - fix may not be working`
|
|
212
|
-
);
|
|
213
128
|
changesRemaining = false;
|
|
214
129
|
}
|
|
215
130
|
|
|
216
131
|
syncRun++;
|
|
217
132
|
}
|
|
218
133
|
|
|
219
|
-
|
|
220
|
-
console.log("\n--- Test Assertions ---");
|
|
221
|
-
|
|
222
|
-
// This test demonstrates the expected behavior vs buggy behavior
|
|
223
|
-
console.log(`📊 Simulated sync runs needed: ${syncRun - 1}`);
|
|
224
|
-
|
|
225
|
-
if (syncRun - 1 > 1) {
|
|
226
|
-
console.log("🚨 CONVERGENCE ISSUE STILL EXISTS:");
|
|
227
|
-
console.log(` Required ${syncRun - 1} sync runs to converge`);
|
|
228
|
-
console.log(" The fix may not be working properly");
|
|
229
|
-
console.log(" Expected: Should ALWAYS converge in exactly 1 run");
|
|
230
|
-
} else {
|
|
231
|
-
console.log("✅ CONVERGENCE SUCCESS:");
|
|
232
|
-
console.log(" Converged in exactly 1 run as expected");
|
|
233
|
-
console.log(" Directory head tracking fix is working!");
|
|
234
|
-
}
|
|
134
|
+
expect(syncRun - 1).toBe(1);
|
|
235
135
|
|
|
236
136
|
// Verify final filesystem state is correct regardless of sync issues
|
|
237
137
|
expect(
|
|
@@ -246,18 +146,10 @@ describe("Sync Convergence Issues", () => {
|
|
|
246
146
|
expect(snapshot.files.has("assets/tool-DhQI94EZ.js")).toBe(false);
|
|
247
147
|
expect(snapshot.files.has("assets/tool-CR5n6i_K.js")).toBe(true);
|
|
248
148
|
|
|
249
|
-
console.log("✅ Filesystem and snapshot state are correct");
|
|
250
|
-
console.log(
|
|
251
|
-
"✅ Directory document head tracking fix has resolved the convergence issue"
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
// Test assertion: Verify the fix works - should be exactly 1 run
|
|
255
149
|
expect(syncRun - 1).toBe(1); // Fixed behavior: exactly 1 run
|
|
256
150
|
});
|
|
257
151
|
|
|
258
152
|
it("should demonstrate snapshot head tracking concepts", async () => {
|
|
259
|
-
console.log("\n🧪 Testing Snapshot Head Tracking Concepts");
|
|
260
|
-
|
|
261
153
|
// Create a simple file structure
|
|
262
154
|
await fs.mkdir(path.join(testDir, "subdir"), { recursive: true });
|
|
263
155
|
await writeFileContent(
|
|
@@ -285,16 +177,10 @@ describe("Sync Convergence Issues", () => {
|
|
|
285
177
|
mimeType: "text/javascript",
|
|
286
178
|
});
|
|
287
179
|
|
|
288
|
-
console.log(`📸 Initial snapshot state:`);
|
|
289
|
-
console.log(` - Files: ${snapshot.files.size}`);
|
|
290
|
-
console.log(` - Directories: ${snapshot.directories.size}`);
|
|
291
|
-
|
|
292
180
|
// === SIMULATE THE HEAD TRACKING ISSUE ===
|
|
293
|
-
console.log("\n--- Simulating Head Tracking Issue ---");
|
|
294
181
|
|
|
295
182
|
// Delete the file locally
|
|
296
183
|
await removePath(path.join(testDir, "subdir/test.js"));
|
|
297
|
-
console.log("🗑️ Deleted file locally");
|
|
298
184
|
|
|
299
185
|
// In a real sync scenario, we would:
|
|
300
186
|
// 1. Detect the file deletion
|
|
@@ -310,24 +196,9 @@ describe("Sync Convergence Issues", () => {
|
|
|
310
196
|
const directoryEntry = snapshot.directories.get("subdir");
|
|
311
197
|
if (directoryEntry) {
|
|
312
198
|
// In real sync, heads would advance: ["initial-head"] -> ["new-head-after-deletion"]
|
|
313
|
-
const oldHeads = directoryEntry.head;
|
|
314
199
|
const newHeads = ["new-head-after-deletion"];
|
|
315
200
|
|
|
316
|
-
console.log(`📊 Directory heads should advance:`);
|
|
317
|
-
console.log(` - Old heads: ${JSON.stringify(oldHeads)}`);
|
|
318
|
-
console.log(` - New heads: ${JSON.stringify(newHeads)}`);
|
|
319
|
-
|
|
320
|
-
// THE BUG: This update might not happen, leaving stale heads in snapshot
|
|
321
|
-
// For demonstration, we'll show both scenarios
|
|
322
|
-
|
|
323
|
-
console.log("\n🐛 BUGGY SCENARIO: Heads not updated in snapshot");
|
|
324
|
-
console.log(" Next directory operation would use stale heads");
|
|
325
|
-
console.log(" This causes the operation to fail or be ineffective");
|
|
326
|
-
|
|
327
|
-
console.log("\n✅ CORRECT SCENARIO: Heads updated in snapshot");
|
|
328
201
|
directoryEntry.head = newHeads as any;
|
|
329
|
-
console.log(" Next directory operation uses current heads");
|
|
330
|
-
console.log(" Operations succeed and converge properly");
|
|
331
202
|
}
|
|
332
203
|
|
|
333
204
|
// Verify the concept
|
|
@@ -336,24 +207,13 @@ describe("Sync Convergence Issues", () => {
|
|
|
336
207
|
);
|
|
337
208
|
const fileStillTracked = snapshot.files.has("subdir/test.js");
|
|
338
209
|
|
|
339
|
-
console.log(`\n📊 Final state:`);
|
|
340
|
-
console.log(` - File exists on disk: ${fileStillExists}`);
|
|
341
|
-
console.log(` - File tracked in snapshot: ${fileStillTracked}`);
|
|
342
|
-
|
|
343
210
|
expect(fileStillExists).toBe(false);
|
|
344
211
|
expect(fileStillTracked).toBe(false);
|
|
345
|
-
|
|
346
|
-
console.log("✅ This demonstrates the head tracking concept");
|
|
347
|
-
console.log(
|
|
348
|
-
"🐛 The real bug occurs when directory document heads aren't updated"
|
|
349
|
-
);
|
|
350
212
|
});
|
|
351
213
|
});
|
|
352
214
|
|
|
353
215
|
describe("Move Detection Interaction", () => {
|
|
354
216
|
it("should show how move detection affects convergence behavior", async () => {
|
|
355
|
-
console.log("\n🧪 Testing Move Detection Impact on Convergence");
|
|
356
|
-
|
|
357
217
|
// Create initial file
|
|
358
218
|
await writeFileContent(
|
|
359
219
|
path.join(testDir, "original.js"),
|
|
@@ -370,8 +230,6 @@ describe("Sync Convergence Issues", () => {
|
|
|
370
230
|
mimeType: "text/javascript",
|
|
371
231
|
});
|
|
372
232
|
|
|
373
|
-
console.log("📄 Created and tracked original.js");
|
|
374
|
-
|
|
375
233
|
// === SIMULATE RENAME WITH LOW SIMILARITY ===
|
|
376
234
|
|
|
377
235
|
// Delete original and create "renamed" file with different content (low similarity)
|
|
@@ -380,26 +238,6 @@ describe("Sync Convergence Issues", () => {
|
|
|
380
238
|
path.join(testDir, "renamed.js"),
|
|
381
239
|
"// Completely different content\nconst newFeature = () => { return 'different'; };"
|
|
382
240
|
);
|
|
383
|
-
console.log("🔄 Simulated rename with low content similarity");
|
|
384
|
-
|
|
385
|
-
// Simulate move detection
|
|
386
|
-
const originalContent = "console.log('original');";
|
|
387
|
-
const newContent = await fs.readFile(
|
|
388
|
-
path.join(testDir, "renamed.js"),
|
|
389
|
-
"utf8"
|
|
390
|
-
);
|
|
391
|
-
|
|
392
|
-
// Calculate rough similarity (would use ContentSimilarity in real code)
|
|
393
|
-
const similarity = 0.2; // Very low similarity due to completely different content
|
|
394
|
-
|
|
395
|
-
console.log(`🔍 Move detection analysis:`);
|
|
396
|
-
console.log(` - Similarity: ${(similarity * 100).toFixed(1)}%`);
|
|
397
|
-
console.log(` - Below auto-apply threshold (80%)`);
|
|
398
|
-
console.log(` - Below prompt threshold (50%)`);
|
|
399
|
-
console.log(` - Will be treated as separate delete + create operations`);
|
|
400
|
-
|
|
401
|
-
// === SIMULATE CONVERGENCE BEHAVIOR ===
|
|
402
|
-
console.log("\n--- Simulating Convergence Behavior ---");
|
|
403
241
|
|
|
404
242
|
// Since move detection doesn't apply, we process as delete + create
|
|
405
243
|
// This should ALWAYS converge in exactly 1 sync run, but the bug causes more
|
|
@@ -409,7 +247,6 @@ describe("Sync Convergence Issues", () => {
|
|
|
409
247
|
|
|
410
248
|
while (hasChanges && convergenceRuns < 3) {
|
|
411
249
|
convergenceRuns++;
|
|
412
|
-
console.log(`\n--- Convergence Run ${convergenceRuns} ---`);
|
|
413
250
|
|
|
414
251
|
// Check for deletion
|
|
415
252
|
const originalExists = await pathExists(
|
|
@@ -418,19 +255,7 @@ describe("Sync Convergence Issues", () => {
|
|
|
418
255
|
const originalTracked = snapshot.files.has("original.js");
|
|
419
256
|
|
|
420
257
|
if (!originalExists && originalTracked) {
|
|
421
|
-
console.log("🔄 Processing deletion: original.js");
|
|
422
258
|
snapshotManager.removeFileEntry(snapshot, "original.js");
|
|
423
|
-
|
|
424
|
-
// THE BUG: In real sync, directory document might still reference the file
|
|
425
|
-
// due to stale heads, causing it to be re-discovered in next run
|
|
426
|
-
if (convergenceRuns === 1) {
|
|
427
|
-
console.log(
|
|
428
|
-
"🐛 SIMULATED BUG: Directory document still has stale reference"
|
|
429
|
-
);
|
|
430
|
-
console.log(
|
|
431
|
-
" File will be 're-discovered' in directory traversal"
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
259
|
}
|
|
435
260
|
|
|
436
261
|
// Check for addition
|
|
@@ -438,7 +263,6 @@ describe("Sync Convergence Issues", () => {
|
|
|
438
263
|
const newTracked = snapshot.files.has("renamed.js");
|
|
439
264
|
|
|
440
265
|
if (newExists && !newTracked) {
|
|
441
|
-
console.log("🔄 Processing addition: renamed.js");
|
|
442
266
|
snapshotManager.updateFileEntry(snapshot, "renamed.js", {
|
|
443
267
|
path: path.join(testDir, "renamed.js"),
|
|
444
268
|
url: "automerge:renamed" as any,
|
|
@@ -452,7 +276,6 @@ describe("Sync Convergence Issues", () => {
|
|
|
452
276
|
// With the fix: Directory heads are properly updated, so convergence happens in 1 run
|
|
453
277
|
if (convergenceRuns === 1) {
|
|
454
278
|
hasChanges = false; // Fixed: converge immediately
|
|
455
|
-
console.log("✅ FIXED: Converged in 1 run with proper head tracking");
|
|
456
279
|
} else {
|
|
457
280
|
// This shouldn't happen with the fix
|
|
458
281
|
hasChanges = false;
|
|
@@ -462,30 +285,12 @@ describe("Sync Convergence Issues", () => {
|
|
|
462
285
|
}
|
|
463
286
|
}
|
|
464
287
|
|
|
465
|
-
console.log(`\n📊 Convergence Analysis:`);
|
|
466
|
-
console.log(` - Runs needed: ${convergenceRuns}`);
|
|
467
|
-
console.log(` - Expected: ALWAYS exactly 1 run`);
|
|
468
|
-
console.log(
|
|
469
|
-
` - Actual: ${convergenceRuns} runs (should be 1 with the fix)`
|
|
470
|
-
);
|
|
471
|
-
|
|
472
288
|
// Verify final state
|
|
473
289
|
expect(await pathExists(path.join(testDir, "original.js"))).toBe(false);
|
|
474
290
|
expect(await pathExists(path.join(testDir, "renamed.js"))).toBe(true);
|
|
475
291
|
expect(snapshot.files.has("original.js")).toBe(false);
|
|
476
292
|
expect(snapshot.files.has("renamed.js")).toBe(true);
|
|
477
293
|
|
|
478
|
-
console.log("✅ Final state is correct");
|
|
479
|
-
|
|
480
|
-
// Verify the fix worked
|
|
481
|
-
if (convergenceRuns === 1) {
|
|
482
|
-
console.log("✅ SUCCESS: Converged in exactly 1 run - fix is working!");
|
|
483
|
-
} else {
|
|
484
|
-
console.log(
|
|
485
|
-
"🚨 ISSUE: Still required multiple runs - fix needs investigation"
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
294
|
// Test assertion: Verify convergence in exactly 1 run
|
|
490
295
|
expect(convergenceRuns).toBe(1);
|
|
491
296
|
});
|
|
@@ -28,8 +28,6 @@ describe("Sync Timing Analysis", () => {
|
|
|
28
28
|
await Promise.all(promises);
|
|
29
29
|
const totalTime = Date.now() - startTime;
|
|
30
30
|
|
|
31
|
-
console.log(`Created 10 files in ${totalTime}ms`);
|
|
32
|
-
|
|
33
31
|
// Verify all files exist
|
|
34
32
|
const files = await fs.readdir(testDir);
|
|
35
33
|
expect(files).toHaveLength(10);
|
|
@@ -38,37 +36,6 @@ describe("Sync Timing Analysis", () => {
|
|
|
38
36
|
expect(totalTime).toBeLessThan(1000); // Should be fast for local operations
|
|
39
37
|
});
|
|
40
38
|
|
|
41
|
-
it("should measure sequential vs parallel file operations", async () => {
|
|
42
|
-
// Sequential operations
|
|
43
|
-
const sequentialStart = Date.now();
|
|
44
|
-
for (let i = 0; i < 5; i++) {
|
|
45
|
-
await fs.writeFile(path.join(testDir, `seq${i}.txt`), `content${i}`);
|
|
46
|
-
}
|
|
47
|
-
const sequentialTime = Date.now() - sequentialStart;
|
|
48
|
-
|
|
49
|
-
// Parallel operations
|
|
50
|
-
const parallelStart = Date.now();
|
|
51
|
-
const promises: Promise<void>[] = [];
|
|
52
|
-
for (let i = 0; i < 5; i++) {
|
|
53
|
-
promises.push(
|
|
54
|
-
fs.writeFile(path.join(testDir, `par${i}.txt`), `content${i}`)
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
await Promise.all(promises);
|
|
58
|
-
const parallelTime = Date.now() - parallelStart;
|
|
59
|
-
|
|
60
|
-
console.log(
|
|
61
|
-
`Sequential: ${sequentialTime}ms, Parallel: ${parallelTime}ms`
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
// Parallel should generally be faster
|
|
65
|
-
expect(parallelTime).toBeLessThanOrEqual(sequentialTime);
|
|
66
|
-
|
|
67
|
-
// Verify all files exist
|
|
68
|
-
const files = await fs.readdir(testDir);
|
|
69
|
-
expect(files).toHaveLength(10);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
39
|
it("should test file operation atomicity", async () => {
|
|
73
40
|
const filePath = path.join(testDir, "test.txt");
|
|
74
41
|
|
|
@@ -86,8 +53,6 @@ describe("Sync Timing Analysis", () => {
|
|
|
86
53
|
// Check final content (should be one of the updates)
|
|
87
54
|
const finalContent = await fs.readFile(filePath, "utf8");
|
|
88
55
|
expect(finalContent).toMatch(/updated content \d/);
|
|
89
|
-
|
|
90
|
-
console.log(`Final content after rapid writes: "${finalContent}"`);
|
|
91
56
|
});
|
|
92
57
|
});
|
|
93
58
|
|
|
@@ -111,11 +76,6 @@ describe("Sync Timing Analysis", () => {
|
|
|
111
76
|
const networkTime = Date.now() - networkStart;
|
|
112
77
|
results.push({ operation: "network write", time: networkTime });
|
|
113
78
|
|
|
114
|
-
console.log("Operation timing:");
|
|
115
|
-
results.forEach((r) => {
|
|
116
|
-
console.log(` ${r.operation}: ${r.time}ms`);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
79
|
// This demonstrates why we might need to wait for slower operations
|
|
120
80
|
expect(networkTime).toBeGreaterThan(localTime);
|
|
121
81
|
expect(networkTime).toBeGreaterThan(40); // Should include our delay
|
|
@@ -140,14 +100,12 @@ describe("Sync Timing Analysis", () => {
|
|
|
140
100
|
|
|
141
101
|
// Check immediately (before operations complete)
|
|
142
102
|
const filesImmediate = await fs.readdir(testDir);
|
|
143
|
-
console.log(`Files immediately: ${filesImmediate.length}`);
|
|
144
103
|
|
|
145
104
|
// Now wait for operations to complete
|
|
146
105
|
await Promise.all(promises);
|
|
147
106
|
|
|
148
107
|
// Check after completion
|
|
149
108
|
const filesAfter = await fs.readdir(testDir);
|
|
150
|
-
console.log(`Files after completion: ${filesAfter.length}`);
|
|
151
109
|
|
|
152
110
|
// This shows the difference between checking immediately vs waiting
|
|
153
111
|
expect(filesAfter.length).toBeGreaterThanOrEqual(filesImmediate.length);
|
|
@@ -171,8 +129,6 @@ describe("Sync Timing Analysis", () => {
|
|
|
171
129
|
// Only one operation should "win"
|
|
172
130
|
const content = await fs.readFile(sharedFile, "utf8");
|
|
173
131
|
expect(["operation1", "operation2", "operation3"]).toContain(content);
|
|
174
|
-
|
|
175
|
-
console.log(`Final content from race condition: "${content}"`);
|
|
176
132
|
});
|
|
177
133
|
});
|
|
178
134
|
});
|
package/test/unit/utils.test.ts
CHANGED
package/tsconfig.json
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"declarationMap": true,
|
|
14
14
|
"sourceMap": true,
|
|
15
15
|
"resolveJsonModule": true,
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
16
|
+
"types": ["node", "jest"],
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true
|
|
19
19
|
},
|
|
20
20
|
"include": ["src/**/*"],
|
|
21
21
|
"exclude": ["node_modules", "dist", "test"]
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-compatible sync engine that adapts the core SyncEngine for browser use
|
|
3
|
-
*/
|
|
4
|
-
import { Repo } from "@automerge/automerge-repo";
|
|
5
|
-
import { SyncSnapshot, SyncResult } from "../types";
|
|
6
|
-
import { BrowserFilesystemAdapter } from "./filesystem-adapter";
|
|
7
|
-
import { BrowserDirectoryHandle, BrowserSyncState } from "./types";
|
|
8
|
-
import "./globals";
|
|
9
|
-
/**
|
|
10
|
-
* Browser-compatible repo factory
|
|
11
|
-
*/
|
|
12
|
-
export declare class BrowserRepoFactory {
|
|
13
|
-
static create(syncServerUrl?: string, storageId?: string): Promise<Repo>;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Browser-specific sync engine that adapts filesystem operations
|
|
17
|
-
*/
|
|
18
|
-
export declare class BrowserSyncEngine {
|
|
19
|
-
private repo;
|
|
20
|
-
private filesystem;
|
|
21
|
-
private snapshotManager;
|
|
22
|
-
private coreEngine;
|
|
23
|
-
private rootPath;
|
|
24
|
-
constructor(repo: Repo, filesystem?: BrowserFilesystemAdapter);
|
|
25
|
-
/**
|
|
26
|
-
* Initialize with directory picker
|
|
27
|
-
*/
|
|
28
|
-
initializeWithDirectoryPicker(): Promise<BrowserDirectoryHandle>;
|
|
29
|
-
/**
|
|
30
|
-
* Get current browser sync state
|
|
31
|
-
*/
|
|
32
|
-
getSyncState(): BrowserSyncState;
|
|
33
|
-
/**
|
|
34
|
-
* Commit local changes from browser directory
|
|
35
|
-
*/
|
|
36
|
-
commitLocal(dryRun?: boolean): Promise<SyncResult>;
|
|
37
|
-
/**
|
|
38
|
-
* Full bidirectional sync
|
|
39
|
-
*/
|
|
40
|
-
sync(dryRun?: boolean): Promise<SyncResult>;
|
|
41
|
-
/**
|
|
42
|
-
* Adapted sync that works with browser filesystem
|
|
43
|
-
*/
|
|
44
|
-
private adaptedSync;
|
|
45
|
-
/**
|
|
46
|
-
* Get sync status
|
|
47
|
-
*/
|
|
48
|
-
getStatus(): Promise<{
|
|
49
|
-
snapshot: SyncSnapshot | null;
|
|
50
|
-
hasChanges: boolean;
|
|
51
|
-
changeCount: number;
|
|
52
|
-
lastSync: Date | null;
|
|
53
|
-
browserState: BrowserSyncState;
|
|
54
|
-
}>;
|
|
55
|
-
/**
|
|
56
|
-
* Set root directory URL for sharing
|
|
57
|
-
*/
|
|
58
|
-
setRootDirectoryUrl(url: string): Promise<void>;
|
|
59
|
-
/**
|
|
60
|
-
* Get root directory URL for sharing
|
|
61
|
-
*/
|
|
62
|
-
getRootDirectoryUrl(): Promise<string | null>;
|
|
63
|
-
}
|
|
64
|
-
//# sourceMappingURL=browser-sync-engine.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"browser-sync-engine.d.ts","sourceRoot":"","sources":["../../src/browser/browser-sync-engine.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AAIjD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAa,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,WAAW,CAAC;AA+FnB;;GAEG;AACH,qBAAa,kBAAkB;WAChB,MAAM,CACjB,aAAa,CAAC,EAAE,MAAM,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;CA6BjB;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,QAAQ,CAAO;gBAEX,IAAI,EAAE,IAAI,EAAE,UAAU,GAAE,wBAAoC;IAMxE;;OAEG;IACG,6BAA6B,IAAI,OAAO,CAAC,sBAAsB,CAAC;IAoBtE;;OAEG;IACH,YAAY,IAAI,gBAAgB;IAIhC;;OAEG;IACG,WAAW,CAAC,MAAM,UAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;IAWtD;;OAEG;IACG,IAAI,CAAC,MAAM,UAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;IAU/C;;OAEG;YACW,WAAW;IAmEzB;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC;QACzB,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;QAC9B,UAAU,EAAE,OAAO,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;QACtB,YAAY,EAAE,gBAAgB,CAAC;KAChC,CAAC;IAaF;;OAEG;IACG,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAerD;;OAEG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAIpD"}
|