pushwork 1.1.3 → 1.1.5

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-similarity.js","sourceRoot":"","sources":["../../src/utils/content-similarity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Polyfills for browser APIs required by @automerge/automerge-subduction.
3
+ * Must be imported before any subduction code.
4
+ *
5
+ * The Subduction WASM module uses IndexedDB for key persistence
6
+ * (via WebCryptoSigner). In Node.js we provide a fake-indexeddb polyfill.
7
+ */
8
+ import "fake-indexeddb/auto";
9
+ //# sourceMappingURL=node-polyfills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-polyfills.d.ts","sourceRoot":"","sources":["../../src/utils/node-polyfills.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,qBAAqB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Polyfills for browser APIs required by @automerge/automerge-subduction.
3
+ * Must be imported before any subduction code.
4
+ *
5
+ * The Subduction WASM module uses IndexedDB for key persistence
6
+ * (via WebCryptoSigner). In Node.js we provide a fake-indexeddb polyfill.
7
+ */
8
+ import "fake-indexeddb/auto";
9
+ //# sourceMappingURL=node-polyfills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-polyfills.js","sourceRoot":"","sources":["../../src/utils/node-polyfills.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pushwork",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Bidirectional directory synchronization using Automerge CRDTs",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -25,6 +25,11 @@ import {
25
25
  import {isContentEqual, contentHash} from "../utils/content"
26
26
  import {out} from "../utils/output"
27
27
 
28
+ const isDebug = !!process.env.DEBUG
29
+ function debug(...args: any[]) {
30
+ if (isDebug) console.error("[pushwork:change-detection]", ...args)
31
+ }
32
+
28
33
  /**
29
34
  * Change detection engine
30
35
  */
@@ -50,7 +55,7 @@ export class ChangeDetector {
50
55
  /**
51
56
  * Detect all changes between local filesystem and snapshot
52
57
  */
53
- async detectChanges(snapshot: SyncSnapshot): Promise<DetectedChange[]> {
58
+ async detectChanges(snapshot: SyncSnapshot, excludePaths?: Set<string>): Promise<DetectedChange[]> {
54
59
  const changes: DetectedChange[] = []
55
60
 
56
61
  // Get current filesystem state
@@ -65,7 +70,7 @@ export class ChangeDetector {
65
70
  changes.push(...remoteChanges)
66
71
 
67
72
  // Check for new remote documents not in snapshot (critical for clone scenarios)
68
- const newRemoteDocuments = await this.detectNewRemoteDocuments(snapshot)
73
+ const newRemoteDocuments = await this.detectNewRemoteDocuments(snapshot, excludePaths)
69
74
  changes.push(...newRemoteDocuments)
70
75
 
71
76
  return changes
@@ -337,7 +342,8 @@ export class ChangeDetector {
337
342
  * This is critical for clone scenarios where local snapshot is empty
338
343
  */
339
344
  private async detectNewRemoteDocuments(
340
- snapshot: SyncSnapshot
345
+ snapshot: SyncSnapshot,
346
+ excludePaths?: Set<string>
341
347
  ): Promise<DetectedChange[]> {
342
348
  const changes: DetectedChange[] = []
343
349
 
@@ -352,7 +358,8 @@ export class ChangeDetector {
352
358
  snapshot.rootDirectoryUrl,
353
359
  "",
354
360
  snapshot,
355
- changes
361
+ changes,
362
+ excludePaths
356
363
  )
357
364
  } catch (error) {
358
365
  out.taskLine(`Failed to discover remote documents: ${error}`, true)
@@ -368,7 +375,8 @@ export class ChangeDetector {
368
375
  directoryUrl: AutomergeUrl,
369
376
  currentPath: string,
370
377
  snapshot: SyncSnapshot,
371
- changes: DetectedChange[]
378
+ changes: DetectedChange[],
379
+ excludePaths?: Set<string>
372
380
  ): Promise<void> {
373
381
  try {
374
382
  // Find and wait for document to be available (retries on "unavailable")
@@ -387,6 +395,12 @@ export class ChangeDetector {
387
395
  : entry.name
388
396
 
389
397
  if (entry.type === "file") {
398
+ // Skip files that were deliberately deleted during this sync cycle
399
+ if (excludePaths?.has(entryPath)) {
400
+ debug(`skipping deleted path during re-detection: ${entryPath}`)
401
+ continue
402
+ }
403
+
390
404
  // Check if this file is already tracked in the snapshot
391
405
  const existingEntry = snapshot.files.get(entryPath)
392
406
 
@@ -470,7 +484,8 @@ export class ChangeDetector {
470
484
  entry.url,
471
485
  entryPath,
472
486
  snapshot,
473
- changes
487
+ changes,
488
+ excludePaths
474
489
  )
475
490
  }
476
491
  }
@@ -125,7 +125,7 @@ export class SyncEngine {
125
125
  }
126
126
 
127
127
  /**
128
- * Get the appropriate URL for a directory entry.
128
+ * Get the appropriate URL for a file's directory entry.
129
129
  * Artifact paths get versioned URLs (with heads) for exact version fetching.
130
130
  * Non-artifact paths get plain URLs for collaborative editing.
131
131
  */
@@ -136,6 +136,15 @@ export class SyncEngine {
136
136
  return getPlainUrl(handle.url)
137
137
  }
138
138
 
139
+ /**
140
+ * Get the appropriate URL for a subdirectory's directory entry.
141
+ * Always uses plain URLs — versioned URLs on directories can cause
142
+ * issues where consumers see a version without the docs array.
143
+ */
144
+ private getDirEntryUrl(handle: DocHandle<unknown>): AutomergeUrl {
145
+ return getPlainUrl(handle.url)
146
+ }
147
+
139
148
  /**
140
149
  * Set the root directory URL in the snapshot
141
150
  */
@@ -224,12 +233,8 @@ export class SyncEngine {
224
233
  result.errors.push(...commitResult.errors)
225
234
  result.warnings.push(...commitResult.warnings)
226
235
 
227
- // Touch root directory if any changes were made
228
- const hasChanges =
229
- result.filesChanged > 0 || result.directoriesChanged > 0
230
- if (hasChanges) {
231
- await this.touchRootDirectory(snapshot)
232
- }
236
+ // Always touch root directory after commit
237
+ await this.touchRootDirectory(snapshot)
233
238
 
234
239
  // Save updated snapshot
235
240
  await this.snapshotManager.save(snapshot)
@@ -433,6 +438,8 @@ export class SyncEngine {
433
438
  // Detect all changes
434
439
  debug("sync: detecting changes")
435
440
  out.update("Detecting local and remote changes")
441
+ // Capture pre-push snapshot file paths to detect deletions after push
442
+ const prePushFilePaths = new Set(snapshot.files.keys())
436
443
  const changes = await this.changeDetector.detectChanges(snapshot)
437
444
 
438
445
  // Detect moves
@@ -560,8 +567,18 @@ export class SyncEngine {
560
567
  }
561
568
 
562
569
  // Re-detect changes after network sync for fresh state
570
+ // Compute paths deleted during push so they aren't resurrected during pull
571
+ const deletedPaths = new Set<string>()
572
+ for (const p of prePushFilePaths) {
573
+ if (!snapshot.files.has(p)) {
574
+ deletedPaths.add(p)
575
+ }
576
+ }
577
+ if (deletedPaths.size > 0) {
578
+ debug(`sync: excluding ${deletedPaths.size} deleted paths from re-detection`)
579
+ }
563
580
  debug("sync: re-detecting changes after network sync")
564
- const freshChanges = await this.changeDetector.detectChanges(snapshot)
581
+ const freshChanges = await this.changeDetector.detectChanges(snapshot, deletedPaths)
565
582
  const freshRemoteChanges = freshChanges.filter(
566
583
  c =>
567
584
  c.changeType === ChangeType.REMOTE_ONLY ||
@@ -862,7 +879,7 @@ export class SyncEngine {
862
879
  )
863
880
  subdirUpdates.push({
864
881
  name: childName,
865
- url: this.getEntryUrl(childHandle, modifiedDir),
882
+ url: this.getDirEntryUrl(childHandle),
866
883
  })
867
884
  }
868
885
  }
@@ -1362,7 +1379,7 @@ export class SyncEngine {
1362
1379
  this.handlesByPath.set(directoryPath, childDirHandle)
1363
1380
 
1364
1381
  // Get appropriate URL for directory entry
1365
- const entryUrl = this.getEntryUrl(childDirHandle, directoryPath)
1382
+ const entryUrl = this.getDirEntryUrl(childDirHandle)
1366
1383
 
1367
1384
  // Update snapshot with discovered directory
1368
1385
  this.snapshotManager.updateDirectoryEntry(snapshot, directoryPath, {
@@ -1393,7 +1410,7 @@ export class SyncEngine {
1393
1410
  const dirHandle = this.repo.create(dirDoc)
1394
1411
 
1395
1412
  // Get appropriate URL for directory entry
1396
- const dirEntryUrl = this.getEntryUrl(dirHandle, directoryPath)
1413
+ const dirEntryUrl = this.getDirEntryUrl(dirHandle)
1397
1414
 
1398
1415
  // Add this directory to its parent
1399
1416
  // Use plain URL for mutable handle