@indigoai-us/hq-cloud 5.3.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cli/sync.ts CHANGED
@@ -13,7 +13,7 @@ import { downloadFile, listRemoteFiles } from "../s3.js";
13
13
  import { readJournal, writeJournal, hashFile, updateEntry, getEntry } from "../journal.js";
14
14
  import { createIgnoreFilter } from "../ignore.js";
15
15
  import { resolveConflict } from "./conflict.js";
16
- import type { ConflictStrategy } from "./conflict.js";
16
+ import type { ConflictStrategy, ConflictResolution } from "./conflict.js";
17
17
 
18
18
  /**
19
19
  * Per-file events emitted by `sync()` as it progresses.
@@ -28,7 +28,13 @@ import type { ConflictStrategy } from "./conflict.js";
28
28
  */
29
29
  export type SyncProgressEvent =
30
30
  | { type: "progress"; path: string; bytes: number; message?: string }
31
- | { type: "error"; path: string; message: string };
31
+ | { type: "error"; path: string; message: string }
32
+ | {
33
+ type: "conflict";
34
+ path: string;
35
+ direction: "pull" | "push";
36
+ resolution: ConflictResolution;
37
+ };
32
38
 
33
39
  export interface SyncOptions {
34
40
  /** Company slug or UID (defaults to active company from config) */
@@ -66,6 +72,12 @@ export interface SyncResult {
66
72
  bytesDownloaded: number;
67
73
  filesSkipped: number;
68
74
  conflicts: number;
75
+ /**
76
+ * Paths (remote keys) that were detected as conflicts during this run.
77
+ * Always populated when `conflicts > 0` so callers can surface them in UI
78
+ * or logs without re-streaming the per-file events.
79
+ */
80
+ conflictPaths: string[];
69
81
  aborted: boolean;
70
82
  }
71
83
 
@@ -106,6 +118,7 @@ export async function sync(options: SyncOptions): Promise<SyncResult> {
106
118
  let bytesDownloaded = 0;
107
119
  let filesSkipped = 0;
108
120
  let conflicts = 0;
121
+ const conflictPaths: string[] = [];
109
122
 
110
123
  // List all remote files (IAM session policy filters at the AWS layer)
111
124
  const remoteFiles = await listRemoteFiles(ctx);
@@ -138,6 +151,7 @@ export async function sync(options: SyncOptions): Promise<SyncResult> {
138
151
  // If local file has changed since last sync, it's a conflict
139
152
  if (journalEntry && journalEntry.hash !== localHash) {
140
153
  conflicts++;
154
+ conflictPaths.push(remoteFile.key);
141
155
 
142
156
  const resolution = await resolveConflict(
143
157
  {
@@ -150,9 +164,23 @@ export async function sync(options: SyncOptions): Promise<SyncResult> {
150
164
  onConflict,
151
165
  );
152
166
 
167
+ emit({
168
+ type: "conflict",
169
+ path: remoteFile.key,
170
+ direction: "pull",
171
+ resolution,
172
+ });
173
+
153
174
  if (resolution === "abort") {
154
175
  writeJournal(journalSlug, journal);
155
- return { filesDownloaded, bytesDownloaded, filesSkipped, conflicts, aborted: true };
176
+ return {
177
+ filesDownloaded,
178
+ bytesDownloaded,
179
+ filesSkipped,
180
+ conflicts,
181
+ conflictPaths,
182
+ aborted: true,
183
+ };
156
184
  }
157
185
  if (resolution === "keep" || resolution === "skip") {
158
186
  filesSkipped++;
@@ -208,7 +236,14 @@ export async function sync(options: SyncOptions): Promise<SyncResult> {
208
236
 
209
237
  writeJournal(journalSlug, journal);
210
238
 
211
- return { filesDownloaded, bytesDownloaded, filesSkipped, conflicts, aborted: false };
239
+ return {
240
+ filesDownloaded,
241
+ bytesDownloaded,
242
+ filesSkipped,
243
+ conflicts,
244
+ conflictPaths,
245
+ aborted: false,
246
+ };
212
247
  }
213
248
 
214
249
  /**
@@ -251,5 +286,9 @@ function defaultConsoleLogger(event: SyncProgressEvent): void {
251
286
  }
252
287
  } else if (event.type === "error") {
253
288
  console.error(` ✗ ${event.path} — ${event.message}`);
289
+ } else if (event.type === "conflict") {
290
+ console.error(
291
+ ` ⚠ conflict (${event.direction}): ${event.path} — ${event.resolution}`,
292
+ );
254
293
  }
255
294
  }