pushwork 1.0.22 → 1.0.25
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/CLAUDE.md +24 -2
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +6 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +40 -6
- package/dist/commands.js.map +1 -1
- package/dist/core/change-detection.d.ts +8 -1
- package/dist/core/change-detection.d.ts.map +1 -1
- package/dist/core/change-detection.js +69 -1
- package/dist/core/change-detection.js.map +1 -1
- package/dist/core/sync-engine.d.ts +6 -0
- package/dist/core/sync-engine.d.ts.map +1 -1
- package/dist/core/sync-engine.js +180 -43
- package/dist/core/sync-engine.js.map +1 -1
- package/dist/types/snapshot.d.ts +1 -0
- package/dist/types/snapshot.d.ts.map +1 -1
- package/dist/utils/content.d.ts +5 -0
- package/dist/utils/content.d.ts.map +1 -1
- package/dist/utils/content.js +9 -0
- package/dist/utils/content.js.map +1 -1
- package/dist/utils/network-sync.d.ts +11 -2
- package/dist/utils/network-sync.d.ts.map +1 -1
- package/dist/utils/network-sync.js +103 -74
- package/dist/utils/network-sync.js.map +1 -1
- package/dist/utils/repo-factory.d.ts.map +1 -1
- package/dist/utils/repo-factory.js +0 -1
- package/dist/utils/repo-factory.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +19 -0
- package/src/commands.ts +52 -5
- package/src/core/change-detection.ts +81 -2
- package/src/core/sync-engine.ts +212 -49
- package/src/types/snapshot.ts +1 -0
- package/src/utils/content.ts +10 -0
- package/src/utils/network-sync.ts +133 -92
- package/src/utils/repo-factory.ts +0 -1
|
@@ -70,12 +70,13 @@ async function waitForBidirectionalSync(repo, rootDirectoryUrl, syncServerStorag
|
|
|
70
70
|
const currentHeads = handles
|
|
71
71
|
? getHandleHeads(handles)
|
|
72
72
|
: await getAllDocumentHeads(repo, rootDirectoryUrl);
|
|
73
|
-
//
|
|
73
|
+
// After first scan: scale timeout to tree size and reset the clock.
|
|
74
|
+
// The first scan is just establishing a baseline — its duration
|
|
75
|
+
// shouldn't count against the stability-wait timeout.
|
|
74
76
|
if (pollCount === 1) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
77
|
+
const scanDuration = Date.now() - startTime;
|
|
78
|
+
dynamicTimeoutMs = Math.max(timeoutMs, 5000 + currentHeads.size * 50) + scanDuration;
|
|
79
|
+
debug(`waitForBidirectionalSync: first scan took ${scanDuration}ms, timeout now ${dynamicTimeoutMs}ms for ${currentHeads.size} docs`);
|
|
79
80
|
}
|
|
80
81
|
// Check if heads are stable (no changes since last check)
|
|
81
82
|
const isStable = headsMapEqual(lastSeenHeads, currentHeads);
|
|
@@ -196,98 +197,126 @@ function headsMapEqual(a, b) {
|
|
|
196
197
|
}
|
|
197
198
|
return true;
|
|
198
199
|
}
|
|
200
|
+
/** Maximum documents to sync concurrently to avoid flooding the server */
|
|
201
|
+
const SYNC_BATCH_SIZE = 10;
|
|
199
202
|
/**
|
|
200
|
-
* Wait for
|
|
203
|
+
* Wait for a single document handle to sync to the server.
|
|
204
|
+
* Resolves with the handle on success, rejects with the handle on timeout.
|
|
205
|
+
*/
|
|
206
|
+
function waitForHandleSync(handle, syncServerStorageId, timeoutMs, startTime) {
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
let pollInterval;
|
|
209
|
+
const cleanup = () => {
|
|
210
|
+
clearTimeout(timeout);
|
|
211
|
+
clearInterval(pollInterval);
|
|
212
|
+
handle.off("remote-heads", onRemoteHeads);
|
|
213
|
+
};
|
|
214
|
+
const onConverged = () => {
|
|
215
|
+
debug(`waitForSync: ${handle.url.slice(0, 20)}... converged in ${Date.now() - startTime}ms`);
|
|
216
|
+
cleanup();
|
|
217
|
+
resolve(handle);
|
|
218
|
+
};
|
|
219
|
+
const timeout = setTimeout(() => {
|
|
220
|
+
debug(`waitForSync: ${handle.url.slice(0, 20)}... timed out after ${timeoutMs}ms`);
|
|
221
|
+
cleanup();
|
|
222
|
+
reject(handle);
|
|
223
|
+
}, timeoutMs);
|
|
224
|
+
const isConverged = () => {
|
|
225
|
+
const localHeads = handle.heads();
|
|
226
|
+
const info = handle.getSyncInfo(syncServerStorageId);
|
|
227
|
+
return A.equals(localHeads, info?.lastHeads);
|
|
228
|
+
};
|
|
229
|
+
const onRemoteHeads = ({ storageId, }) => {
|
|
230
|
+
if (storageId === syncServerStorageId && isConverged()) {
|
|
231
|
+
onConverged();
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
// Initial check
|
|
235
|
+
if (isConverged()) {
|
|
236
|
+
cleanup();
|
|
237
|
+
resolve(handle);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// Start polling and event listening
|
|
241
|
+
pollInterval = setInterval(() => {
|
|
242
|
+
if (isConverged()) {
|
|
243
|
+
onConverged();
|
|
244
|
+
}
|
|
245
|
+
}, 100);
|
|
246
|
+
handle.on("remote-heads", onRemoteHeads);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Wait for documents to sync to the remote server.
|
|
251
|
+
* Processes handles in batches to avoid flooding the server.
|
|
252
|
+
* Returns a result with any failed handles instead of throwing,
|
|
253
|
+
* so callers can attempt recovery (e.g. recreating documents).
|
|
201
254
|
*/
|
|
202
255
|
async function waitForSync(handlesToWaitOn, syncServerStorageId, timeoutMs = 60000) {
|
|
203
256
|
const startTime = Date.now();
|
|
204
257
|
if (!syncServerStorageId) {
|
|
205
258
|
debug("waitForSync: no sync server storage ID, skipping");
|
|
206
|
-
return;
|
|
259
|
+
return { failed: [] };
|
|
207
260
|
}
|
|
208
261
|
if (handlesToWaitOn.length === 0) {
|
|
209
262
|
debug("waitForSync: no documents to sync");
|
|
210
|
-
return;
|
|
263
|
+
return { failed: [] };
|
|
211
264
|
}
|
|
212
|
-
debug(`waitForSync: waiting for ${handlesToWaitOn.length} documents (timeout=${timeoutMs}ms)`);
|
|
265
|
+
debug(`waitForSync: waiting for ${handlesToWaitOn.length} documents (timeout=${timeoutMs}ms, batchSize=${SYNC_BATCH_SIZE})`);
|
|
266
|
+
// Separate already-synced from needs-sync
|
|
267
|
+
const needsSync = [];
|
|
213
268
|
let alreadySynced = 0;
|
|
214
|
-
const
|
|
215
|
-
// Check if already synced
|
|
269
|
+
for (const handle of handlesToWaitOn) {
|
|
216
270
|
const heads = handle.heads();
|
|
217
271
|
const syncInfo = handle.getSyncInfo(syncServerStorageId);
|
|
218
272
|
const remoteHeads = syncInfo?.lastHeads;
|
|
219
|
-
|
|
220
|
-
if (wasAlreadySynced) {
|
|
273
|
+
if (A.equals(heads, remoteHeads)) {
|
|
221
274
|
alreadySynced++;
|
|
222
275
|
debug(`waitForSync: ${handle.url.slice(0, 20)}... already synced`);
|
|
223
|
-
return Promise.resolve();
|
|
224
276
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
};
|
|
234
|
-
const onConverged = () => {
|
|
235
|
-
debug(`waitForSync: ${handle.url.slice(0, 20)}... converged in ${Date.now() - startTime}ms`);
|
|
236
|
-
cleanup();
|
|
237
|
-
resolve();
|
|
238
|
-
};
|
|
239
|
-
const timeout = setTimeout(() => {
|
|
240
|
-
debug(`waitForSync: ${handle.url.slice(0, 20)}... timed out after ${timeoutMs}ms`);
|
|
241
|
-
cleanup();
|
|
242
|
-
reject(new Error(`Sync timeout after ${timeoutMs}ms for document ${handle.url}`));
|
|
243
|
-
}, timeoutMs);
|
|
244
|
-
const isConverged = () => {
|
|
245
|
-
const localHeads = handle.heads();
|
|
246
|
-
const info = handle.getSyncInfo(syncServerStorageId);
|
|
247
|
-
return A.equals(localHeads, info?.lastHeads);
|
|
248
|
-
};
|
|
249
|
-
const onRemoteHeads = ({ storageId, }) => {
|
|
250
|
-
if (storageId === syncServerStorageId && isConverged()) {
|
|
251
|
-
onConverged();
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
const poll = () => {
|
|
255
|
-
if (isConverged()) {
|
|
256
|
-
onConverged();
|
|
257
|
-
return true;
|
|
258
|
-
}
|
|
259
|
-
return false;
|
|
260
|
-
};
|
|
261
|
-
// Initial check
|
|
262
|
-
if (poll()) {
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
// Start polling and event listening
|
|
266
|
-
pollInterval = setInterval(() => {
|
|
267
|
-
poll();
|
|
268
|
-
}, 100);
|
|
269
|
-
handle.on("remote-heads", onRemoteHeads);
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
const needSync = handlesToWaitOn.length - alreadySynced;
|
|
273
|
-
if (needSync > 0) {
|
|
274
|
-
debug(`waitForSync: ${alreadySynced} already synced, waiting for ${needSync} remaining`);
|
|
275
|
-
output_1.out.taskLine(`Uploading: ${alreadySynced}/${handlesToWaitOn.length} already synced, waiting for ${needSync} more`);
|
|
277
|
+
else {
|
|
278
|
+
debug(`waitForSync: ${handle.url.slice(0, 20)}... needs sync (remoteHeads=${remoteHeads ? 'present' : 'missing'})`);
|
|
279
|
+
needsSync.push(handle);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (needsSync.length > 0) {
|
|
283
|
+
debug(`waitForSync: ${alreadySynced} already synced, ${needsSync.length} need sync`);
|
|
284
|
+
output_1.out.taskLine(`Uploading: ${alreadySynced}/${handlesToWaitOn.length} already synced, waiting for ${needsSync.length} more`);
|
|
276
285
|
}
|
|
277
286
|
else {
|
|
278
287
|
debug(`waitForSync: all ${handlesToWaitOn.length} already synced`);
|
|
288
|
+
return { failed: [] };
|
|
279
289
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
290
|
+
// Process in batches to avoid flooding the server
|
|
291
|
+
const failed = [];
|
|
292
|
+
let synced = alreadySynced;
|
|
293
|
+
for (let i = 0; i < needsSync.length; i += SYNC_BATCH_SIZE) {
|
|
294
|
+
const batch = needsSync.slice(i, i + SYNC_BATCH_SIZE);
|
|
295
|
+
const batchNum = Math.floor(i / SYNC_BATCH_SIZE) + 1;
|
|
296
|
+
const totalBatches = Math.ceil(needsSync.length / SYNC_BATCH_SIZE);
|
|
297
|
+
if (totalBatches > 1) {
|
|
298
|
+
debug(`waitForSync: batch ${batchNum}/${totalBatches} (${batch.length} docs)`);
|
|
299
|
+
output_1.out.update(`Uploading batch ${batchNum}/${totalBatches} (${synced}/${handlesToWaitOn.length} done)`);
|
|
300
|
+
}
|
|
301
|
+
const results = await Promise.allSettled(batch.map(handle => waitForHandleSync(handle, syncServerStorageId, timeoutMs, startTime)));
|
|
302
|
+
for (const result of results) {
|
|
303
|
+
if (result.status === "rejected") {
|
|
304
|
+
failed.push(result.reason);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
synced++;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const elapsed = Date.now() - startTime;
|
|
312
|
+
if (failed.length > 0) {
|
|
313
|
+
debug(`waitForSync: ${failed.length} documents failed after ${elapsed}ms`);
|
|
314
|
+
output_1.out.taskLine(`Upload: ${synced} synced, ${failed.length} failed after ${(elapsed / 1000).toFixed(1)}s`, true);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
283
317
|
debug(`waitForSync: all ${handlesToWaitOn.length} documents synced in ${elapsed}ms (${alreadySynced} were already synced)`);
|
|
284
318
|
output_1.out.taskLine(`All ${handlesToWaitOn.length} documents uploaded to server (${(elapsed / 1000).toFixed(1)}s)`);
|
|
285
319
|
}
|
|
286
|
-
|
|
287
|
-
const elapsed = Date.now() - startTime;
|
|
288
|
-
debug(`waitForSync: failed after ${elapsed}ms: ${error}`);
|
|
289
|
-
output_1.out.taskLine(`Upload to server failed after ${(elapsed / 1000).toFixed(1)}s: ${error}`, true);
|
|
290
|
-
throw error;
|
|
291
|
-
}
|
|
320
|
+
return { failed };
|
|
292
321
|
}
|
|
293
322
|
//# sourceMappingURL=network-sync.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network-sync.js","sourceRoot":"","sources":["../../src/utils/network-sync.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,
|
|
1
|
+
{"version":3,"file":"network-sync.js","sourceRoot":"","sources":["../../src/utils/network-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo-factory.d.ts","sourceRoot":"","sources":["../../src/utils/repo-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAa,MAAM,2BAA2B,CAAC;AAI5D,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C;;GAEG;AACH,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"repo-factory.d.ts","sourceRoot":"","sources":["../../src/utils/repo-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAa,MAAM,2BAA2B,CAAC;AAI5D,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C;;GAEG;AACH,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
|
|
@@ -49,7 +49,6 @@ async function createRepo(workingDir, config) {
|
|
|
49
49
|
if (config.sync_enabled && config.sync_server) {
|
|
50
50
|
const networkAdapter = new automerge_repo_network_websocket_1.BrowserWebSocketClientAdapter(config.sync_server);
|
|
51
51
|
repoConfig.network = [networkAdapter];
|
|
52
|
-
repoConfig.enableRemoteHeadsGossiping = true;
|
|
53
52
|
}
|
|
54
53
|
const repo = new automerge_repo_1.Repo(repoConfig);
|
|
55
54
|
// Subscribe to the sync server storage for network sync
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo-factory.js","sourceRoot":"","sources":["../../src/utils/repo-factory.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,
|
|
1
|
+
{"version":3,"file":"repo-factory.js","sourceRoot":"","sources":["../../src/utils/repo-factory.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,gCA6BC;AAtCD,8DAA4D;AAC5D,4FAAgF;AAChF,kGAA4F;AAC5F,2CAA6B;AAG7B;;GAEG;AACI,KAAK,UAAU,UAAU,CAC9B,UAAkB,EAClB,MAAuB;IAEvB,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,IAAI,oDAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;IAE9E,MAAM,UAAU,GAAQ,EAAE,OAAO,EAAE,CAAC;IAEpC,uEAAuE;IACvE,IAAI,MAAM,CAAC,YAAY,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QAC9C,MAAM,cAAc,GAAG,IAAI,gEAA6B,CACtD,MAAM,CAAC,WAAW,CACnB,CAAC;QACF,UAAU,CAAC,OAAO,GAAG,CAAC,cAAc,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,qBAAI,CAAC,UAAU,CAAC,CAAC;IAElC,wDAAwD;IACxD,IACE,MAAM,CAAC,YAAY;QACnB,MAAM,CAAC,WAAW;QAClB,MAAM,CAAC,sBAAsB,EAC7B,CAAC;QACD,IAAI,CAAC,kBAAkB,CAAC,CAAC,MAAM,CAAC,sBAAmC,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
clone,
|
|
9
9
|
sync,
|
|
10
10
|
push,
|
|
11
|
+
root,
|
|
11
12
|
diff,
|
|
12
13
|
status,
|
|
13
14
|
log,
|
|
@@ -46,6 +47,24 @@ program
|
|
|
46
47
|
await init(path, { syncServer, syncServerStorageId });
|
|
47
48
|
});
|
|
48
49
|
|
|
50
|
+
// Root command
|
|
51
|
+
program
|
|
52
|
+
.command("root")
|
|
53
|
+
.summary("Set root directory URL without full initialization")
|
|
54
|
+
.argument(
|
|
55
|
+
"<url>",
|
|
56
|
+
"AutomergeUrl of root directory (format: automerge:XXXXX)"
|
|
57
|
+
)
|
|
58
|
+
.argument(
|
|
59
|
+
"[path]",
|
|
60
|
+
"Directory path (default: current directory)",
|
|
61
|
+
"."
|
|
62
|
+
)
|
|
63
|
+
.option("-f, --force", "Overwrite existing pushwork setup", false)
|
|
64
|
+
.action(async (url, path, opts) => {
|
|
65
|
+
await root(url, path, { force: opts.force });
|
|
66
|
+
});
|
|
67
|
+
|
|
49
68
|
// Clone command
|
|
50
69
|
program
|
|
51
70
|
.command("clone")
|
package/src/commands.ts
CHANGED
|
@@ -194,11 +194,10 @@ export async function init(
|
|
|
194
194
|
// This ensures the document is uploaded before we exit
|
|
195
195
|
// waitForSync() verifies the server has the document by comparing local and remote heads
|
|
196
196
|
if (config.sync_enabled && config.sync_server_storage_id) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
out.taskLine(`Network sync failed: ${error}`, true);
|
|
197
|
+
out.update("Syncing to server");
|
|
198
|
+
const { failed } = await waitForSync([rootHandle], config.sync_server_storage_id);
|
|
199
|
+
if (failed.length > 0) {
|
|
200
|
+
out.taskLine("Root document failed to sync to server", true);
|
|
202
201
|
// Continue anyway - the document is created locally and will sync later
|
|
203
202
|
}
|
|
204
203
|
}
|
|
@@ -1026,6 +1025,54 @@ async function runScript(
|
|
|
1026
1025
|
});
|
|
1027
1026
|
}
|
|
1028
1027
|
|
|
1028
|
+
/**
|
|
1029
|
+
* Set root directory URL for an existing or new pushwork directory
|
|
1030
|
+
*/
|
|
1031
|
+
export async function root(
|
|
1032
|
+
rootUrl: string,
|
|
1033
|
+
targetPath: string = ".",
|
|
1034
|
+
options: { force?: boolean } = {}
|
|
1035
|
+
): Promise<void> {
|
|
1036
|
+
if (!rootUrl.startsWith("automerge:")) {
|
|
1037
|
+
out.error(
|
|
1038
|
+
`Invalid Automerge URL: ${rootUrl}\n` +
|
|
1039
|
+
`Expected format: automerge:XXXXX`
|
|
1040
|
+
);
|
|
1041
|
+
out.exit(1);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const resolvedPath = path.resolve(targetPath);
|
|
1045
|
+
const syncToolDir = path.join(resolvedPath, ConfigManager.CONFIG_DIR);
|
|
1046
|
+
|
|
1047
|
+
if (await pathExists(syncToolDir)) {
|
|
1048
|
+
if (!options.force) {
|
|
1049
|
+
out.error("Directory already initialized for pushwork. Use --force to overwrite");
|
|
1050
|
+
out.exit(1);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
await ensureDirectoryExists(syncToolDir);
|
|
1055
|
+
await ensureDirectoryExists(path.join(syncToolDir, "automerge"));
|
|
1056
|
+
|
|
1057
|
+
// Create minimal snapshot with just the root URL
|
|
1058
|
+
const snapshotPath = path.join(syncToolDir, "snapshot.json");
|
|
1059
|
+
const snapshot = {
|
|
1060
|
+
timestamp: Date.now(),
|
|
1061
|
+
rootPath: resolvedPath,
|
|
1062
|
+
rootDirectoryUrl: rootUrl,
|
|
1063
|
+
files: [],
|
|
1064
|
+
directories: [],
|
|
1065
|
+
};
|
|
1066
|
+
await fs.writeFile(snapshotPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
1067
|
+
|
|
1068
|
+
// Ensure config exists
|
|
1069
|
+
const configManager = new ConfigManager(resolvedPath);
|
|
1070
|
+
await configManager.initializeWithOverrides({});
|
|
1071
|
+
|
|
1072
|
+
out.successBlock("ROOT SET", rootUrl);
|
|
1073
|
+
process.exit();
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1029
1076
|
/**
|
|
1030
1077
|
* Push local changes to server without pulling remote changes
|
|
1031
1078
|
*/
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
getPlainUrl,
|
|
18
18
|
readDocContent,
|
|
19
19
|
} from "../utils"
|
|
20
|
-
import {isContentEqual} from "../utils/content"
|
|
20
|
+
import {isContentEqual, contentHash} from "../utils/content"
|
|
21
21
|
import {out} from "../utils/output"
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -27,9 +27,21 @@ export class ChangeDetector {
|
|
|
27
27
|
constructor(
|
|
28
28
|
private repo: Repo,
|
|
29
29
|
private rootPath: string,
|
|
30
|
-
private excludePatterns: string[] = []
|
|
30
|
+
private excludePatterns: string[] = [],
|
|
31
|
+
private artifactDirectories: string[] = []
|
|
31
32
|
) {}
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Check if a file path is inside an artifact directory.
|
|
36
|
+
* Artifact files use RawString and are always replaced wholesale,
|
|
37
|
+
* so we can skip expensive remote content reads for them.
|
|
38
|
+
*/
|
|
39
|
+
private isArtifactPath(filePath: string): boolean {
|
|
40
|
+
return this.artifactDirectories.some(
|
|
41
|
+
dir => filePath === dir || filePath.startsWith(dir + "/")
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
/**
|
|
34
46
|
* Detect all changes between local filesystem and snapshot
|
|
35
47
|
*/
|
|
@@ -78,6 +90,35 @@ export class ChangeDetector {
|
|
|
78
90
|
localContent: fileInfo.content,
|
|
79
91
|
remoteContent: null,
|
|
80
92
|
})
|
|
93
|
+
} else if (this.isArtifactPath(relativePath)) {
|
|
94
|
+
// Artifact files are always replaced wholesale (RawString).
|
|
95
|
+
// Skip remote doc content reads — compare local hash against
|
|
96
|
+
// stored hash to detect local changes, and check heads for remote.
|
|
97
|
+
const localHash = contentHash(fileInfo.content)
|
|
98
|
+
const localChanged = snapshotEntry.contentHash
|
|
99
|
+
? localHash !== snapshotEntry.contentHash
|
|
100
|
+
: true // No stored hash = first sync with hash support, assume changed
|
|
101
|
+
|
|
102
|
+
const remoteHead = await this.getCurrentRemoteHead(
|
|
103
|
+
snapshotEntry.url
|
|
104
|
+
)
|
|
105
|
+
const remoteChanged = !A.equals(remoteHead, snapshotEntry.head)
|
|
106
|
+
|
|
107
|
+
if (localChanged || remoteChanged) {
|
|
108
|
+
changes.push({
|
|
109
|
+
path: relativePath,
|
|
110
|
+
changeType: localChanged && remoteChanged
|
|
111
|
+
? ChangeType.BOTH_CHANGED
|
|
112
|
+
: localChanged
|
|
113
|
+
? ChangeType.LOCAL_ONLY
|
|
114
|
+
: ChangeType.REMOTE_ONLY,
|
|
115
|
+
fileType: fileInfo.type,
|
|
116
|
+
localContent: fileInfo.content,
|
|
117
|
+
remoteContent: null,
|
|
118
|
+
localHead: snapshotEntry.head,
|
|
119
|
+
remoteHead,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
81
122
|
} else {
|
|
82
123
|
// Check if content changed
|
|
83
124
|
const lastKnownContent = await this.getContentAtHead(
|
|
@@ -129,6 +170,27 @@ export class ChangeDetector {
|
|
|
129
170
|
Array.from(snapshot.files.entries())
|
|
130
171
|
.filter(([relativePath]) => !currentFiles.has(relativePath))
|
|
131
172
|
.map(async ([relativePath, snapshotEntry]) => {
|
|
173
|
+
if (this.isArtifactPath(relativePath)) {
|
|
174
|
+
// Artifact deletion: skip remote content read
|
|
175
|
+
const remoteHead = await this.getCurrentRemoteHead(
|
|
176
|
+
snapshotEntry.url
|
|
177
|
+
)
|
|
178
|
+
const remoteChanged = !A.equals(remoteHead, snapshotEntry.head)
|
|
179
|
+
|
|
180
|
+
changes.push({
|
|
181
|
+
path: relativePath,
|
|
182
|
+
changeType: remoteChanged
|
|
183
|
+
? ChangeType.BOTH_CHANGED
|
|
184
|
+
: ChangeType.LOCAL_ONLY,
|
|
185
|
+
fileType: FileType.TEXT,
|
|
186
|
+
localContent: null,
|
|
187
|
+
remoteContent: null,
|
|
188
|
+
localHead: snapshotEntry.head,
|
|
189
|
+
remoteHead,
|
|
190
|
+
})
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
132
194
|
// File was deleted locally
|
|
133
195
|
const currentRemoteContent = await this.getCurrentRemoteContent(
|
|
134
196
|
snapshotEntry.url
|
|
@@ -204,6 +266,23 @@ export class ChangeDetector {
|
|
|
204
266
|
)
|
|
205
267
|
|
|
206
268
|
if (!A.equals(currentRemoteHead, snapshotEntry.head)) {
|
|
269
|
+
if (this.isArtifactPath(relativePath)) {
|
|
270
|
+
// Artifact: skip content reads, just report head change
|
|
271
|
+
const localContent = await this.getLocalContent(relativePath)
|
|
272
|
+
changes.push({
|
|
273
|
+
path: relativePath,
|
|
274
|
+
changeType: localContent !== null
|
|
275
|
+
? ChangeType.BOTH_CHANGED
|
|
276
|
+
: ChangeType.REMOTE_ONLY,
|
|
277
|
+
fileType: FileType.TEXT,
|
|
278
|
+
localContent,
|
|
279
|
+
remoteContent: null,
|
|
280
|
+
localHead: snapshotEntry.head,
|
|
281
|
+
remoteHead: currentRemoteHead,
|
|
282
|
+
})
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
207
286
|
// Remote document has changed
|
|
208
287
|
const currentRemoteContent = await this.getCurrentRemoteContent(
|
|
209
288
|
snapshotEntry.url
|