@indigoai-us/hq-cloud 6.18.12-alpha.1 → 6.18.12
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/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +0 -1
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner-watch-loop.test.js +0 -2
- package/dist/bin/sync-runner-watch-loop.test.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +22 -12
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +52 -78
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +32 -73
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/rescue-core.d.ts.map +1 -1
- package/dist/cli/rescue-core.js +190 -21
- package/dist/cli/rescue-core.js.map +1 -1
- package/dist/cli/rescue-preserve-copy-symlink.test.js +173 -23
- package/dist/cli/rescue-preserve-copy-symlink.test.js.map +1 -1
- package/dist/cli/sync.test.js +20 -0
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/ignore.d.ts.map +1 -1
- package/dist/ignore.js +18 -0
- package/dist/ignore.js.map +1 -1
- package/dist/ignore.test.js +41 -0
- package/dist/ignore.test.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/exit-codes.d.ts +2 -9
- package/dist/lib/exit-codes.d.ts.map +1 -1
- package/dist/lib/exit-codes.js +2 -9
- package/dist/lib/exit-codes.js.map +1 -1
- package/dist/manifest/build-manifest-resident-parity.test.js +6 -5
- package/dist/manifest/build-manifest-resident-parity.test.js.map +1 -1
- package/dist/operation-lock.d.ts +10 -41
- package/dist/operation-lock.d.ts.map +1 -1
- package/dist/operation-lock.js +48 -107
- package/dist/operation-lock.js.map +1 -1
- package/dist/operation-lock.test.js +116 -205
- package/dist/operation-lock.test.js.map +1 -1
- package/dist/restore-staging.d.ts +9 -0
- package/dist/restore-staging.d.ts.map +1 -0
- package/dist/restore-staging.js +19 -0
- package/dist/restore-staging.js.map +1 -0
- package/dist/sync-progress.d.ts +33 -0
- package/dist/sync-progress.d.ts.map +1 -1
- package/dist/sync-progress.js +81 -0
- package/dist/sync-progress.js.map +1 -1
- package/dist/sync-progress.test.js +80 -1
- package/dist/sync-progress.test.js.map +1 -1
- package/dist/watcher.test.js +9 -0
- package/dist/watcher.test.js.map +1 -1
- package/package.json +1 -1
- package/dist/watch-owner-lease.d.ts +0 -44
- package/dist/watch-owner-lease.d.ts.map +0 -1
- package/dist/watch-owner-lease.js +0 -368
- package/dist/watch-owner-lease.js.map +0 -1
- package/dist/watch-owner-lease.test.d.ts +0 -2
- package/dist/watch-owner-lease.test.d.ts.map +0 -1
- package/dist/watch-owner-lease.test.js +0 -335
- package/dist/watch-owner-lease.test.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rescue-core.d.ts","sourceRoot":"","sources":["../../src/cli/rescue-core.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAO7B,OAAO,EAOL,KAAK,gBAAgB,EAEtB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"rescue-core.d.ts","sourceRoot":"","sources":["../../src/cli/rescue-core.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/rescue-core.js
CHANGED
|
@@ -35,6 +35,7 @@ import * as path from "path";
|
|
|
35
35
|
import yaml from "js-yaml";
|
|
36
36
|
import { assertRescuePathTypeUnchanged, isRescueTransientDirectory, } from "./rescue-snapshot.js";
|
|
37
37
|
import { applyBackupPrune, canonicalRootHash, inventoryManagedSnapshots, isManagedSnapshotManifest, } from "../backup-prune.js";
|
|
38
|
+
import { makeRestoreProbePath, makeRestoreStagePath, } from "../restore-staging.js";
|
|
38
39
|
import { readCoreSourceStamp, writeCoreSourceStamp } from "./core-source-stamp.js";
|
|
39
40
|
// Paths always preserved across wipe+overlay (shuttled out, restored after).
|
|
40
41
|
const CARVE_OUT_PATHS = [
|
|
@@ -1290,7 +1291,7 @@ function recreateSnapshotSymlink(hqRoot, rel, target, destination = snapshotRela
|
|
|
1290
1291
|
function snapshotSymlinkRepresentation(hqRoot, source, destination, rel, target) {
|
|
1291
1292
|
if (process.platform !== "win32")
|
|
1292
1293
|
return undefined;
|
|
1293
|
-
const probe =
|
|
1294
|
+
const probe = makeRestoreProbePath(destination, process.pid, Date.now());
|
|
1294
1295
|
try {
|
|
1295
1296
|
recreateSnapshotSymlink(hqRoot, rel, target, probe);
|
|
1296
1297
|
fs.rmSync(probe, { recursive: true, force: true });
|
|
@@ -1967,6 +1968,7 @@ function doRescue(cfg, env, out, err, setTmp, gitReachabilityProbeTimeoutMs = GI
|
|
|
1967
1968
|
throw new ExitError(overlayPreflight.status || 1);
|
|
1968
1969
|
}
|
|
1969
1970
|
ctx.actions = buildRescueActions(ctx);
|
|
1971
|
+
protectPreservedPathsFromClassifiedActions(ctx);
|
|
1970
1972
|
// Permission preflight is deliberately before snapshot allocation. A
|
|
1971
1973
|
// root-owned release tree (EACCES on rename/delete) used to allocate a
|
|
1972
1974
|
// snapshot, fail apply twice, and then freeze rescue behind the
|
|
@@ -2115,22 +2117,30 @@ function doRescue(cfg, env, out, err, setTmp, gitReachabilityProbeTimeoutMs = GI
|
|
|
2115
2117
|
fs.mkdirSync(shuttle, { recursive: true });
|
|
2116
2118
|
let shuttleId = 0;
|
|
2117
2119
|
for (const sp of cfg.preserveSubpaths) {
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
+
const normalizedSp = sp
|
|
2121
|
+
.replace(/\\/g, "/")
|
|
2122
|
+
.replace(/^\.\//, "")
|
|
2123
|
+
.replace(/\/+$/, "");
|
|
2124
|
+
if (snapshotSkippedEntries.some((entry) => rescuePathsOverlap(normalizedSp, entry.relativePath))) {
|
|
2125
|
+
out(`==> Left ${normalizedSp} in place because it contains a skipped snapshot path\n`);
|
|
2120
2126
|
continue;
|
|
2121
2127
|
}
|
|
2122
|
-
const src = path.join(hqRoot,
|
|
2128
|
+
const src = path.join(hqRoot, normalizedSp);
|
|
2123
2129
|
if (lexists(src)) {
|
|
2124
2130
|
shuttleId += 1;
|
|
2125
2131
|
try {
|
|
2126
2132
|
cpA(src, shuttle, String(shuttleId));
|
|
2127
2133
|
}
|
|
2128
2134
|
catch (error) {
|
|
2129
|
-
reportPreserveBackupFailure(
|
|
2135
|
+
reportPreserveBackupFailure(normalizedSp, error, err);
|
|
2130
2136
|
throw error;
|
|
2131
2137
|
}
|
|
2132
|
-
preserveMap.push({ id: shuttleId, rel:
|
|
2133
|
-
|
|
2138
|
+
preserveMap.push({ id: shuttleId, rel: normalizedSp });
|
|
2139
|
+
// A preserved subtree stays local through the overlay. This avoids
|
|
2140
|
+
// transient rsync writes that would otherwise look like sync changes
|
|
2141
|
+
// before the shuttle is reconciled.
|
|
2142
|
+
appendLiteralRsyncExclude(unchangedList, normalizedSp, lstatOrNull(src)?.isDirectory() ?? false);
|
|
2143
|
+
out(`==> Backed up ${normalizedSp} -> shuttle/${shuttleId}\n`);
|
|
2134
2144
|
}
|
|
2135
2145
|
}
|
|
2136
2146
|
}
|
|
@@ -2200,7 +2210,7 @@ function doRescue(cfg, env, out, err, setTmp, gitReachabilityProbeTimeoutMs = GI
|
|
|
2200
2210
|
const dest = path.join(hqRoot, rel);
|
|
2201
2211
|
const shuttleItem = path.join(shuttle, String(id));
|
|
2202
2212
|
try {
|
|
2203
|
-
|
|
2213
|
+
reconcilePreservedPath(shuttleItem, dest);
|
|
2204
2214
|
out(` restored ${rel}\n`);
|
|
2205
2215
|
}
|
|
2206
2216
|
catch (error) {
|
|
@@ -3643,6 +3653,29 @@ function renderDryRunDecision(ctx, decision) {
|
|
|
3643
3653
|
break;
|
|
3644
3654
|
}
|
|
3645
3655
|
}
|
|
3656
|
+
function protectPreservedPathsFromClassifiedActions(ctx) {
|
|
3657
|
+
const preserved = ctx.cfg.preserveSubpaths.map((rel) => rel.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, ""));
|
|
3658
|
+
const isInsidePreservedPath = (rel) => {
|
|
3659
|
+
const normalized = rel.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
3660
|
+
return preserved.some((preservedRel) => normalized === preservedRel ||
|
|
3661
|
+
normalized.startsWith(`${preservedRel}/`));
|
|
3662
|
+
};
|
|
3663
|
+
ctx.actions = ctx.actions.filter((action) => {
|
|
3664
|
+
const protectedRels = action.affectedRels.filter(isInsidePreservedPath);
|
|
3665
|
+
if (protectedRels.length === 0)
|
|
3666
|
+
return true;
|
|
3667
|
+
// Keep mixed actions intact. A rescue can move a file out of a preserved
|
|
3668
|
+
// subtree into personal/ or .hq-conflicts; the shuttle restores the source
|
|
3669
|
+
// after the overlay while the action's destination remains useful.
|
|
3670
|
+
const actionStaysInsidePreservedPaths = action.affectedRels.length > 0 &&
|
|
3671
|
+
action.affectedRels.every(isInsidePreservedPath) &&
|
|
3672
|
+
action.destParentRels.every(isInsidePreservedPath);
|
|
3673
|
+
if (!actionStaysInsidePreservedPaths)
|
|
3674
|
+
return true;
|
|
3675
|
+
ctx.out(` left in place by --preserve-subpath: ${protectedRels.join(", ")}\n`);
|
|
3676
|
+
return false;
|
|
3677
|
+
});
|
|
3678
|
+
}
|
|
3646
3679
|
function buildRescueActions(ctx) {
|
|
3647
3680
|
const actions = [];
|
|
3648
3681
|
const add = (label, affectedRels, runAction, destParentRels = []) => {
|
|
@@ -4493,28 +4526,121 @@ function confirmedAbsent(p) {
|
|
|
4493
4526
|
throw error;
|
|
4494
4527
|
}
|
|
4495
4528
|
}
|
|
4496
|
-
function restoreStagePath(dest) {
|
|
4529
|
+
function restoreStagePath(dest, stageBase = dest) {
|
|
4497
4530
|
for (let attempt = 0; attempt < 1000; attempt += 1) {
|
|
4498
|
-
const
|
|
4499
|
-
const staged = `${dest}.hq-restore-${process.pid}${suffix}`;
|
|
4531
|
+
const staged = makeRestoreStagePath(stageBase, process.pid, attempt);
|
|
4500
4532
|
if (confirmedAbsent(staged))
|
|
4501
4533
|
return staged;
|
|
4502
4534
|
}
|
|
4503
4535
|
throw new Error(`could not allocate a restore staging sibling for ${dest}`);
|
|
4504
4536
|
}
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4537
|
+
function lstatForRestore(p) {
|
|
4538
|
+
try {
|
|
4539
|
+
return fs.lstatSync(p);
|
|
4540
|
+
}
|
|
4541
|
+
catch (error) {
|
|
4542
|
+
const code = error.code;
|
|
4543
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
4544
|
+
return null;
|
|
4545
|
+
throw error;
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
const PRESERVED_FILE_COMPARE_CHUNK_BYTES = 64 * 1024;
|
|
4549
|
+
function readRestoreChunk(fd, buffer, length, position) {
|
|
4550
|
+
let read = 0;
|
|
4551
|
+
while (read < length) {
|
|
4552
|
+
const bytesRead = fs.readSync(fd, buffer, read, length - read, position + read);
|
|
4553
|
+
if (bytesRead === 0)
|
|
4554
|
+
return false;
|
|
4555
|
+
read += bytesRead;
|
|
4556
|
+
}
|
|
4557
|
+
return true;
|
|
4558
|
+
}
|
|
4559
|
+
function regularFileContentsMatch(source, destination, size) {
|
|
4560
|
+
const sourceFd = fs.openSync(source, "r");
|
|
4561
|
+
let destinationFd;
|
|
4562
|
+
try {
|
|
4563
|
+
destinationFd = fs.openSync(destination, "r");
|
|
4564
|
+
const sourceBuffer = Buffer.allocUnsafe(PRESERVED_FILE_COMPARE_CHUNK_BYTES);
|
|
4565
|
+
const destinationBuffer = Buffer.allocUnsafe(PRESERVED_FILE_COMPARE_CHUNK_BYTES);
|
|
4566
|
+
for (let position = 0; position < size;) {
|
|
4567
|
+
const length = Math.min(PRESERVED_FILE_COMPARE_CHUNK_BYTES, size - position);
|
|
4568
|
+
if (!readRestoreChunk(sourceFd, sourceBuffer, length, position) ||
|
|
4569
|
+
!readRestoreChunk(destinationFd, destinationBuffer, length, position) ||
|
|
4570
|
+
!sourceBuffer.subarray(0, length).equals(destinationBuffer.subarray(0, length))) {
|
|
4571
|
+
return false;
|
|
4572
|
+
}
|
|
4573
|
+
position += length;
|
|
4574
|
+
}
|
|
4575
|
+
return true;
|
|
4576
|
+
}
|
|
4577
|
+
finally {
|
|
4578
|
+
if (destinationFd !== undefined)
|
|
4579
|
+
fs.closeSync(destinationFd);
|
|
4580
|
+
fs.closeSync(sourceFd);
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
function preservedEntriesMatch(source, destination) {
|
|
4584
|
+
const sourceStats = lstatForRestore(source);
|
|
4585
|
+
const destinationStats = lstatForRestore(destination);
|
|
4586
|
+
if (!sourceStats || !destinationStats)
|
|
4587
|
+
return false;
|
|
4588
|
+
if (sourceStats.isSymbolicLink()) {
|
|
4589
|
+
return (destinationStats.isSymbolicLink() &&
|
|
4590
|
+
fs.readlinkSync(source) === fs.readlinkSync(destination));
|
|
4591
|
+
}
|
|
4592
|
+
if (sourceStats.isDirectory()) {
|
|
4593
|
+
if (!destinationStats.isDirectory() || destinationStats.isSymbolicLink()) {
|
|
4594
|
+
return false;
|
|
4595
|
+
}
|
|
4596
|
+
const sourceNames = fs.readdirSync(source).sort();
|
|
4597
|
+
const destinationNames = fs.readdirSync(destination).sort();
|
|
4598
|
+
if (sourceNames.length !== destinationNames.length ||
|
|
4599
|
+
sourceNames.some((name, index) => name !== destinationNames[index])) {
|
|
4600
|
+
return false;
|
|
4601
|
+
}
|
|
4602
|
+
return sourceNames.every((name) => preservedEntriesMatch(path.join(source, name), path.join(destination, name)));
|
|
4603
|
+
}
|
|
4604
|
+
if (!sourceStats.isFile() || !destinationStats.isFile())
|
|
4605
|
+
return false;
|
|
4606
|
+
return (sourceStats.size === destinationStats.size &&
|
|
4607
|
+
(sourceStats.mode & 0o7777) === (destinationStats.mode & 0o7777) &&
|
|
4608
|
+
regularFileContentsMatch(source, destination, sourceStats.size));
|
|
4609
|
+
}
|
|
4610
|
+
function restorePreservedDirectoryMode(destination, stats) {
|
|
4611
|
+
try {
|
|
4612
|
+
if ((lstatForRestore(destination)?.mode ?? stats.mode) !== stats.mode) {
|
|
4613
|
+
fs.chmodSync(destination, stats.mode);
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
catch {
|
|
4617
|
+
// Match cpATo's existing best-effort metadata handling on Windows.
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
function replacePreservedEntry(source, destination) {
|
|
4621
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
4622
|
+
const sourceDevice = fs.statSync(path.dirname(source)).dev;
|
|
4623
|
+
const destinationDevice = fs.statSync(path.dirname(destination)).dev;
|
|
4624
|
+
// Prefer a shuttle-side stage when both roots share a filesystem. If they do
|
|
4625
|
+
// not, stage beside the destination so the final rename remains atomic.
|
|
4626
|
+
// Symlinks stay destination-side because Windows junction targets are
|
|
4627
|
+
// resolved while copying and must be anchored at their final parent.
|
|
4628
|
+
const sourceStats = lstatForRestore(source);
|
|
4629
|
+
const stageBase = sourceStats?.isFile() && sourceDevice === destinationDevice
|
|
4630
|
+
? source
|
|
4631
|
+
: destination;
|
|
4632
|
+
const staged = restoreStagePath(destination, stageBase);
|
|
4508
4633
|
let completed = false;
|
|
4509
4634
|
try {
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4635
|
+
cpATo(source, staged);
|
|
4636
|
+
if (lstatForRestore(destination)) {
|
|
4637
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
4638
|
+
}
|
|
4513
4639
|
try {
|
|
4514
|
-
fs.renameSync(staged,
|
|
4640
|
+
fs.renameSync(staged, destination);
|
|
4515
4641
|
}
|
|
4516
4642
|
catch {
|
|
4517
|
-
cpATo(staged,
|
|
4643
|
+
cpATo(staged, destination);
|
|
4518
4644
|
fs.rmSync(staged, { recursive: true, force: true });
|
|
4519
4645
|
}
|
|
4520
4646
|
completed = true;
|
|
@@ -4530,6 +4656,49 @@ function restorePreservedPath(shuttleItem, dest) {
|
|
|
4530
4656
|
}
|
|
4531
4657
|
}
|
|
4532
4658
|
}
|
|
4659
|
+
function reconcilePreservedEntry(source, destination) {
|
|
4660
|
+
const sourceStats = lstatForRestore(source);
|
|
4661
|
+
if (!sourceStats) {
|
|
4662
|
+
if (lstatForRestore(destination)) {
|
|
4663
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
4664
|
+
}
|
|
4665
|
+
return;
|
|
4666
|
+
}
|
|
4667
|
+
const destinationStats = lstatForRestore(destination);
|
|
4668
|
+
if (sourceStats.isDirectory()) {
|
|
4669
|
+
if (destinationStats &&
|
|
4670
|
+
(!destinationStats.isDirectory() || destinationStats.isSymbolicLink())) {
|
|
4671
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
4672
|
+
}
|
|
4673
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
4674
|
+
const sourceNames = fs.readdirSync(source);
|
|
4675
|
+
const sourceNameSet = new Set(sourceNames);
|
|
4676
|
+
for (const name of sourceNames) {
|
|
4677
|
+
reconcilePreservedEntry(path.join(source, name), path.join(destination, name));
|
|
4678
|
+
}
|
|
4679
|
+
for (const name of fs.readdirSync(destination)) {
|
|
4680
|
+
if (!sourceNameSet.has(name)) {
|
|
4681
|
+
fs.rmSync(path.join(destination, name), { recursive: true, force: true });
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
// Preserve directory permissions, but let its mtime reflect changed
|
|
4685
|
+
// children. Reapplying an old directory timestamp is itself watcher-visible.
|
|
4686
|
+
restorePreservedDirectoryMode(destination, sourceStats);
|
|
4687
|
+
return;
|
|
4688
|
+
}
|
|
4689
|
+
if (preservedEntriesMatch(source, destination))
|
|
4690
|
+
return;
|
|
4691
|
+
replacePreservedEntry(source, destination);
|
|
4692
|
+
}
|
|
4693
|
+
/** Reconcile at file granularity; unchanged entries stay in place. */
|
|
4694
|
+
function reconcilePreservedPath(shuttleItem, dest) {
|
|
4695
|
+
if (!lstatForRestore(shuttleItem)) {
|
|
4696
|
+
throw new Error(`preserved shuttle item is missing: ${shuttleItem}`);
|
|
4697
|
+
}
|
|
4698
|
+
if (preservedEntriesMatch(shuttleItem, dest))
|
|
4699
|
+
return;
|
|
4700
|
+
reconcilePreservedEntry(shuttleItem, dest);
|
|
4701
|
+
}
|
|
4533
4702
|
function preservePathFailureCode(error) {
|
|
4534
4703
|
return error?.code ?? "UNKNOWN";
|
|
4535
4704
|
}
|
|
@@ -4682,14 +4851,14 @@ function rescuePathsOverlap(first, second) {
|
|
|
4682
4851
|
function isSkippedSnapshotPath(ctx, rel) {
|
|
4683
4852
|
return ctx.skippedSnapshotRels.some((skippedRel) => rescuePathsOverlap(rel, skippedRel));
|
|
4684
4853
|
}
|
|
4685
|
-
function appendLiteralRsyncExclude(excludeList, rel) {
|
|
4854
|
+
function appendLiteralRsyncExclude(excludeList, rel, recursive = false) {
|
|
4686
4855
|
let pattern = rel;
|
|
4687
4856
|
if (/[\\*?[]/.test(pattern)) {
|
|
4688
4857
|
pattern = pattern
|
|
4689
4858
|
.replace(/\\/g, "\\\\")
|
|
4690
4859
|
.replace(/([*?[])/g, "\\$1");
|
|
4691
4860
|
}
|
|
4692
|
-
fs.appendFileSync(excludeList, `/${pattern}\0`);
|
|
4861
|
+
fs.appendFileSync(excludeList, `/${pattern}${recursive ? "/***" : ""}\0`);
|
|
4693
4862
|
}
|
|
4694
4863
|
/**
|
|
4695
4864
|
* A skipped snapshot path cannot enter the preserve shuttle because copying it
|