@indigoai-us/hq-cloud 6.18.49-alpha.7 → 6.18.49

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.
Files changed (63) hide show
  1. package/dist/bin/sync-runner-company.d.ts +3 -5
  2. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  3. package/dist/bin/sync-runner-company.js +13 -22
  4. package/dist/bin/sync-runner-company.js.map +1 -1
  5. package/dist/bin/sync-runner-watch-loop.d.ts +2 -12
  6. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  7. package/dist/bin/sync-runner-watch-loop.js +112 -675
  8. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  9. package/dist/bin/sync-runner.d.ts +5 -27
  10. package/dist/bin/sync-runner.d.ts.map +1 -1
  11. package/dist/bin/sync-runner.js +15 -133
  12. package/dist/bin/sync-runner.js.map +1 -1
  13. package/dist/cli/rescue-core.d.ts +6 -1
  14. package/dist/cli/rescue-core.d.ts.map +1 -1
  15. package/dist/cli/rescue-core.js +217 -31
  16. package/dist/cli/rescue-core.js.map +1 -1
  17. package/dist/cli/share.d.ts +0 -12
  18. package/dist/cli/share.d.ts.map +1 -1
  19. package/dist/cli/share.js +4 -57
  20. package/dist/cli/share.js.map +1 -1
  21. package/dist/lib/cloud-authoritative.d.ts +2 -9
  22. package/dist/lib/cloud-authoritative.d.ts.map +1 -1
  23. package/dist/lib/cloud-authoritative.js +2 -34
  24. package/dist/lib/cloud-authoritative.js.map +1 -1
  25. package/dist/outposts/session-host-command.d.ts +13 -0
  26. package/dist/outposts/session-host-command.d.ts.map +1 -1
  27. package/dist/outposts/session-host-command.js +34 -17
  28. package/dist/outposts/session-host-command.js.map +1 -1
  29. package/dist/outposts/session-host-connection.d.ts +2 -0
  30. package/dist/outposts/session-host-connection.d.ts.map +1 -1
  31. package/dist/outposts/session-host-connection.js +15 -0
  32. package/dist/outposts/session-host-connection.js.map +1 -1
  33. package/dist/outposts/session-host-server.d.ts +4 -0
  34. package/dist/outposts/session-host-server.d.ts.map +1 -1
  35. package/dist/outposts/session-host-server.js +42 -0
  36. package/dist/outposts/session-host-server.js.map +1 -1
  37. package/dist/session-log-capture.d.ts.map +1 -1
  38. package/dist/session-log-capture.js +140 -0
  39. package/dist/session-log-capture.js.map +1 -1
  40. package/dist/symlink-target-policy.d.ts +0 -11
  41. package/dist/symlink-target-policy.d.ts.map +1 -1
  42. package/dist/symlink-target-policy.js +0 -80
  43. package/dist/symlink-target-policy.js.map +1 -1
  44. package/dist/sync/event-sync.d.ts.map +1 -1
  45. package/dist/sync/event-sync.js +12 -0
  46. package/dist/sync/event-sync.js.map +1 -1
  47. package/dist/sync/parallel-sync-coordinator.d.ts +0 -4
  48. package/dist/sync/parallel-sync-coordinator.d.ts.map +1 -1
  49. package/dist/sync/parallel-sync-coordinator.js +14 -155
  50. package/dist/sync/parallel-sync-coordinator.js.map +1 -1
  51. package/dist/sync/push-receiver.d.ts +20 -6
  52. package/dist/sync/push-receiver.d.ts.map +1 -1
  53. package/dist/sync/push-receiver.js +160 -34
  54. package/dist/sync/push-receiver.js.map +1 -1
  55. package/dist/watcher.d.ts +3 -3
  56. package/dist/watcher.d.ts.map +1 -1
  57. package/dist/watcher.js +7 -15
  58. package/dist/watcher.js.map +1 -1
  59. package/package.json +2 -1
  60. package/dist/bin/watcher-catchup-skip-store.d.ts +0 -59
  61. package/dist/bin/watcher-catchup-skip-store.d.ts.map +0 -1
  62. package/dist/bin/watcher-catchup-skip-store.js +0 -278
  63. package/dist/bin/watcher-catchup-skip-store.js.map +0 -1
@@ -30,6 +30,7 @@
30
30
  import { spawnSync } from "child_process";
31
31
  import * as crypto from "crypto";
32
32
  import * as fs from "fs";
33
+ import { createRequire } from "node:module";
33
34
  import * as os from "os";
34
35
  import * as path from "path";
35
36
  import yaml from "js-yaml";
@@ -37,6 +38,40 @@ import { assertRescuePathTypeUnchanged, isRescueTransientDirectory, } from "./re
37
38
  import { applyBackupPrune, canonicalRootHash, inventoryManagedSnapshots, isManagedSnapshotManifest, } from "../backup-prune.js";
38
39
  import { makeRestoreProbePath, makeRestoreStagePath, } from "../restore-staging.js";
39
40
  import { readCoreSourceStamp, writeCoreSourceStamp } from "./core-source-stamp.js";
41
+ const MAX_CLONE_FILES_PER_BATCH = 200;
42
+ const MAX_CLONE_BATCH_PATH_BYTES = 64 * 1024;
43
+ const require = createRequire(import.meta.url);
44
+ function runDarwinClone(sources, destinationDirectory, destinationPaths) {
45
+ // Node/libuv does not implement forced APFS clones on macOS. Use the native
46
+ // clonefile(2) binding, which fails when the volume cannot clone instead of
47
+ // silently consuming the full-copy budget.
48
+ const cloneFileSync = require("rclonefile").cloneFileSync;
49
+ for (const [index, source] of sources.entries()) {
50
+ const destination = destinationPaths?.[index] ??
51
+ path.join(destinationDirectory, path.basename(source));
52
+ cloneFileSync(source, destination);
53
+ }
54
+ }
55
+ function chunkCloneSources(sources, destinationDirectory) {
56
+ const batches = [];
57
+ let batch = [];
58
+ let batchPathBytes = Buffer.byteLength(destinationDirectory) + 8;
59
+ for (const source of sources) {
60
+ const nextBytes = Buffer.byteLength(source) + 1;
61
+ if (batch.length > 0 &&
62
+ (batch.length >= MAX_CLONE_FILES_PER_BATCH ||
63
+ batchPathBytes + nextBytes > MAX_CLONE_BATCH_PATH_BYTES)) {
64
+ batches.push(batch);
65
+ batch = [];
66
+ batchPathBytes = Buffer.byteLength(destinationDirectory) + 8;
67
+ }
68
+ batch.push(source);
69
+ batchPathBytes += nextBytes;
70
+ }
71
+ if (batch.length > 0)
72
+ batches.push(batch);
73
+ return batches;
74
+ }
40
75
  // Paths always preserved across wipe+overlay (shuttled out, restored after).
41
76
  const CARVE_OUT_PATHS = [
42
77
  "core/packages",
@@ -733,7 +768,7 @@ export function runRescue(argv, opts = {}) {
733
768
  tmpdir = d;
734
769
  }, () => {
735
770
  retainTmpdir = true;
736
- }, opts.gitReachabilityProbeTimeoutMs ?? GIT_REACHABILITY_PROBE_TIMEOUT_MS, opts.coreSourceStampWriter, opts.restoreOnRsyncFailure === true, opts.setupPathSettingsLocal === true, opts.backupCapacityStatfsSync ?? fs.statfsSync, opts.backupCapacityCloneProbe ?? probeCopyOnWriteClone, opts.backupCapacityStatSync ?? fs.statSync, opts.pathSepForTest ?? path.sep);
771
+ }, opts.gitReachabilityProbeTimeoutMs ?? GIT_REACHABILITY_PROBE_TIMEOUT_MS, opts.coreSourceStampWriter, opts.restoreOnRsyncFailure === true, opts.setupPathSettingsLocal === true, opts.backupCapacityStatfsSync ?? fs.statfsSync, opts.backupCapacityCloneProbe, opts.backupCapacityStatSync ?? fs.statSync, opts.pathSepForTest ?? path.sep, opts.snapshotClonePlatform ?? process.platform, opts.snapshotCloneRunner ?? runDarwinClone);
737
772
  }
738
773
  catch (e) {
739
774
  if (e instanceof ExitError)
@@ -1536,7 +1571,7 @@ function recoveryProof(unresolved, cfg, hqRoot, srcDir, srcSha, rsyncLauncher, r
1536
1571
  }
1537
1572
  return { eligible: !hasSkippedSnapshotEntries(newest.entry.manifest), failures };
1538
1573
  }
1539
- function doRescue(cfg, env, out, err, setTmp, retainTmp, gitReachabilityProbeTimeoutMs = GIT_REACHABILITY_PROBE_TIMEOUT_MS, stampWriter = writeCoreSourceStamp, restoreOnRsyncFailure = false, setupPathSettingsLocal = false, backupCapacityStatfsSync = fs.statfsSync, backupCapacityCloneProbe = probeCopyOnWriteClone, backupCapacityStatSync = fs.statSync, pathSep = path.sep) {
1574
+ function doRescue(cfg, env, out, err, setTmp, retainTmp, gitReachabilityProbeTimeoutMs = GIT_REACHABILITY_PROBE_TIMEOUT_MS, stampWriter = writeCoreSourceStamp, restoreOnRsyncFailure = false, setupPathSettingsLocal = false, backupCapacityStatfsSync = fs.statfsSync, backupCapacityCloneProbe, backupCapacityStatSync = fs.statSync, pathSep = path.sep, snapshotClonePlatform = process.platform, snapshotCloneRunner = runDarwinClone) {
1540
1575
  const hqRoot = resolveHqRoot(cfg, err);
1541
1576
  const RUN_TS = utcStamp(new Date(), "dash");
1542
1577
  // Both the release overlay and a restorable snapshot require rsync. A shim
@@ -2052,7 +2087,7 @@ function doRescue(cfg, env, out, err, setTmp, retainTmp, gitReachabilityProbeTim
2052
2087
  }
2053
2088
  }
2054
2089
  }
2055
- const snapshotCapacityAvailable = requireBackupCapacity(cfg, hqRoot, snapshotRoots, srcDir, env, err, backupCapacityStatfsSync, backupCapacityCloneProbe, backupCapacityStatSync);
2090
+ const snapshotCapacityAvailable = requireBackupCapacity(cfg, hqRoot, snapshotRoots, srcDir, env, err, backupCapacityStatfsSync, backupCapacityCloneProbe, backupCapacityStatSync, snapshotClonePlatform, snapshotCloneRunner);
2056
2091
  if (!snapshotCapacityAvailable) {
2057
2092
  cfg.doBackup = false;
2058
2093
  out("\nwarning: safety snapshot skipped because available space cannot cover the snapshot allowance; the cloud update continues without a pre-update restore point.\n");
@@ -2076,7 +2111,11 @@ function doRescue(cfg, env, out, err, setTmp, retainTmp, gitReachabilityProbeTim
2076
2111
  if (isRescueTransientDirectory(rootRel))
2077
2112
  continue;
2078
2113
  try {
2079
- const copy = cpAToFiltered(srcAbs, path.join(backupDir, rootRel), rootRel, hqRoot, snapshotSymlinks, snapshotSkippedErrors);
2114
+ const copy = cpAToFiltered(srcAbs, path.join(backupDir, rootRel), rootRel, hqRoot, snapshotSymlinks, snapshotSkippedErrors, {
2115
+ platform: snapshotClonePlatform,
2116
+ runner: snapshotCloneRunner,
2117
+ statfsSync: backupCapacityStatfsSync,
2118
+ });
2080
2119
  if (!copy.captured) {
2081
2120
  const failure = copy.firstFailure ??
2082
2121
  new SnapshotCopyError(rootRel, "metadata", new Error("snapshot root contains no capturable entries"), srcAbs);
@@ -2188,7 +2227,11 @@ function doRescue(cfg, env, out, err, setTmp, retainTmp, gitReachabilityProbeTim
2188
2227
  if (lexists(src)) {
2189
2228
  shuttleId += 1;
2190
2229
  try {
2191
- cpA(src, shuttle, String(shuttleId));
2230
+ cpA(src, shuttle, String(shuttleId), {
2231
+ platform: snapshotClonePlatform,
2232
+ runner: snapshotCloneRunner,
2233
+ statfsSync: backupCapacityStatfsSync,
2234
+ });
2192
2235
  }
2193
2236
  catch (error) {
2194
2237
  reportPreserveBackupFailure(normalizedSp, error, err);
@@ -4650,6 +4693,10 @@ function estimateFiltered(abs, rel) {
4650
4693
  }
4651
4694
  return { files, bytes };
4652
4695
  }
4696
+ function snapshotFreeSpaceReserve(fsStat) {
4697
+ const capacity = Number(fsStat.blocks) * Number(fsStat.bsize);
4698
+ return Math.min(10 * 1024 ** 3, Math.max(5 * 1024 ** 3, Math.ceil(capacity * 0.1)));
4699
+ }
4653
4700
  function estimateShuttleBytes(abs) {
4654
4701
  const st = lstatOrNull(abs);
4655
4702
  if (!st)
@@ -4678,8 +4725,9 @@ function estimateShuttleBytes(abs) {
4678
4725
  .readdirSync(abs)
4679
4726
  .reduce((total, name) => total + estimateShuttleBytes(path.join(abs, name)), 0);
4680
4727
  }
4681
- export function requireBackupCapacity(cfg, hqRoot, roots, srcDir, _env, err, statfsSync = fs.statfsSync, cloneProbe = probeCopyOnWriteClone, statSync = fs.statSync) {
4728
+ export function requireBackupCapacity(cfg, hqRoot, roots, srcDir, _env, err, statfsSync = fs.statfsSync, cloneProbe = undefined, statSync = fs.statSync, platform = process.platform, cloneRunner = runDarwinClone) {
4682
4729
  try {
4730
+ const probe = cloneProbe ?? ((root, backup, capturedRoots) => probeCopyOnWriteClone(root, backup, capturedRoots, platform, cloneRunner, statSync));
4683
4731
  const estimate = roots.reduce((total, root) => total + estimateFiltered(path.join(hqRoot, root), root).bytes, 0);
4684
4732
  const backupLocation = nearestExistingDirectory(cfg.backupRoot);
4685
4733
  const tempLocation = nearestExistingDirectory(os.tmpdir());
@@ -4688,18 +4736,17 @@ export function requireBackupCapacity(cfg, hqRoot, roots, srcDir, _env, err, sta
4688
4736
  const tempDevice = Number(statSync(tempLocation).dev);
4689
4737
  const backupFs = statfsSync(backupLocation);
4690
4738
  const backupFree = Number(backupFs.bavail) * Number(backupFs.bsize);
4691
- const backupCapacity = Number(backupFs.blocks) * Number(backupFs.bsize);
4692
4739
  const sameHqBackupFilesystem = hqDevice === backupDevice;
4693
4740
  const sameBackupTempFilesystem = backupDevice === tempDevice;
4694
4741
  const sameHqTempFilesystem = hqDevice === tempDevice;
4695
4742
  const shuttleBytes = (cfg.preserveSubpaths ?? []).reduce((total, rel) => total + estimateShuttleBytes(path.join(hqRoot, rel)), 0);
4696
- const snapshotCloneSupported = sameHqBackupFilesystem && cloneProbe(hqRoot, cfg.backupRoot, roots);
4743
+ const snapshotCloneSupported = sameHqBackupFilesystem && probe(hqRoot, cfg.backupRoot, roots);
4697
4744
  const shuttleCloneSupported = shuttleBytes > 0 &&
4698
4745
  sameHqTempFilesystem &&
4699
4746
  (sameBackupTempFilesystem && snapshotCloneSupported
4700
4747
  ? true
4701
- : cloneProbe(hqRoot, tempLocation, cfg.preserveSubpaths ?? []));
4702
- const backupMargin = Math.min(10 * 1024 ** 3, Math.max(5 * 1024 ** 3, Math.ceil(backupCapacity * 0.1)));
4748
+ : probe(hqRoot, tempLocation, cfg.preserveSubpaths ?? []));
4749
+ const backupMargin = snapshotFreeSpaceReserve(backupFs);
4703
4750
  const snapshotRequired = snapshotCloneSupported
4704
4751
  ? SNAPSHOT_CLONE_METADATA_ALLOWANCE_BYTES
4705
4752
  : estimate + backupMargin;
@@ -4716,8 +4763,7 @@ export function requireBackupCapacity(cfg, hqRoot, roots, srcDir, _env, err, sta
4716
4763
  if (!sameBackupTempFilesystem && shuttleBytes > 0) {
4717
4764
  const tempFs = statfsSync(tempLocation);
4718
4765
  shuttleFree = Number(tempFs.bavail) * Number(tempFs.bsize);
4719
- const tempCapacity = Number(tempFs.blocks) * Number(tempFs.bsize);
4720
- const tempMargin = Math.min(10 * 1024 ** 3, Math.max(5 * 1024 ** 3, Math.ceil(tempCapacity * 0.1)));
4766
+ const tempMargin = snapshotFreeSpaceReserve(tempFs);
4721
4767
  shuttleRequired = shuttleCloneSupported
4722
4768
  ? SNAPSHOT_CLONE_METADATA_ALLOWANCE_BYTES
4723
4769
  : shuttleBytes + tempMargin;
@@ -4760,12 +4806,7 @@ export function requireBackupCapacity(cfg, hqRoot, roots, srcDir, _env, err, sta
4760
4806
  }
4761
4807
  return true;
4762
4808
  }
4763
- function probeCopyOnWriteClone(hqRoot, backupRoot, roots) {
4764
- const source = roots
4765
- .map((root) => firstRegularFile(path.join(hqRoot, root)))
4766
- .find((candidate) => candidate !== null);
4767
- if (!source)
4768
- return false;
4809
+ function probeCopyOnWriteClone(hqRoot, backupRoot, roots, platform = process.platform, cloneRunner = runDarwinClone, statSync = fs.statSync) {
4769
4810
  const parent = nearestExistingDirectory(backupRoot);
4770
4811
  let probeDir;
4771
4812
  try {
@@ -4777,7 +4818,33 @@ function probeCopyOnWriteClone(hqRoot, backupRoot, roots) {
4777
4818
  throw error;
4778
4819
  }
4779
4820
  try {
4780
- fs.copyFileSync(source, path.join(probeDir, "probe"), fs.constants.COPYFILE_FICLONE_FORCE);
4821
+ if (platform === "darwin") {
4822
+ const hqDevice = Number(statSync(hqRoot).dev);
4823
+ for (let index = 0; index < roots.length; index += 1) {
4824
+ const sample = darwinCloneSampleOnDevice(path.join(hqRoot, roots[index]), hqDevice, statSync);
4825
+ if (!sample.sameDevice)
4826
+ return false;
4827
+ if (!sample.file)
4828
+ continue;
4829
+ const destination = path.join(probeDir, `root-${index}`);
4830
+ fs.mkdirSync(destination, { recursive: true });
4831
+ try {
4832
+ cloneRunner([sample.file], destination);
4833
+ }
4834
+ catch {
4835
+ return false;
4836
+ }
4837
+ }
4838
+ return true;
4839
+ }
4840
+ else {
4841
+ const source = roots
4842
+ .map((root) => firstRegularFile(path.join(hqRoot, root)))
4843
+ .find((candidate) => candidate !== null);
4844
+ if (!source)
4845
+ return false;
4846
+ fs.copyFileSync(source, path.join(probeDir, "probe"), fs.constants.COPYFILE_FICLONE_FORCE);
4847
+ }
4781
4848
  return true;
4782
4849
  }
4783
4850
  catch (error) {
@@ -4798,6 +4865,33 @@ function probeCopyOnWriteClone(hqRoot, backupRoot, roots) {
4798
4865
  fs.rmSync(probeDir, { recursive: true, force: true });
4799
4866
  }
4800
4867
  }
4868
+ function darwinCloneSampleOnDevice(root, hqDevice, statSync) {
4869
+ let sample;
4870
+ const walk = (absolute, relative) => {
4871
+ const lstat = lstatOrNull(absolute);
4872
+ if (!lstat || lstat.isSymbolicLink())
4873
+ return true;
4874
+ if (relative && isRescueTransientDirectory(relative))
4875
+ return true;
4876
+ if (Number(statSync(absolute).dev) !== hqDevice)
4877
+ return false;
4878
+ if (lstat.isFile()) {
4879
+ sample ??= absolute;
4880
+ return true;
4881
+ }
4882
+ if (!lstat.isDirectory())
4883
+ return true;
4884
+ for (const name of fs.readdirSync(absolute)) {
4885
+ const childRelative = path.posix.join(relative, name);
4886
+ if (isRescueTransientDirectory(childRelative))
4887
+ continue;
4888
+ if (!walk(path.join(absolute, name), childRelative))
4889
+ return false;
4890
+ }
4891
+ return true;
4892
+ };
4893
+ return walk(root, "") ? { sameDevice: true, ...(sample ? { file: sample } : {}) } : { sameDevice: false };
4894
+ }
4801
4895
  function firstRegularFile(root, rel = "") {
4802
4896
  const st = lstatOrNull(root);
4803
4897
  if (!st || st.isSymbolicLink())
@@ -4871,11 +4965,11 @@ function countFiles(dir) {
4871
4965
  * source basename into destDir) and the shuttle form (`cp -a "$src"
4872
4966
  * "$SHUTTLE/$id"`, an explicit destination name).
4873
4967
  */
4874
- function cpA(src, destDir, asName) {
4968
+ function cpA(src, destDir, asName, cloneOptions) {
4875
4969
  const dest = asName
4876
4970
  ? path.join(destDir, asName)
4877
4971
  : path.join(destDir, path.basename(src));
4878
- cpATo(src, dest);
4972
+ cpATo(src, dest, cloneOptions);
4879
4973
  }
4880
4974
  /** Copy a link, using the same Windows representation choices as snapshots. */
4881
4975
  function copySymlinkWithWindowsFallback(src, dest, link) {
@@ -4902,7 +4996,7 @@ function copySymlinkWithWindowsFallback(src, dest, link) {
4902
4996
  throw linkPrivilegeError;
4903
4997
  }
4904
4998
  /** `cp -a <src> <dest>` with an explicit destination path. */
4905
- function cpATo(src, dest) {
4999
+ function cpATo(src, dest, cloneOptions) {
4906
5000
  const st = lstatOrNull(src);
4907
5001
  if (!st)
4908
5002
  return;
@@ -4920,7 +5014,7 @@ function cpATo(src, dest) {
4920
5014
  }
4921
5015
  fs.mkdirSync(dest, { recursive: true });
4922
5016
  for (const name of fs.readdirSync(src)) {
4923
- cpATo(path.join(src, name), path.join(dest, name));
5017
+ cpATo(path.join(src, name), path.join(dest, name), cloneOptions);
4924
5018
  }
4925
5019
  try {
4926
5020
  fs.utimesSync(dest, st.atime, st.mtime);
@@ -4935,10 +5029,13 @@ function cpATo(src, dest) {
4935
5029
  if (destinationStats && !destinationStats.isFile()) {
4936
5030
  fs.rmSync(dest, { recursive: true, force: true });
4937
5031
  }
4938
- // The shuttle is on the same filesystem as the HQ root on common installs,
4939
- // so preserve it with a clone where available. Node falls back to a full
4940
- // copy on filesystems without reflink support.
4941
- fs.copyFileSync(src, dest, fs.constants.COPYFILE_FICLONE);
5032
+ if (cloneOptions) {
5033
+ copyRegularFileForSnapshot(src, dest, path.basename(src), cloneOptions);
5034
+ }
5035
+ else {
5036
+ // Restore copies remain independent files and retain their existing path.
5037
+ fs.copyFileSync(src, dest, fs.constants.COPYFILE_FICLONE);
5038
+ }
4942
5039
  try {
4943
5040
  fs.chmodSync(dest, st.mode);
4944
5041
  fs.utimesSync(dest, st.atime, st.mtime);
@@ -5389,7 +5486,25 @@ function validateSnapshotSymlink(hqRoot, rel, target) {
5389
5486
  ? null
5390
5487
  : `target for ${rel} resolves outside the HQ root`;
5391
5488
  }
5392
- function copyRegularFileForSnapshot(src, dest, rel) {
5489
+ function copyRegularFileForSnapshot(src, dest, rel, cloneOptions) {
5490
+ if (cloneOptions?.platform === "darwin") {
5491
+ try {
5492
+ cloneOptions.runner([src], path.dirname(dest), [dest]);
5493
+ return;
5494
+ }
5495
+ catch {
5496
+ try {
5497
+ if (lstatOrNull(dest))
5498
+ fs.rmSync(dest, { force: true });
5499
+ assertFullCopySnapshotCapacity(src, dest, rel, cloneOptions.statfsSync);
5500
+ fs.copyFileSync(src, dest);
5501
+ return;
5502
+ }
5503
+ catch (fallbackError) {
5504
+ throw classifySnapshotCopyFailure(rel, src, dest, fallbackError);
5505
+ }
5506
+ }
5507
+ }
5393
5508
  try {
5394
5509
  // Node falls back to a full copy when the filesystem cannot reflink.
5395
5510
  // Restore paths use ordinary copies, so they remain independent files.
@@ -5408,6 +5523,23 @@ function copyRegularFileForSnapshot(src, dest, rel) {
5408
5523
  throw classifySnapshotCopyFailure(rel, src, dest, error);
5409
5524
  }
5410
5525
  }
5526
+ function assertFullCopySnapshotCapacity(src, dest, rel, statfsSync) {
5527
+ try {
5528
+ const bytes = fs.statSync(src).size;
5529
+ const location = nearestExistingDirectory(path.dirname(dest));
5530
+ const fsStat = statfsSync(location);
5531
+ const available = Number(fsStat.bavail) * Number(fsStat.bsize);
5532
+ const required = bytes + snapshotFreeSpaceReserve(fsStat);
5533
+ if (available < required) {
5534
+ const error = new Error(`insufficient free space before full-copy snapshot fallback (need ${required} bytes, have ${available})`);
5535
+ error.code = "ENOSPC";
5536
+ throw error;
5537
+ }
5538
+ }
5539
+ catch (error) {
5540
+ throw classifySnapshotCopyFailure(rel, src, dest, error);
5541
+ }
5542
+ }
5411
5543
  function classifySnapshotCopyFailure(rel, src, dest, error) {
5412
5544
  if (error.code === "EDEADLK") {
5413
5545
  return new SnapshotCopyError(rel, "read", error, src);
@@ -5443,7 +5575,7 @@ function copySymlinkFileRepresentationForSnapshot(src, dest, rel) {
5443
5575
  preserveSnapshotMetadata(dest, targetStats);
5444
5576
  }
5445
5577
  /** Snapshot copy that preserves metadata while pruning canonical transient directories. */
5446
- function cpAToFiltered(src, dest, rel, hqRoot, symlinks, skippedEntries) {
5578
+ function cpAToFiltered(src, dest, rel, hqRoot, symlinks, skippedEntries, cloneOptions) {
5447
5579
  let st;
5448
5580
  try {
5449
5581
  st = fs.lstatSync(src);
@@ -5489,9 +5621,63 @@ function cpAToFiltered(src, dest, rel, hqRoot, symlinks, skippedEntries) {
5489
5621
  }
5490
5622
  let captured = names.length === 0;
5491
5623
  let firstFailure;
5624
+ const batchClonedNames = new Set();
5625
+ if (cloneOptions.platform === "darwin") {
5626
+ const regularNames = names.filter((name) => {
5627
+ try {
5628
+ const child = lstatOrNull(path.join(src, name));
5629
+ return Boolean(child && !child.isSymbolicLink() && child.isFile());
5630
+ }
5631
+ catch {
5632
+ return false;
5633
+ }
5634
+ });
5635
+ if (regularNames.length > 0) {
5636
+ const sources = regularNames.map((name) => path.join(src, name));
5637
+ for (const batch of chunkCloneSources(sources, dest)) {
5638
+ const destinations = batch.map((source) => path.join(dest, path.basename(source)));
5639
+ let batchCloneFailed = false;
5640
+ try {
5641
+ cloneOptions.runner(batch, dest, destinations);
5642
+ }
5643
+ catch {
5644
+ batchCloneFailed = true;
5645
+ for (const destination of destinations) {
5646
+ fs.rmSync(destination, { force: true });
5647
+ }
5648
+ }
5649
+ for (const [index, source] of batch.entries()) {
5650
+ const destination = destinations[index];
5651
+ const name = path.basename(source);
5652
+ const relative = path.posix.join(rel, name);
5653
+ try {
5654
+ if (batchCloneFailed) {
5655
+ copyRegularFileForSnapshot(source, destination, relative, cloneOptions);
5656
+ }
5657
+ const sourceStats = fs.lstatSync(source);
5658
+ preserveSnapshotMetadata(destination, sourceStats);
5659
+ captured = true;
5660
+ }
5661
+ catch (error) {
5662
+ if (!(error instanceof SnapshotCopyError))
5663
+ throw error;
5664
+ if (error.code === "ENOSPC")
5665
+ throw error;
5666
+ skippedEntries.push(error);
5667
+ firstFailure ??= error;
5668
+ }
5669
+ finally {
5670
+ batchClonedNames.add(name);
5671
+ }
5672
+ }
5673
+ }
5674
+ }
5675
+ }
5492
5676
  for (const name of names) {
5677
+ if (batchClonedNames.has(name))
5678
+ continue;
5493
5679
  try {
5494
- const child = cpAToFiltered(path.join(src, name), path.join(dest, name), path.posix.join(rel, name), hqRoot, symlinks, skippedEntries);
5680
+ const child = cpAToFiltered(path.join(src, name), path.join(dest, name), path.posix.join(rel, name), hqRoot, symlinks, skippedEntries, cloneOptions);
5495
5681
  captured ||= child.captured;
5496
5682
  firstFailure ??= child.firstFailure;
5497
5683
  }
@@ -5524,7 +5710,7 @@ function cpAToFiltered(src, dest, rel, hqRoot, symlinks, skippedEntries) {
5524
5710
  catch (error) {
5525
5711
  throw new SnapshotCopyError(rel, "write", error, dest);
5526
5712
  }
5527
- copyRegularFileForSnapshot(src, dest, rel);
5713
+ copyRegularFileForSnapshot(src, dest, rel, cloneOptions);
5528
5714
  try {
5529
5715
  fs.chmodSync(dest, st.mode);
5530
5716
  fs.utimesSync(dest, st.atime, st.mtime);