pushwork 1.0.0 → 1.0.3
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 +23 -21
- package/dist/cli/commands.d.ts +6 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +114 -4
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli.js +27 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/change-detection.d.ts.map +1 -1
- package/dist/core/change-detection.js +27 -9
- package/dist/core/change-detection.js.map +1 -1
- package/dist/core/move-detection.d.ts.map +1 -1
- package/dist/core/move-detection.js +8 -2
- package/dist/core/move-detection.js.map +1 -1
- package/dist/core/sync-engine.d.ts +4 -0
- package/dist/core/sync-engine.d.ts.map +1 -1
- package/dist/core/sync-engine.js +263 -7
- package/dist/core/sync-engine.js.map +1 -1
- package/dist/types/documents.d.ts +2 -0
- package/dist/types/documents.d.ts.map +1 -1
- package/dist/types/documents.js.map +1 -1
- package/dist/utils/fs.d.ts.map +1 -1
- package/dist/utils/fs.js +7 -1
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/network-sync.d.ts.map +1 -1
- package/dist/utils/network-sync.js +16 -3
- package/dist/utils/network-sync.js.map +1 -1
- package/package.json +30 -30
- package/src/cli/commands.ts +162 -8
- package/src/cli.ts +40 -0
- package/src/core/change-detection.ts +25 -12
- package/src/core/move-detection.ts +8 -2
- package/src/core/sync-engine.ts +270 -7
- package/src/types/documents.ts +2 -0
- package/src/utils/fs.ts +7 -3
- package/src/utils/network-sync.ts +19 -3
- package/test/integration/clone-test.sh +0 -0
- package/test/integration/conflict-resolution-test.sh +0 -0
- package/test/integration/debug-both-nested.sh +74 -0
- package/test/integration/debug-concurrent-nested.sh +87 -0
- package/test/integration/debug-nested.sh +73 -0
- package/test/integration/deletion-behavior-test.sh +0 -0
- package/test/integration/deletion-sync-test-simple.sh +0 -0
- package/test/integration/deletion-sync-test.sh +0 -0
- package/test/integration/full-integration-test.sh +0 -0
- package/test/integration/fuzzer.test.ts +865 -0
- package/test/integration/manual-sync-test.sh +84 -0
- package/test/run-tests.sh +0 -0
- package/test/unit/sync-convergence.test.ts +493 -0
- package/tools/browser-sync/README.md +0 -116
- package/tools/browser-sync/package.json +0 -44
- package/tools/browser-sync/patchwork.json +0 -1
- package/tools/browser-sync/pnpm-lock.yaml +0 -4202
- package/tools/browser-sync/src/components/BrowserSyncTool.tsx +0 -599
- package/tools/browser-sync/src/index.ts +0 -20
- package/tools/browser-sync/src/polyfills.ts +0 -31
- package/tools/browser-sync/src/styles.css +0 -290
- package/tools/browser-sync/src/types.ts +0 -27
- package/tools/browser-sync/vite.config.ts +0 -25
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move-detection.d.ts","sourceRoot":"","sources":["../../src/core/move-detection.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EAGd,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,cAAc,EAAc,MAAM,oBAAoB,CAAC;AAEhE;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAO;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAO;IAE/C;;OAEG;IACG,WAAW,CACf,OAAO,EAAE,cAAc,EAAE,EACzB,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,KAAK,EAAE,aAAa,EAAE,CAAC;QAAC,gBAAgB,EAAE,cAAc,EAAE,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"move-detection.d.ts","sourceRoot":"","sources":["../../src/core/move-detection.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EAGd,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,cAAc,EAAc,MAAM,oBAAoB,CAAC;AAEhE;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAO;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAO;IAE/C;;OAEG;IACG,WAAW,CACf,OAAO,EAAE,cAAc,EAAE,EACzB,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,KAAK,EAAE,aAAa,EAAE,CAAC;QAAC,gBAAgB,EAAE,cAAc,EAAE,CAAA;KAAE,CAAC;IAoF1E;;OAEG;YACW,qBAAqB;IAWnC;;OAEG;IACH,sBAAsB,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG;QAC9C,SAAS,EAAE,aAAa,EAAE,CAAC;QAC3B,WAAW,EAAE,aAAa,EAAE,CAAC;QAC7B,kBAAkB,EAAE,aAAa,EAAE,CAAC;KACrC;IAQD;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG;QACrC,UAAU,EAAE,aAAa,EAAE,CAAC;QAC5B,SAAS,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,aAAa,EAAE,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAC9D;IA4CD;;OAEG;IACH,eAAe,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE;IAwBxD;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAKxB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO;IAI7C;;OAEG;IACH,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO;IAI9C;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM;IAKvC;;OAEG;IACH,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG;QACtC,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,iBAAiB,EAAE,MAAM,CAAC;KAC3B;CASF"}
|
|
@@ -25,13 +25,17 @@ class MoveDetector {
|
|
|
25
25
|
// Find potential moves by comparing content
|
|
26
26
|
for (const deletedFile of deletedFiles) {
|
|
27
27
|
const deletedContent = await this.getDeletedFileContent(deletedFile, snapshot);
|
|
28
|
-
|
|
28
|
+
// CRITICAL: Check for null explicitly, not falsy values
|
|
29
|
+
// Empty strings "" are valid file content!
|
|
30
|
+
if (deletedContent === null)
|
|
29
31
|
continue;
|
|
30
32
|
let bestMatch = null;
|
|
31
33
|
for (const createdFile of createdFiles) {
|
|
32
34
|
if (usedCreations.has(createdFile.path))
|
|
33
35
|
continue;
|
|
34
|
-
|
|
36
|
+
// CRITICAL: Check for null explicitly, not falsy values
|
|
37
|
+
// Empty strings "" are valid file content!
|
|
38
|
+
if (createdFile.localContent === null)
|
|
35
39
|
continue;
|
|
36
40
|
const similarity = await utils_1.ContentSimilarity.calculateSimilarity(deletedContent, createdFile.localContent);
|
|
37
41
|
if (similarity >= MoveDetector.PROMPT_THRESHOLD) {
|
|
@@ -48,6 +52,8 @@ class MoveDetector {
|
|
|
48
52
|
toPath: bestMatch.file.path,
|
|
49
53
|
similarity: bestMatch.similarity,
|
|
50
54
|
confidence,
|
|
55
|
+
// Capture new content (may include modifications)
|
|
56
|
+
newContent: bestMatch.file.localContent || undefined,
|
|
51
57
|
});
|
|
52
58
|
// Only consume the deletion/creation pair when we would auto-apply the move.
|
|
53
59
|
// If we only want to prompt, leave the original changes in place so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move-detection.js","sourceRoot":"","sources":["../../src/core/move-detection.ts"],"names":[],"mappings":";;;AAMA,oCAA+E;AAC/E,yDAAgE;AAEhE;;GAEG;AACH,MAAa,YAAY;IAIvB;;OAEG;IACH,KAAK,CAAC,WAAW,CACf,OAAyB,EACzB,QAAsB,EACtB,QAAgB;QAEhB,mCAAmC;QACnC,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CACjC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,UAAU,KAAK,6BAAU,CAAC,UAAU,CACjE,CAAC;QACF,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CACjC,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,YAAY;YACd,CAAC,CAAC,UAAU,KAAK,6BAAU,CAAC,UAAU;YACtC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAC9B,CAAC;QAEF,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3D,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC;QAClD,CAAC;QAED,MAAM,KAAK,GAAoB,EAAE,CAAC;QAClC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;QACxC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;QAExC,4CAA4C;QAC5C,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;YACvC,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,qBAAqB,CACrD,WAAW,EACX,QAAQ,CACT,CAAC;YACF,IAAI,
|
|
1
|
+
{"version":3,"file":"move-detection.js","sourceRoot":"","sources":["../../src/core/move-detection.ts"],"names":[],"mappings":"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"}
|
|
@@ -106,5 +106,9 @@ export declare class SyncEngine {
|
|
|
106
106
|
* Generate human-readable summary of changes
|
|
107
107
|
*/
|
|
108
108
|
private generateChangeSummary;
|
|
109
|
+
/**
|
|
110
|
+
* Update the lastSyncAt timestamp on the root directory document
|
|
111
|
+
*/
|
|
112
|
+
private touchRootDirectory;
|
|
109
113
|
}
|
|
110
114
|
//# sourceMappingURL=sync-engine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../src/core/sync-engine.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sync-engine.d.ts","sourceRoot":"","sources":["../../src/core/sync-engine.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,IAAI,EAIL,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,YAAY,EACZ,UAAU,EAQV,aAAa,EACd,MAAM,UAAU,CAAC;AAgBlB,OAAO,EAAkB,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGpE;;GAEG;AACH,qBAAa,UAAU;IASnB,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,QAAQ;IATlB,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,kBAAkB,CAAiB;IAC3C,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,mBAAmB,CAAC,CAAS;gBAG3B,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EACxB,eAAe,GAAE,MAAM,EAAO,EAC9B,kBAAkB,GAAE,OAAc,EAClC,mBAAmB,CAAC,EAAE,MAAM;IAS9B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAKrB;;OAEG;IACG,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAS3D;;OAEG;IACG,WAAW,CAAC,MAAM,UAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;IA6FtD;;OAEG;IACG,IAAI,CAAC,MAAM,UAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;IA8N/C;;OAEG;YACW,gBAAgB;IA+D9B;;OAEG;YACW,iBAAiB;IAwC/B;;OAEG;YACW,wBAAwB;IAyFtC;;OAEG;YACW,wBAAwB;IAqEtC;;OAEG;YACW,iBAAiB;IAyF/B;;OAEG;YACW,gBAAgB;IAsC9B;;OAEG;YACW,gBAAgB;IA+D9B;;OAEG;YACW,gBAAgB;IA4B9B;;OAEG;YACW,kBAAkB;IAoEhC;;;OAGG;YACW,uBAAuB;IAqIrC;;OAEG;YACW,uBAAuB;IAkFrC;;OAEG;YACW,4BAA4B;IAkD1C;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAS/B;;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;KACvB,CAAC;IAsBF;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC;QAC9B,OAAO,EAAE,cAAc,EAAE,CAAC;QAC1B,KAAK,EAAE,aAAa,EAAE,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IAsBF;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAiD7B;;OAEG;YACW,kBAAkB;CA8CjC"}
|
package/dist/core/sync-engine.js
CHANGED
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.SyncEngine = void 0;
|
|
37
|
+
const myers = require("myers-diff");
|
|
4
38
|
const automerge_repo_1 = require("@automerge/automerge-repo");
|
|
39
|
+
const A = __importStar(require("@automerge/automerge"));
|
|
5
40
|
const types_1 = require("../types");
|
|
6
41
|
const utils_1 = require("../utils");
|
|
7
42
|
const content_1 = require("../utils/content");
|
|
@@ -91,6 +126,11 @@ class SyncEngine {
|
|
|
91
126
|
result.directoriesChanged += commitResult.directoriesChanged;
|
|
92
127
|
result.errors.push(...commitResult.errors);
|
|
93
128
|
result.warnings.push(...commitResult.warnings);
|
|
129
|
+
// Touch root directory if any changes were made
|
|
130
|
+
const hasChanges = result.filesChanged > 0 || result.directoriesChanged > 0;
|
|
131
|
+
if (hasChanges) {
|
|
132
|
+
await this.touchRootDirectory(snapshot, dryRun);
|
|
133
|
+
}
|
|
94
134
|
// Save updated snapshot if not dry run
|
|
95
135
|
if (!dryRun) {
|
|
96
136
|
await this.snapshotManager.save(snapshot);
|
|
@@ -115,6 +155,8 @@ class SyncEngine {
|
|
|
115
155
|
* Run full bidirectional sync
|
|
116
156
|
*/
|
|
117
157
|
async sync(dryRun = false) {
|
|
158
|
+
const syncStartTime = Date.now();
|
|
159
|
+
const timings = {};
|
|
118
160
|
const result = {
|
|
119
161
|
success: false,
|
|
120
162
|
filesChanged: 0,
|
|
@@ -126,29 +168,40 @@ class SyncEngine {
|
|
|
126
168
|
this.handlesToWaitOn = [];
|
|
127
169
|
try {
|
|
128
170
|
// Load current snapshot
|
|
171
|
+
const t0 = Date.now();
|
|
129
172
|
let snapshot = await this.snapshotManager.load();
|
|
173
|
+
timings["load_snapshot"] = Date.now() - t0;
|
|
130
174
|
if (!snapshot) {
|
|
131
175
|
snapshot = this.snapshotManager.createEmpty();
|
|
132
176
|
}
|
|
133
177
|
// Backup snapshot before starting
|
|
178
|
+
const t1 = Date.now();
|
|
134
179
|
if (!dryRun) {
|
|
135
180
|
await this.snapshotManager.backup();
|
|
136
181
|
}
|
|
182
|
+
timings["backup_snapshot"] = Date.now() - t1;
|
|
137
183
|
// Detect all changes
|
|
184
|
+
const t2 = Date.now();
|
|
138
185
|
const changes = await this.changeDetector.detectChanges(snapshot);
|
|
186
|
+
timings["detect_changes"] = Date.now() - t2;
|
|
139
187
|
// Detect moves
|
|
188
|
+
const t3 = Date.now();
|
|
140
189
|
const { moves, remainingChanges } = await this.moveDetector.detectMoves(changes, snapshot, this.rootPath);
|
|
190
|
+
timings["detect_moves"] = Date.now() - t3;
|
|
141
191
|
if (changes.length > 0) {
|
|
142
192
|
console.log(`🔄 Syncing ${changes.length} changes...`);
|
|
143
193
|
}
|
|
144
194
|
// Phase 1: Push local changes to remote
|
|
195
|
+
const t4 = Date.now();
|
|
145
196
|
const phase1Result = await this.pushLocalChanges(remainingChanges, moves, snapshot, dryRun);
|
|
197
|
+
timings["phase1_push"] = Date.now() - t4;
|
|
146
198
|
result.filesChanged += phase1Result.filesChanged;
|
|
147
199
|
result.directoriesChanged += phase1Result.directoriesChanged;
|
|
148
200
|
result.errors.push(...phase1Result.errors);
|
|
149
201
|
result.warnings.push(...phase1Result.warnings);
|
|
150
202
|
// Always wait for network sync when enabled (not just when local changes exist)
|
|
151
203
|
// This is critical for clone scenarios where we need to pull remote changes
|
|
204
|
+
const t5 = Date.now();
|
|
152
205
|
if (!dryRun && this.networkSyncEnabled) {
|
|
153
206
|
try {
|
|
154
207
|
// If we have a root directory URL, wait for it to sync
|
|
@@ -157,7 +210,22 @@ class SyncEngine {
|
|
|
157
210
|
this.handlesToWaitOn.push(rootHandle);
|
|
158
211
|
}
|
|
159
212
|
if (this.handlesToWaitOn.length > 0) {
|
|
213
|
+
const tWaitStart = Date.now();
|
|
160
214
|
await (0, network_sync_1.waitForSync)(this.handlesToWaitOn, (0, network_sync_1.getSyncServerStorageId)(this.syncServerStorageId));
|
|
215
|
+
timings["network_sync"] = Date.now() - tWaitStart;
|
|
216
|
+
// CRITICAL: Wait a bit after our changes reach the server to allow
|
|
217
|
+
// time for WebSocket to deliver OTHER peers' changes to us.
|
|
218
|
+
// waitForSync only ensures OUR changes reached the server, not that
|
|
219
|
+
// we've RECEIVED changes from other peers. This delay allows the
|
|
220
|
+
// WebSocket protocol to propagate peer changes before we re-detect.
|
|
221
|
+
// Without this, concurrent operations on different peers can miss
|
|
222
|
+
// each other due to timing races.
|
|
223
|
+
//
|
|
224
|
+
// Optimization: Only wait if we pushed changes (shorter delay if no changes)
|
|
225
|
+
const tDelayStart = Date.now();
|
|
226
|
+
const delayMs = phase1Result.filesChanged > 0 ? 200 : 100;
|
|
227
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
228
|
+
timings["post_sync_delay"] = Date.now() - tDelayStart;
|
|
161
229
|
}
|
|
162
230
|
}
|
|
163
231
|
catch (error) {
|
|
@@ -165,21 +233,91 @@ class SyncEngine {
|
|
|
165
233
|
result.warnings.push(`Network sync failed: ${error}`);
|
|
166
234
|
}
|
|
167
235
|
}
|
|
236
|
+
timings["total_network"] = Date.now() - t5;
|
|
168
237
|
// Re-detect remote changes after network sync to ensure fresh state
|
|
169
238
|
// This fixes race conditions where we detect changes before server propagation
|
|
239
|
+
// NOTE: We DON'T update snapshot heads yet - that would prevent detecting remote changes!
|
|
240
|
+
const t6 = Date.now();
|
|
170
241
|
const freshChanges = await this.changeDetector.detectChanges(snapshot);
|
|
171
242
|
const freshRemoteChanges = freshChanges.filter((c) => c.changeType === types_1.ChangeType.REMOTE_ONLY ||
|
|
172
243
|
c.changeType === types_1.ChangeType.BOTH_CHANGED);
|
|
244
|
+
timings["redetect_changes"] = Date.now() - t6;
|
|
173
245
|
// Phase 2: Pull remote changes to local using fresh detection
|
|
246
|
+
const t7 = Date.now();
|
|
174
247
|
const phase2Result = await this.pullRemoteChanges(freshRemoteChanges, snapshot, dryRun);
|
|
248
|
+
timings["phase2_pull"] = Date.now() - t7;
|
|
175
249
|
result.filesChanged += phase2Result.filesChanged;
|
|
176
250
|
result.directoriesChanged += phase2Result.directoriesChanged;
|
|
177
251
|
result.errors.push(...phase2Result.errors);
|
|
178
252
|
result.warnings.push(...phase2Result.warnings);
|
|
253
|
+
// CRITICAL FIX: Update snapshot heads AFTER pulling remote changes
|
|
254
|
+
// This ensures that change detection can find remote changes, and we only
|
|
255
|
+
// update the snapshot after the filesystem is in sync with the documents
|
|
256
|
+
const t8 = Date.now();
|
|
257
|
+
if (!dryRun) {
|
|
258
|
+
// Update file document heads
|
|
259
|
+
for (const [filePath, snapshotEntry] of snapshot.files.entries()) {
|
|
260
|
+
try {
|
|
261
|
+
const handle = await this.repo.find(snapshotEntry.url);
|
|
262
|
+
const currentHeads = handle.heads();
|
|
263
|
+
if (!A.equals(currentHeads, snapshotEntry.head)) {
|
|
264
|
+
// Update snapshot with current heads after pulling changes
|
|
265
|
+
snapshot.files.set(filePath, {
|
|
266
|
+
...snapshotEntry,
|
|
267
|
+
head: currentHeads,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
// Handle might not exist if file was deleted, skip
|
|
273
|
+
console.warn(`Could not update heads for ${filePath}: ${error}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Update directory document heads
|
|
277
|
+
for (const [dirPath, snapshotEntry] of snapshot.directories.entries()) {
|
|
278
|
+
try {
|
|
279
|
+
const handle = await this.repo.find(snapshotEntry.url);
|
|
280
|
+
const currentHeads = handle.heads();
|
|
281
|
+
if (!A.equals(currentHeads, snapshotEntry.head)) {
|
|
282
|
+
// Update snapshot with current heads after pulling changes
|
|
283
|
+
snapshot.directories.set(dirPath, {
|
|
284
|
+
...snapshotEntry,
|
|
285
|
+
head: currentHeads,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
// Handle might not exist if directory was deleted, skip
|
|
291
|
+
console.warn(`Could not update heads for directory ${dirPath}: ${error}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
timings["update_snapshot_heads"] = Date.now() - t8;
|
|
296
|
+
// Touch root directory if any changes were made during sync
|
|
297
|
+
const t9 = Date.now();
|
|
298
|
+
const hasChanges = result.filesChanged > 0 || result.directoriesChanged > 0;
|
|
299
|
+
if (hasChanges) {
|
|
300
|
+
await this.touchRootDirectory(snapshot, dryRun);
|
|
301
|
+
}
|
|
302
|
+
timings["touch_root"] = Date.now() - t9;
|
|
179
303
|
// Save updated snapshot if not dry run
|
|
304
|
+
const t10 = Date.now();
|
|
180
305
|
if (!dryRun) {
|
|
181
306
|
await this.snapshotManager.save(snapshot);
|
|
182
307
|
}
|
|
308
|
+
timings["save_snapshot"] = Date.now() - t10;
|
|
309
|
+
// Output timing breakdown if enabled via environment variable
|
|
310
|
+
if (process.env.PUSHWORK_TIMING === "1") {
|
|
311
|
+
const totalTime = Date.now() - syncStartTime;
|
|
312
|
+
console.error("\n⏱️ Sync Timing Breakdown:");
|
|
313
|
+
for (const [key, ms] of Object.entries(timings)) {
|
|
314
|
+
const pct = ((ms / totalTime) * 100).toFixed(1);
|
|
315
|
+
console.error(` ${key.padEnd(25)} ${ms.toString().padStart(5)}ms (${pct}%)`);
|
|
316
|
+
}
|
|
317
|
+
console.error(` ${"TOTAL".padEnd(25)} ${totalTime
|
|
318
|
+
.toString()
|
|
319
|
+
.padStart(5)}ms (100.0%)\n`);
|
|
320
|
+
}
|
|
183
321
|
result.success = result.errors.length === 0;
|
|
184
322
|
return result;
|
|
185
323
|
}
|
|
@@ -282,7 +420,9 @@ class SyncEngine {
|
|
|
282
420
|
*/
|
|
283
421
|
async applyLocalChangeToRemote(change, snapshot, dryRun) {
|
|
284
422
|
const snapshotEntry = snapshot.files.get(change.path);
|
|
285
|
-
|
|
423
|
+
// CRITICAL: Check for null explicitly, not falsy values
|
|
424
|
+
// Empty strings "" and empty Uint8Array are valid file content!
|
|
425
|
+
if (change.localContent === null) {
|
|
286
426
|
// File was deleted locally
|
|
287
427
|
if (snapshotEntry) {
|
|
288
428
|
console.log(`🗑️ ${change.path}`);
|
|
@@ -301,6 +441,8 @@ class SyncEngine {
|
|
|
301
441
|
const handle = await this.createRemoteFile(change, dryRun);
|
|
302
442
|
if (!dryRun && handle) {
|
|
303
443
|
await this.addFileToDirectory(snapshot, change.path, handle.url, dryRun);
|
|
444
|
+
// CRITICAL FIX: Update snapshot with heads AFTER adding to directory
|
|
445
|
+
// The addFileToDirectory call above may have changed the document heads
|
|
304
446
|
this.snapshotManager.updateFileEntry(snapshot, change.path, {
|
|
305
447
|
path: (0, utils_1.normalizePath)(this.rootPath + "/" + change.path),
|
|
306
448
|
url: handle.url,
|
|
@@ -313,6 +455,28 @@ class SyncEngine {
|
|
|
313
455
|
else {
|
|
314
456
|
// Update existing file
|
|
315
457
|
console.log(`📝 ${change.path}`);
|
|
458
|
+
// log the change in detail for debugging
|
|
459
|
+
// split out remotea nd local content so we don't overwhelm the logs
|
|
460
|
+
const { remoteContent, localContent, ...rest } = change;
|
|
461
|
+
console.log(`🔍 Change in detail:`, rest);
|
|
462
|
+
// compare the local and remote content and make a diff so we can
|
|
463
|
+
// see what happened between the two
|
|
464
|
+
const { diff, changed } = require("myers-diff");
|
|
465
|
+
const lhs = change.remoteContent ? change.remoteContent.toString() : "";
|
|
466
|
+
const rhs = change.localContent ? change.localContent.toString() : "";
|
|
467
|
+
const changes = diff(lhs, rhs, { compare: "chars" });
|
|
468
|
+
for (const change of changes) {
|
|
469
|
+
if (changed(change.lhs)) {
|
|
470
|
+
// deleted
|
|
471
|
+
const { pos, text, del, length } = change.lhs;
|
|
472
|
+
console.log(`🔍 Deleted:`, { pos, text, del, length });
|
|
473
|
+
}
|
|
474
|
+
if (changed(change.rhs)) {
|
|
475
|
+
// added
|
|
476
|
+
const { pos, text, add, length } = change.rhs;
|
|
477
|
+
console.log(`🔍 Added:`, { pos, text, add, length });
|
|
478
|
+
}
|
|
479
|
+
}
|
|
316
480
|
await this.updateRemoteFile(snapshotEntry.url, change.localContent, dryRun, snapshot, change.path);
|
|
317
481
|
}
|
|
318
482
|
}
|
|
@@ -322,9 +486,11 @@ class SyncEngine {
|
|
|
322
486
|
async applyRemoteChangeToLocal(change, snapshot, dryRun) {
|
|
323
487
|
const localPath = (0, utils_1.normalizePath)(this.rootPath + "/" + change.path);
|
|
324
488
|
if (!change.remoteHead) {
|
|
325
|
-
throw new Error(`No remote head found for remote change to${change.path}`);
|
|
489
|
+
throw new Error(`No remote head found for remote change to ${change.path}`);
|
|
326
490
|
}
|
|
327
|
-
|
|
491
|
+
// CRITICAL: Check for null explicitly, not falsy values
|
|
492
|
+
// Empty strings "" and empty Uint8Array are valid file content!
|
|
493
|
+
if (change.remoteContent === null) {
|
|
328
494
|
// File was deleted remotely
|
|
329
495
|
console.log(`🗑️ ${change.path}`);
|
|
330
496
|
if (!dryRun) {
|
|
@@ -393,18 +559,39 @@ class SyncEngine {
|
|
|
393
559
|
// 2) Ensure destination directory document exists and add file entry there
|
|
394
560
|
const destDirUrl = await this.ensureDirectoryDocument(snapshot, toDirPath, dryRun);
|
|
395
561
|
await this.addFileToDirectory(snapshot, move.toPath, fromEntry.url, dryRun);
|
|
396
|
-
// 3) Update the FileDocument name to match new
|
|
562
|
+
// 3) Update the FileDocument name and content to match new location/state
|
|
397
563
|
try {
|
|
398
564
|
const handle = await this.repo.find(fromEntry.url);
|
|
399
565
|
const heads = fromEntry.head;
|
|
566
|
+
// Update both name and content (if content changed during move)
|
|
400
567
|
if (heads && heads.length > 0) {
|
|
401
568
|
handle.changeAt(heads, (doc) => {
|
|
402
569
|
doc.name = toFileName;
|
|
570
|
+
// If new content is provided, update it (handles move + modification case)
|
|
571
|
+
if (move.newContent !== undefined) {
|
|
572
|
+
const isText = this.isTextContent(move.newContent);
|
|
573
|
+
if (isText && typeof move.newContent === "string") {
|
|
574
|
+
(0, automerge_repo_1.updateText)(doc, ["content"], move.newContent);
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
doc.content = move.newContent;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
403
580
|
});
|
|
404
581
|
}
|
|
405
582
|
else {
|
|
406
583
|
handle.change((doc) => {
|
|
407
584
|
doc.name = toFileName;
|
|
585
|
+
// If new content is provided, update it (handles move + modification case)
|
|
586
|
+
if (move.newContent !== undefined) {
|
|
587
|
+
const isText = this.isTextContent(move.newContent);
|
|
588
|
+
if (isText && typeof move.newContent === "string") {
|
|
589
|
+
(0, automerge_repo_1.updateText)(doc, ["content"], move.newContent);
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
doc.content = move.newContent;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
408
595
|
});
|
|
409
596
|
}
|
|
410
597
|
// Track file handle for network sync
|
|
@@ -426,7 +613,9 @@ class SyncEngine {
|
|
|
426
613
|
* Create new remote file document
|
|
427
614
|
*/
|
|
428
615
|
async createRemoteFile(change, dryRun) {
|
|
429
|
-
|
|
616
|
+
// CRITICAL: Check for null explicitly, not falsy values
|
|
617
|
+
// Empty strings "" and empty Uint8Array are valid file content!
|
|
618
|
+
if (dryRun || change.localContent === null)
|
|
430
619
|
return null;
|
|
431
620
|
const isText = this.isTextContent(change.localContent);
|
|
432
621
|
// Create initial document structure
|
|
@@ -463,10 +652,22 @@ class SyncEngine {
|
|
|
463
652
|
const doc = await handle.doc();
|
|
464
653
|
const currentContent = doc?.content;
|
|
465
654
|
const contentChanged = !(0, content_1.isContentEqual)(content, currentContent);
|
|
655
|
+
// CRITICAL FIX: Always update snapshot heads, even when content is identical
|
|
656
|
+
// This prevents stale head issues that cause false change detection
|
|
657
|
+
const snapshotEntry = snapshot.files.get(filePath);
|
|
658
|
+
if (snapshotEntry) {
|
|
659
|
+
// Update snapshot with current document heads
|
|
660
|
+
snapshot.files.set(filePath, {
|
|
661
|
+
...snapshotEntry,
|
|
662
|
+
head: handle.heads(),
|
|
663
|
+
});
|
|
664
|
+
}
|
|
466
665
|
if (!contentChanged) {
|
|
666
|
+
// Content is identical, but we've updated the snapshot heads above
|
|
667
|
+
// This prevents fresh change detection from seeing stale heads
|
|
668
|
+
console.log(`🔍 Content is identical, but we've updated the snapshot heads above`);
|
|
467
669
|
return;
|
|
468
670
|
}
|
|
469
|
-
const snapshotEntry = snapshot.files.get(filePath);
|
|
470
671
|
const heads = snapshotEntry?.head;
|
|
471
672
|
if (!heads) {
|
|
472
673
|
throw new Error(`No heads found for ${url}`);
|
|
@@ -480,7 +681,8 @@ class SyncEngine {
|
|
|
480
681
|
doc.content = content;
|
|
481
682
|
}
|
|
482
683
|
});
|
|
483
|
-
|
|
684
|
+
// Update snapshot with new heads after content change
|
|
685
|
+
if (!dryRun && snapshotEntry) {
|
|
484
686
|
snapshot.files.set(filePath, {
|
|
485
687
|
...snapshotEntry,
|
|
486
688
|
head: handle.heads(),
|
|
@@ -559,6 +761,11 @@ class SyncEngine {
|
|
|
559
761
|
}
|
|
560
762
|
if (didChange) {
|
|
561
763
|
this.handlesToWaitOn.push(dirHandle);
|
|
764
|
+
// CRITICAL FIX: Update snapshot with new directory heads immediately
|
|
765
|
+
// This prevents stale head issues that cause convergence problems
|
|
766
|
+
if (snapshotEntry) {
|
|
767
|
+
snapshotEntry.head = dirHandle.heads();
|
|
768
|
+
}
|
|
562
769
|
}
|
|
563
770
|
}
|
|
564
771
|
/**
|
|
@@ -640,6 +847,12 @@ class SyncEngine {
|
|
|
640
847
|
this.handlesToWaitOn.push(dirHandle);
|
|
641
848
|
if (didChange) {
|
|
642
849
|
this.handlesToWaitOn.push(parentHandle);
|
|
850
|
+
// CRITICAL FIX: Update parent directory heads in snapshot immediately
|
|
851
|
+
// This prevents stale head issues when parent directory is modified
|
|
852
|
+
const parentSnapshotEntry = snapshot.directories.get(parentPath);
|
|
853
|
+
if (parentSnapshotEntry) {
|
|
854
|
+
parentSnapshotEntry.head = parentHandle.heads();
|
|
855
|
+
}
|
|
643
856
|
}
|
|
644
857
|
// Update snapshot with new directory
|
|
645
858
|
this.snapshotManager.updateDirectoryEntry(snapshot, directoryPath, {
|
|
@@ -679,11 +892,13 @@ class SyncEngine {
|
|
|
679
892
|
this.handlesToWaitOn.push(dirHandle);
|
|
680
893
|
const snapshotEntry = snapshot.directories.get(directoryPath);
|
|
681
894
|
const heads = snapshotEntry?.head;
|
|
895
|
+
let didChange = false;
|
|
682
896
|
if (heads) {
|
|
683
897
|
dirHandle.changeAt(heads, (doc) => {
|
|
684
898
|
const indexToRemove = doc.docs.findIndex((entry) => entry.name === fileName && entry.type === "file");
|
|
685
899
|
if (indexToRemove !== -1) {
|
|
686
900
|
doc.docs.splice(indexToRemove, 1);
|
|
901
|
+
didChange = true;
|
|
687
902
|
console.log(`🗑️ Removed ${fileName} from directory ${directoryPath || "root"}`);
|
|
688
903
|
}
|
|
689
904
|
});
|
|
@@ -693,10 +908,16 @@ class SyncEngine {
|
|
|
693
908
|
const indexToRemove = doc.docs.findIndex((entry) => entry.name === fileName && entry.type === "file");
|
|
694
909
|
if (indexToRemove !== -1) {
|
|
695
910
|
doc.docs.splice(indexToRemove, 1);
|
|
911
|
+
didChange = true;
|
|
696
912
|
console.log(`🗑️ Removed ${fileName} from directory ${directoryPath || "root"}`);
|
|
697
913
|
}
|
|
698
914
|
});
|
|
699
915
|
}
|
|
916
|
+
// CRITICAL FIX: Update snapshot with new directory heads immediately
|
|
917
|
+
// This prevents stale head issues that cause convergence problems
|
|
918
|
+
if (didChange && snapshotEntry) {
|
|
919
|
+
snapshotEntry.head = dirHandle.heads();
|
|
920
|
+
}
|
|
700
921
|
}
|
|
701
922
|
catch (error) {
|
|
702
923
|
console.warn(`Failed to remove ${fileName} from directory ${directoryPath || "root"}: ${error}`);
|
|
@@ -812,6 +1033,41 @@ class SyncEngine {
|
|
|
812
1033
|
}
|
|
813
1034
|
return parts.join(", ");
|
|
814
1035
|
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Update the lastSyncAt timestamp on the root directory document
|
|
1038
|
+
*/
|
|
1039
|
+
async touchRootDirectory(snapshot, dryRun) {
|
|
1040
|
+
if (dryRun || !snapshot.rootDirectoryUrl) {
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
try {
|
|
1044
|
+
const rootHandle = await this.repo.find(snapshot.rootDirectoryUrl);
|
|
1045
|
+
const snapshotEntry = snapshot.directories.get("");
|
|
1046
|
+
const heads = snapshotEntry?.head;
|
|
1047
|
+
const timestamp = Date.now();
|
|
1048
|
+
if (heads) {
|
|
1049
|
+
rootHandle.changeAt(heads, (doc) => {
|
|
1050
|
+
doc.lastSyncAt = timestamp;
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
else {
|
|
1054
|
+
rootHandle.change((doc) => {
|
|
1055
|
+
doc.lastSyncAt = timestamp;
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
// Track root directory for network sync
|
|
1059
|
+
this.handlesToWaitOn.push(rootHandle);
|
|
1060
|
+
// CRITICAL FIX: Update root directory heads in snapshot immediately
|
|
1061
|
+
// This prevents stale head issues when root directory is modified
|
|
1062
|
+
if (snapshotEntry) {
|
|
1063
|
+
snapshotEntry.head = rootHandle.heads();
|
|
1064
|
+
}
|
|
1065
|
+
console.log(`🕒 Updated root directory lastSyncAt to ${new Date(timestamp).toISOString()}`);
|
|
1066
|
+
}
|
|
1067
|
+
catch (error) {
|
|
1068
|
+
console.warn(`Failed to update root directory lastSyncAt: ${error}`);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
815
1071
|
}
|
|
816
1072
|
exports.SyncEngine = SyncEngine;
|
|
817
1073
|
//# sourceMappingURL=sync-engine.js.map
|