@indigoai-us/hq-cloud 6.15.26 → 6.15.27
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/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +165 -48
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +83 -13
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +75 -14
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +90 -0
- package/dist/cli/sync.test.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/share.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../../src/cli/share.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAe,YAAY,EAAE,MAAM,aAAa,CAAC;AAkBhG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AA8B7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,uBAAuB,CAAC;AA2E/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,eAAO,MAAM,sBAAsB,QACmD,CAAC;AAEvF;;;;;;;;;GASG;AACH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,QAAqB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAEtF;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;CASpB,CAAC;AAEF;;;;;;;;;GASG;AACH,KAAK,YAAY,GACb;IACE,MAAM,EAAE,QAAQ,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IAOb,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B,GACD;IACE,MAAM,EAAE,QAAQ,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IAIrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,CAAC,CAAC;CACT,GACD;IACE,MAAM,EAAE,iBAAiB,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB,GACD;IACE,MAAM,EAAE,gBAAgB,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IAMrB,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9D,CAAC;AASN;;;;;;;;;GASG;AACH,oDAAoD;AACpD,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAgED;;;;;;;;;;;;;;GAcG;AACH,iBAAS,kBAAkB,CACzB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,YAAY,GAAG,SAAS,GAC9B,OAAO,CAuBT;
|
|
1
|
+
{"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../../src/cli/share.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/share.js
CHANGED
|
@@ -463,6 +463,52 @@ function computePushPlan(filesToShare, journal, skipUnchanged, onHeartbeat) {
|
|
|
463
463
|
}
|
|
464
464
|
return { items, filesToUpload, bytesToUpload, filesToSkip };
|
|
465
465
|
}
|
|
466
|
+
/** Re-read one collected path at the execution seam. */
|
|
467
|
+
function recollectPushEntry(item) {
|
|
468
|
+
let lstat;
|
|
469
|
+
try {
|
|
470
|
+
lstat = fs.lstatSync(item.absolutePath);
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
if (lstat.isSymbolicLink()) {
|
|
476
|
+
const target = readlinkOrNull(item.absolutePath);
|
|
477
|
+
return target === null
|
|
478
|
+
? null
|
|
479
|
+
: {
|
|
480
|
+
kind: "symlink",
|
|
481
|
+
absolutePath: item.absolutePath,
|
|
482
|
+
relativePath: item.relativePath,
|
|
483
|
+
target,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
return lstat.isFile()
|
|
487
|
+
? {
|
|
488
|
+
kind: "file",
|
|
489
|
+
absolutePath: item.absolutePath,
|
|
490
|
+
relativePath: item.relativePath,
|
|
491
|
+
}
|
|
492
|
+
: null;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Turn the whole-run push plan into a preview only. The item returned here was
|
|
496
|
+
* classified from the bytes and journal state that exist immediately before a
|
|
497
|
+
* worker starts, so callers never upload with a hash captured near pass entry.
|
|
498
|
+
*/
|
|
499
|
+
function replanPushItemAtExecution(item, journal, skipUnchanged, onHeartbeat) {
|
|
500
|
+
const current = recollectPushEntry(item);
|
|
501
|
+
if (current === null)
|
|
502
|
+
return null;
|
|
503
|
+
return computePushPlan([current], journal, skipUnchanged, onHeartbeat).items[0] ?? null;
|
|
504
|
+
}
|
|
505
|
+
function uploadBodyMatchesPlan(planned, current) {
|
|
506
|
+
if (planned.kind !== current.kind || planned.localHash !== current.localHash) {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
return planned.kind === "file" ||
|
|
510
|
+
(current.kind === "symlink" && planned.target === current.target);
|
|
511
|
+
}
|
|
466
512
|
/**
|
|
467
513
|
* Thrown by `share()` when a caller-named path cannot be pushed and
|
|
468
514
|
* `unreachablePathPolicy` is `"error"` (the default). Raised while the plans
|
|
@@ -933,50 +979,60 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
|
|
|
933
979
|
(!run.ctx.uid.startsWith("prs_") && run.vaultConfig
|
|
934
980
|
? await fetchCompanyTombstones(run.vaultConfig, run.ctx.uid)
|
|
935
981
|
: new Map());
|
|
936
|
-
const
|
|
937
|
-
|
|
982
|
+
const isUploadSuppressedByTombstone = (item) => {
|
|
983
|
+
if (fileTombstones.size === 0)
|
|
984
|
+
return false;
|
|
985
|
+
const tombstone = fileTombstones.get(toPosixKey(item.relativePath));
|
|
986
|
+
if (tombstone === undefined)
|
|
987
|
+
return false;
|
|
988
|
+
const entry = run.journal.files[item.relativePath];
|
|
989
|
+
return entry !== undefined && entry.hash === item.localHash;
|
|
990
|
+
};
|
|
991
|
+
// Prime from the preview's likely uploads for transport efficiency. The
|
|
992
|
+
// candidate is refreshed after the plan event and tombstone suppression is
|
|
993
|
+
// applied BEFORE any presigns are minted. The queue below still re-plans
|
|
994
|
+
// every preview item before admission, so a later edit cannot disappear from
|
|
995
|
+
// the pass or carry a stale hash into its journal entry.
|
|
996
|
+
const initiallyPlannedUploads = [];
|
|
997
|
+
for (const previewItem of pushPlan.items) {
|
|
998
|
+
if (previewItem.action !== "upload")
|
|
999
|
+
continue;
|
|
1000
|
+
const item = replanPushItemAtExecution(previewItem, run.journal, run.skipUnchanged === true, run.options.onHeartbeat);
|
|
1001
|
+
if (item?.action === "upload" &&
|
|
1002
|
+
!isUploadSuppressedByTombstone(item)) {
|
|
1003
|
+
initiallyPlannedUploads.push(item);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
await primeUploads(run.ctx, initiallyPlannedUploads.map((it) => ({
|
|
1007
|
+
key: it.relativePath,
|
|
1008
|
+
localPath: it.absolutePath,
|
|
1009
|
+
isSymlink: it.kind === "symlink",
|
|
1010
|
+
author: run.options.author,
|
|
1011
|
+
})));
|
|
1012
|
+
// Warm the GET presigns the per-item conflict HEAD (remoteMeta) reuses, so a
|
|
1013
|
+
// large upload set doesn't mint one presign per HEAD and burst/trip the
|
|
1014
|
+
// presign breaker. Mirrors the new-files + tombstone pre-primes on the pull.
|
|
1015
|
+
await primeObjectTransport(run.ctx, "get", initiallyPlannedUploads.map((it) => it.relativePath));
|
|
1016
|
+
let aborted = false;
|
|
1017
|
+
let abortFlightConflictPaths = [];
|
|
1018
|
+
let hadUploadFailure = false;
|
|
1019
|
+
const settleFreshSkip = (item) => {
|
|
938
1020
|
if (item.action === "skip-size-limit") {
|
|
939
1021
|
// A file over the size cap is a permanent, benign skip — NOT an error.
|
|
940
|
-
// Emitting it as `type: "error"` pushed it into the runner's `errors[]`,
|
|
941
|
-
// so every watch pass returned exit 2 and the menubar reported an
|
|
942
|
-
// "auto-sync watcher exited unexpectedly (code=Some(2))" crash on every
|
|
943
|
-
// tick (the HQ-SYNC-4 flood across user machines). Surface it for visibility, but
|
|
944
|
-
// do not let it flip the pass to a non-zero exit.
|
|
945
1022
|
let bytes = 0;
|
|
946
1023
|
try {
|
|
947
1024
|
bytes = fs.statSync(item.absolutePath).size;
|
|
948
1025
|
}
|
|
949
1026
|
catch {
|
|
950
|
-
// Best-effort size for display only; a stat race must not break
|
|
1027
|
+
// Best-effort size for display only; a stat race must not break sync.
|
|
951
1028
|
}
|
|
952
1029
|
run.emit({
|
|
953
1030
|
type: "skip-size-limit",
|
|
954
1031
|
path: item.relativePath,
|
|
955
1032
|
bytes,
|
|
956
1033
|
});
|
|
957
|
-
counters.filesSkipped++;
|
|
958
|
-
continue;
|
|
959
1034
|
}
|
|
960
|
-
if (item.
|
|
961
|
-
if (fileTombstones.size > 0) {
|
|
962
|
-
const ts = fileTombstones.get(toPosixKey(item.relativePath));
|
|
963
|
-
if (ts !== undefined) {
|
|
964
|
-
const entry = run.journal.files[item.relativePath];
|
|
965
|
-
if (entry && entry.hash === item.localHash) {
|
|
966
|
-
counters.filesSuppressedByTombstone++;
|
|
967
|
-
run.emit({
|
|
968
|
-
type: "upload-suppressed-tombstone",
|
|
969
|
-
path: item.relativePath,
|
|
970
|
-
deletedAt: ts.deletedAt,
|
|
971
|
-
});
|
|
972
|
-
continue;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
uploadItems.push(item);
|
|
977
|
-
continue;
|
|
978
|
-
}
|
|
979
|
-
if (item.restamp) {
|
|
1035
|
+
else if (item.restamp) {
|
|
980
1036
|
const existing = run.journal.files[item.relativePath];
|
|
981
1037
|
if (existing && existing.hash) {
|
|
982
1038
|
existing.mtimeMs = item.restamp.mtimeMs;
|
|
@@ -985,24 +1041,49 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
|
|
|
985
1041
|
}
|
|
986
1042
|
}
|
|
987
1043
|
counters.filesSkipped++;
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1044
|
+
};
|
|
1045
|
+
const suppressFreshTombstone = (item) => {
|
|
1046
|
+
if (!isUploadSuppressedByTombstone(item))
|
|
1047
|
+
return false;
|
|
1048
|
+
const tombstone = fileTombstones.get(toPosixKey(item.relativePath));
|
|
1049
|
+
if (tombstone === undefined)
|
|
1050
|
+
return false;
|
|
1051
|
+
counters.filesSuppressedByTombstone++;
|
|
1052
|
+
run.emit({
|
|
1053
|
+
type: "upload-suppressed-tombstone",
|
|
1054
|
+
path: item.relativePath,
|
|
1055
|
+
deletedAt: tombstone.deletedAt,
|
|
1056
|
+
});
|
|
1057
|
+
return true;
|
|
1058
|
+
};
|
|
1059
|
+
const processUploadItem = async (item, replanAttempts = 0) => {
|
|
1003
1060
|
if (aborted)
|
|
1004
1061
|
return;
|
|
1005
1062
|
const { absolutePath, relativePath, localHash } = item;
|
|
1063
|
+
const refreshUploadItem = () => {
|
|
1064
|
+
const refreshed = replanPushItemAtExecution(item, run.journal, run.skipUnchanged === true, run.options.onHeartbeat);
|
|
1065
|
+
if (refreshed === null) {
|
|
1066
|
+
counters.filesSkipped++;
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
if (refreshed.action !== "upload") {
|
|
1070
|
+
settleFreshSkip(refreshed);
|
|
1071
|
+
return null;
|
|
1072
|
+
}
|
|
1073
|
+
if (suppressFreshTombstone(refreshed))
|
|
1074
|
+
return null;
|
|
1075
|
+
return refreshed;
|
|
1076
|
+
};
|
|
1077
|
+
const restartForChangedBody = async (refreshed) => {
|
|
1078
|
+
// An actively-written file must never become a synthetic conflict or a
|
|
1079
|
+
// journal row for bytes we did not upload. Bound retries so a hot log
|
|
1080
|
+
// cannot monopolise a worker; the next sync pass will try it again.
|
|
1081
|
+
if (replanAttempts >= 3) {
|
|
1082
|
+
counters.filesSkipped++;
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
await processUploadItem(refreshed, replanAttempts + 1);
|
|
1086
|
+
};
|
|
1006
1087
|
// Set when `publish-local` was chosen for a FRESH two-sided conflict rather
|
|
1007
1088
|
// than for the `localDiverges` recovery. The two cases need different write
|
|
1008
1089
|
// fences — see the precondition block below.
|
|
@@ -1037,6 +1118,16 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
|
|
|
1037
1118
|
}
|
|
1038
1119
|
throw headErr;
|
|
1039
1120
|
}
|
|
1121
|
+
// HEAD is an await boundary. Re-read the local object afterwards and
|
|
1122
|
+
// restart the whole per-file decision if it moved while the remote check
|
|
1123
|
+
// was in flight. The old HEAD result must not classify the new body.
|
|
1124
|
+
const afterHead = refreshUploadItem();
|
|
1125
|
+
if (afterHead === null)
|
|
1126
|
+
return;
|
|
1127
|
+
if (!uploadBodyMatchesPlan(item, afterHead)) {
|
|
1128
|
+
await restartForChangedBody(afterHead);
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1040
1131
|
// Journal entry for this key (if any). Used both for 3-way conflict
|
|
1041
1132
|
// classification and for the write fence below: Prefer last-synced
|
|
1042
1133
|
// `remoteEtag` over live HEAD so a peer-advanced object cannot be
|
|
@@ -1175,6 +1266,16 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
|
|
|
1175
1266
|
}
|
|
1176
1267
|
if (aborted)
|
|
1177
1268
|
return;
|
|
1269
|
+
// Conflict probes and prompts above are also await boundaries. Make the
|
|
1270
|
+
// PUT inseparable from a final fresh local classification: if the body
|
|
1271
|
+
// changed, discard this decision and begin again with a new HEAD.
|
|
1272
|
+
const beforePut = refreshUploadItem();
|
|
1273
|
+
if (beforePut === null)
|
|
1274
|
+
return;
|
|
1275
|
+
if (!uploadBodyMatchesPlan(item, beforePut)) {
|
|
1276
|
+
await restartForChangedBody(beforePut);
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1178
1279
|
// Write fence: when remote exists, prefer last-synced journal remoteEtag
|
|
1179
1280
|
// over live HEAD so a peer-advanced object fails closed (412 → keep/
|
|
1180
1281
|
// abort/overwrite). Live-HEAD If-Match alone only covers HEAD→PUT TOCTOU.
|
|
@@ -1306,19 +1407,35 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
|
|
|
1306
1407
|
// unless nothing is in flight, so a single file larger than the entire
|
|
1307
1408
|
// budget still makes progress and the pool never deadlocks.
|
|
1308
1409
|
const byteBudget = resolveUploadByteBudget(run.hostLoadAtStart.availableMemFraction);
|
|
1309
|
-
const queue = [...
|
|
1410
|
+
const queue = [...pushPlan.items];
|
|
1310
1411
|
const inFlight = new Set();
|
|
1311
1412
|
let inFlightBytes = 0;
|
|
1312
1413
|
while (queue.length > 0 || inFlight.size > 0) {
|
|
1313
1414
|
while (!aborted && inFlight.size < TRANSFER_CONCURRENCY && queue.length > 0) {
|
|
1415
|
+
const previewItem = queue[0];
|
|
1416
|
+
const item = replanPushItemAtExecution(previewItem, run.journal, run.skipUnchanged === true, run.options.onHeartbeat);
|
|
1417
|
+
if (item === null) {
|
|
1418
|
+
queue.shift();
|
|
1419
|
+
counters.filesSkipped++;
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
if (item.action !== "upload") {
|
|
1423
|
+
queue.shift();
|
|
1424
|
+
settleFreshSkip(item);
|
|
1425
|
+
continue;
|
|
1426
|
+
}
|
|
1427
|
+
if (suppressFreshTombstone(item)) {
|
|
1428
|
+
queue.shift();
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1314
1431
|
// Reserve the file's CURRENT on-disk size, not the planned size: an
|
|
1315
1432
|
// active log/session file can grow between planning and upload, and
|
|
1316
1433
|
// uploadFile buffers the whole current body. A stale planned size would
|
|
1317
1434
|
// let several "small" files all be admitted and then blow the budget.
|
|
1318
|
-
const nextBytes = currentUploadBytes(
|
|
1435
|
+
const nextBytes = currentUploadBytes(item);
|
|
1319
1436
|
if (inFlight.size > 0 && inFlightBytes + nextBytes > byteBudget)
|
|
1320
1437
|
break;
|
|
1321
|
-
|
|
1438
|
+
queue.shift();
|
|
1322
1439
|
inFlightBytes += nextBytes;
|
|
1323
1440
|
const p = processUploadItem(item)
|
|
1324
1441
|
.catch((err) => {
|