@solaqua/gji 0.12.2 → 0.12.4

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 (55) hide show
  1. package/dist/bootstrap-output.d.ts +3 -2
  2. package/dist/bootstrap-output.js +1 -16
  3. package/dist/bootstrap-preview.d.ts +2 -3
  4. package/dist/bootstrap-preview.js +1 -11
  5. package/dist/cli.js +5 -1
  6. package/dist/config.d.ts +3 -13
  7. package/dist/config.js +12 -59
  8. package/dist/dependency-bootstrap.d.ts +11 -45
  9. package/dist/dependency-bootstrap.js +142 -608
  10. package/dist/gji-bundle.mjs +580 -2360
  11. package/dist/new.d.ts +4 -5
  12. package/dist/new.js +10 -45
  13. package/dist/pr.d.ts +4 -5
  14. package/dist/pr.js +9 -69
  15. package/dist/shell-completion.js +7 -5
  16. package/dist/uv-validation.d.ts +4 -0
  17. package/dist/uv-validation.js +232 -0
  18. package/dist/worktree-bootstrap.d.ts +5 -16
  19. package/dist/worktree-bootstrap.js +1 -24
  20. package/man/man1/gji-back.1 +1 -1
  21. package/man/man1/gji-clean.1 +1 -1
  22. package/man/man1/gji-completion.1 +1 -1
  23. package/man/man1/gji-config.1 +1 -1
  24. package/man/man1/gji-doctor.1 +1 -1
  25. package/man/man1/gji-done.1 +1 -1
  26. package/man/man1/gji-go.1 +1 -1
  27. package/man/man1/gji-history.1 +1 -1
  28. package/man/man1/gji-init.1 +1 -1
  29. package/man/man1/gji-ls.1 +1 -1
  30. package/man/man1/gji-new.1 +8 -5
  31. package/man/man1/gji-open.1 +1 -1
  32. package/man/man1/gji-pr.1 +6 -3
  33. package/man/man1/gji-remove.1 +1 -1
  34. package/man/man1/gji-root.1 +1 -1
  35. package/man/man1/gji-run-hook.1 +1 -1
  36. package/man/man1/gji-status.1 +1 -1
  37. package/man/man1/gji-sync-files.1 +1 -1
  38. package/man/man1/gji-sync.1 +1 -1
  39. package/man/man1/gji-task.1 +1 -1
  40. package/man/man1/gji-undo.1 +1 -1
  41. package/man/man1/gji-warp.1 +1 -1
  42. package/man/man1/gji.1 +2 -2
  43. package/package.json +1 -1
  44. package/dist/dependency-bootstrap-prompt.d.ts +0 -24
  45. package/dist/dependency-bootstrap-prompt.js +0 -103
  46. package/dist/dir-clone.d.ts +0 -32
  47. package/dist/dir-clone.js +0 -641
  48. package/dist/install-prompt.d.ts +0 -12
  49. package/dist/install-prompt.js +0 -127
  50. package/dist/package-manager.d.ts +0 -5
  51. package/dist/package-manager.js +0 -159
  52. package/dist/sync-directories.d.ts +0 -29
  53. package/dist/sync-directories.js +0 -77
  54. package/dist/sync-plan.d.ts +0 -16
  55. package/dist/sync-plan.js +0 -128
@@ -3462,7 +3462,7 @@ var require_commander = __commonJS({
3462
3462
  // node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/polyfills.js
3463
3463
  var require_polyfills = __commonJS({
3464
3464
  "node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/polyfills.js"(exports, module) {
3465
- var constants10 = __require("constants");
3465
+ var constants9 = __require("constants");
3466
3466
  var origCwd = process.cwd;
3467
3467
  var cwd = null;
3468
3468
  var platform3 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
@@ -3486,7 +3486,7 @@ var require_polyfills = __commonJS({
3486
3486
  var chdir;
3487
3487
  module.exports = patch;
3488
3488
  function patch(fs5) {
3489
- if (constants10.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
3489
+ if (constants9.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
3490
3490
  patchLchmod(fs5);
3491
3491
  }
3492
3492
  if (!fs5.lutimes) {
@@ -3526,7 +3526,7 @@ var require_polyfills = __commonJS({
3526
3526
  }
3527
3527
  if (platform3 === "win32") {
3528
3528
  fs5.rename = typeof fs5.rename !== "function" ? fs5.rename : (function(fs$rename) {
3529
- function rename4(from, to, cb) {
3529
+ function rename3(from, to, cb) {
3530
3530
  var start = Date.now();
3531
3531
  var backoff = 0;
3532
3532
  fs$rename(from, to, function CB(er) {
@@ -3546,8 +3546,8 @@ var require_polyfills = __commonJS({
3546
3546
  if (cb) cb(er);
3547
3547
  });
3548
3548
  }
3549
- if (Object.setPrototypeOf) Object.setPrototypeOf(rename4, fs$rename);
3550
- return rename4;
3549
+ if (Object.setPrototypeOf) Object.setPrototypeOf(rename3, fs$rename);
3550
+ return rename3;
3551
3551
  })(fs5.rename);
3552
3552
  }
3553
3553
  fs5.read = typeof fs5.read !== "function" ? fs5.read : (function(fs$read) {
@@ -3588,7 +3588,7 @@ var require_polyfills = __commonJS({
3588
3588
  fs6.lchmod = function(path9, mode, callback) {
3589
3589
  fs6.open(
3590
3590
  path9,
3591
- constants10.O_WRONLY | constants10.O_SYMLINK,
3591
+ constants9.O_WRONLY | constants9.O_SYMLINK,
3592
3592
  mode,
3593
3593
  function(err, fd) {
3594
3594
  if (err) {
@@ -3604,7 +3604,7 @@ var require_polyfills = __commonJS({
3604
3604
  );
3605
3605
  };
3606
3606
  fs6.lchmodSync = function(path9, mode) {
3607
- var fd = fs6.openSync(path9, constants10.O_WRONLY | constants10.O_SYMLINK, mode);
3607
+ var fd = fs6.openSync(path9, constants9.O_WRONLY | constants9.O_SYMLINK, mode);
3608
3608
  var threw = true;
3609
3609
  var ret;
3610
3610
  try {
@@ -3624,9 +3624,9 @@ var require_polyfills = __commonJS({
3624
3624
  };
3625
3625
  }
3626
3626
  function patchLutimes(fs6) {
3627
- if (constants10.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
3627
+ if (constants9.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
3628
3628
  fs6.lutimes = function(path9, at, mt, cb) {
3629
- fs6.open(path9, constants10.O_SYMLINK, function(er, fd) {
3629
+ fs6.open(path9, constants9.O_SYMLINK, function(er, fd) {
3630
3630
  if (er) {
3631
3631
  if (cb) cb(er);
3632
3632
  return;
@@ -3639,7 +3639,7 @@ var require_polyfills = __commonJS({
3639
3639
  });
3640
3640
  };
3641
3641
  fs6.lutimesSync = function(path9, at, mt) {
3642
- var fd = fs6.openSync(path9, constants10.O_SYMLINK);
3642
+ var fd = fs6.openSync(path9, constants9.O_SYMLINK);
3643
3643
  var ret;
3644
3644
  var threw = true;
3645
3645
  try {
@@ -3951,8 +3951,8 @@ var require_graceful_fs = __commonJS({
3951
3951
  fs6.createReadStream = createReadStream;
3952
3952
  fs6.createWriteStream = createWriteStream;
3953
3953
  var fs$readFile = fs6.readFile;
3954
- fs6.readFile = readFile11;
3955
- function readFile11(path9, options, cb) {
3954
+ fs6.readFile = readFile10;
3955
+ function readFile10(path9, options, cb) {
3956
3956
  if (typeof options === "function")
3957
3957
  cb = options, options = null;
3958
3958
  return go$readFile(path9, options, cb);
@@ -3968,8 +3968,8 @@ var require_graceful_fs = __commonJS({
3968
3968
  }
3969
3969
  }
3970
3970
  var fs$writeFile = fs6.writeFile;
3971
- fs6.writeFile = writeFile11;
3972
- function writeFile11(path9, data, options, cb) {
3971
+ fs6.writeFile = writeFile10;
3972
+ function writeFile10(path9, data, options, cb) {
3973
3973
  if (typeof options === "function")
3974
3974
  cb = options, options = null;
3975
3975
  return go$writeFile(path9, data, options, cb);
@@ -4023,9 +4023,9 @@ var require_graceful_fs = __commonJS({
4023
4023
  }
4024
4024
  }
4025
4025
  var fs$readdir = fs6.readdir;
4026
- fs6.readdir = readdir4;
4026
+ fs6.readdir = readdir2;
4027
4027
  var noReaddirOptionVersions = /^v[0-5]\./;
4028
- function readdir4(path9, options, cb) {
4028
+ function readdir2(path9, options, cb) {
4029
4029
  if (typeof options === "function")
4030
4030
  cb = options, options = null;
4031
4031
  var go$readdir = noReaddirOptionVersions.test(process.version) ? function go$readdir2(path10, options2, cb2, startTime) {
@@ -5386,7 +5386,7 @@ var require_minimist = __commonJS({
5386
5386
  var require_rc = __commonJS({
5387
5387
  "node_modules/.pnpm/rc@1.2.8/node_modules/rc/index.js"(exports, module) {
5388
5388
  var cc = require_utils();
5389
- var join18 = __require("path").join;
5389
+ var join16 = __require("path").join;
5390
5390
  var deepExtend = require_deep_extend();
5391
5391
  var etc = "/etc";
5392
5392
  var win = process.platform === "win32";
@@ -5411,15 +5411,15 @@ var require_rc = __commonJS({
5411
5411
  }
5412
5412
  if (!win)
5413
5413
  [
5414
- join18(etc, name, "config"),
5415
- join18(etc, name + "rc")
5414
+ join16(etc, name, "config"),
5415
+ join16(etc, name + "rc")
5416
5416
  ].forEach(addConfigFile);
5417
5417
  if (home)
5418
5418
  [
5419
- join18(home, ".config", name, "config"),
5420
- join18(home, ".config", name),
5421
- join18(home, "." + name, "config"),
5422
- join18(home, "." + name + "rc")
5419
+ join16(home, ".config", name, "config"),
5420
+ join16(home, ".config", name),
5421
+ join16(home, "." + name, "config"),
5422
+ join16(home, "." + name + "rc")
5423
5423
  ].forEach(addConfigFile);
5424
5424
  addConfigFile(cc.find("." + name + "rc"));
5425
5425
  if (env5.config) addConfigFile(env5.config);
@@ -5436,7 +5436,7 @@ var require_rc = __commonJS({
5436
5436
  // node_modules/.pnpm/graceful-fs@4.2.10/node_modules/graceful-fs/polyfills.js
5437
5437
  var require_polyfills2 = __commonJS({
5438
5438
  "node_modules/.pnpm/graceful-fs@4.2.10/node_modules/graceful-fs/polyfills.js"(exports, module) {
5439
- var constants10 = __require("constants");
5439
+ var constants9 = __require("constants");
5440
5440
  var origCwd = process.cwd;
5441
5441
  var cwd = null;
5442
5442
  var platform3 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
@@ -5460,7 +5460,7 @@ var require_polyfills2 = __commonJS({
5460
5460
  var chdir;
5461
5461
  module.exports = patch;
5462
5462
  function patch(fs5) {
5463
- if (constants10.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
5463
+ if (constants9.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
5464
5464
  patchLchmod(fs5);
5465
5465
  }
5466
5466
  if (!fs5.lutimes) {
@@ -5500,7 +5500,7 @@ var require_polyfills2 = __commonJS({
5500
5500
  }
5501
5501
  if (platform3 === "win32") {
5502
5502
  fs5.rename = typeof fs5.rename !== "function" ? fs5.rename : (function(fs$rename) {
5503
- function rename4(from, to, cb) {
5503
+ function rename3(from, to, cb) {
5504
5504
  var start = Date.now();
5505
5505
  var backoff = 0;
5506
5506
  fs$rename(from, to, function CB(er) {
@@ -5520,8 +5520,8 @@ var require_polyfills2 = __commonJS({
5520
5520
  if (cb) cb(er);
5521
5521
  });
5522
5522
  }
5523
- if (Object.setPrototypeOf) Object.setPrototypeOf(rename4, fs$rename);
5524
- return rename4;
5523
+ if (Object.setPrototypeOf) Object.setPrototypeOf(rename3, fs$rename);
5524
+ return rename3;
5525
5525
  })(fs5.rename);
5526
5526
  }
5527
5527
  fs5.read = typeof fs5.read !== "function" ? fs5.read : (function(fs$read) {
@@ -5562,7 +5562,7 @@ var require_polyfills2 = __commonJS({
5562
5562
  fs6.lchmod = function(path9, mode, callback) {
5563
5563
  fs6.open(
5564
5564
  path9,
5565
- constants10.O_WRONLY | constants10.O_SYMLINK,
5565
+ constants9.O_WRONLY | constants9.O_SYMLINK,
5566
5566
  mode,
5567
5567
  function(err, fd) {
5568
5568
  if (err) {
@@ -5578,7 +5578,7 @@ var require_polyfills2 = __commonJS({
5578
5578
  );
5579
5579
  };
5580
5580
  fs6.lchmodSync = function(path9, mode) {
5581
- var fd = fs6.openSync(path9, constants10.O_WRONLY | constants10.O_SYMLINK, mode);
5581
+ var fd = fs6.openSync(path9, constants9.O_WRONLY | constants9.O_SYMLINK, mode);
5582
5582
  var threw = true;
5583
5583
  var ret;
5584
5584
  try {
@@ -5598,9 +5598,9 @@ var require_polyfills2 = __commonJS({
5598
5598
  };
5599
5599
  }
5600
5600
  function patchLutimes(fs6) {
5601
- if (constants10.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
5601
+ if (constants9.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
5602
5602
  fs6.lutimes = function(path9, at, mt, cb) {
5603
- fs6.open(path9, constants10.O_SYMLINK, function(er, fd) {
5603
+ fs6.open(path9, constants9.O_SYMLINK, function(er, fd) {
5604
5604
  if (er) {
5605
5605
  if (cb) cb(er);
5606
5606
  return;
@@ -5613,7 +5613,7 @@ var require_polyfills2 = __commonJS({
5613
5613
  });
5614
5614
  };
5615
5615
  fs6.lutimesSync = function(path9, at, mt) {
5616
- var fd = fs6.openSync(path9, constants10.O_SYMLINK);
5616
+ var fd = fs6.openSync(path9, constants9.O_SYMLINK);
5617
5617
  var ret;
5618
5618
  var threw = true;
5619
5619
  try {
@@ -5925,8 +5925,8 @@ var require_graceful_fs2 = __commonJS({
5925
5925
  fs6.createReadStream = createReadStream;
5926
5926
  fs6.createWriteStream = createWriteStream;
5927
5927
  var fs$readFile = fs6.readFile;
5928
- fs6.readFile = readFile11;
5929
- function readFile11(path9, options, cb) {
5928
+ fs6.readFile = readFile10;
5929
+ function readFile10(path9, options, cb) {
5930
5930
  if (typeof options === "function")
5931
5931
  cb = options, options = null;
5932
5932
  return go$readFile(path9, options, cb);
@@ -5942,8 +5942,8 @@ var require_graceful_fs2 = __commonJS({
5942
5942
  }
5943
5943
  }
5944
5944
  var fs$writeFile = fs6.writeFile;
5945
- fs6.writeFile = writeFile11;
5946
- function writeFile11(path9, data, options, cb) {
5945
+ fs6.writeFile = writeFile10;
5946
+ function writeFile10(path9, data, options, cb) {
5947
5947
  if (typeof options === "function")
5948
5948
  cb = options, options = null;
5949
5949
  return go$writeFile(path9, data, options, cb);
@@ -5997,9 +5997,9 @@ var require_graceful_fs2 = __commonJS({
5997
5997
  }
5998
5998
  }
5999
5999
  var fs$readdir = fs6.readdir;
6000
- fs6.readdir = readdir4;
6000
+ fs6.readdir = readdir2;
6001
6001
  var noReaddirOptionVersions = /^v[0-5]\./;
6002
- function readdir4(path9, options, cb) {
6002
+ function readdir2(path9, options, cb) {
6003
6003
  if (typeof options === "function")
6004
6004
  cb = options, options = null;
6005
6005
  var go$readdir = noReaddirOptionVersions.test(process.version) ? function go$readdir2(path10, options2, cb2, startTime) {
@@ -6605,13 +6605,13 @@ var require_envKeyToSetting = __commonJS({
6605
6605
  module.exports = function(x2) {
6606
6606
  const colonIndex = x2.indexOf(":");
6607
6607
  if (colonIndex === -1) {
6608
- return normalize3(x2);
6608
+ return normalize2(x2);
6609
6609
  }
6610
6610
  const firstPart = x2.substr(0, colonIndex);
6611
6611
  const secondPart = x2.substr(colonIndex + 1);
6612
- return `${firstPart}:${normalize3(secondPart)}`;
6612
+ return `${firstPart}:${normalize2(secondPart)}`;
6613
6613
  };
6614
- function normalize3(s) {
6614
+ function normalize2(s) {
6615
6615
  s = s.toLowerCase();
6616
6616
  if (s === "_authtoken") return "_authToken";
6617
6617
  let r2 = s[0];
@@ -6757,11 +6757,11 @@ var require_util = __commonJS({
6757
6757
  if (files.includes("node_modules") || files.includes("package.json") || files.includes("package.json5") || files.includes("package.yaml") || files.includes("pnpm-workspace.yaml")) {
6758
6758
  return name2;
6759
6759
  }
6760
- const dirname14 = path9.dirname(name2);
6761
- if (dirname14 === name2) {
6760
+ const dirname13 = path9.dirname(name2);
6761
+ if (dirname13 === name2) {
6762
6762
  return original;
6763
6763
  }
6764
- return find(dirname14, original);
6764
+ return find(dirname13, original);
6765
6765
  } catch (error) {
6766
6766
  if (name2 === original) {
6767
6767
  if (error.code === "ENOENT") {
@@ -8729,7 +8729,7 @@ var require_semver2 = __commonJS({
8729
8729
  "node_modules/.pnpm/semver@7.7.4/node_modules/semver/index.js"(exports, module) {
8730
8730
  "use strict";
8731
8731
  var internalRe = require_re();
8732
- var constants10 = require_constants();
8732
+ var constants9 = require_constants();
8733
8733
  var SemVer = require_semver();
8734
8734
  var identifiers = require_identifiers();
8735
8735
  var parse = require_parse();
@@ -8811,8 +8811,8 @@ var require_semver2 = __commonJS({
8811
8811
  re: internalRe.re,
8812
8812
  src: internalRe.src,
8813
8813
  tokens: internalRe.t,
8814
- SEMVER_SPEC_VERSION: constants10.SEMVER_SPEC_VERSION,
8815
- RELEASE_TYPES: constants10.RELEASE_TYPES,
8814
+ SEMVER_SPEC_VERSION: constants9.SEMVER_SPEC_VERSION,
8815
+ RELEASE_TYPES: constants9.RELEASE_TYPES,
8816
8816
  compareIdentifiers: identifiers.compareIdentifiers,
8817
8817
  rcompareIdentifiers: identifiers.rcompareIdentifiers
8818
8818
  };
@@ -9522,7 +9522,7 @@ var retryifyAsync = (fn, options) => {
9522
9522
  throw error;
9523
9523
  const delay2 = Math.round(interval * Math.random());
9524
9524
  if (delay2 > 0) {
9525
- const delayPromise = new Promise((resolve13) => setTimeout(resolve13, delay2));
9525
+ const delayPromise = new Promise((resolve12) => setTimeout(resolve12, delay2));
9526
9526
  return delayPromise.then(() => attempt.apply(void 0, args));
9527
9527
  } else {
9528
9528
  return attempt.apply(void 0, args);
@@ -9781,14 +9781,14 @@ var Temp = {
9781
9781
  }
9782
9782
  },
9783
9783
  truncate: (filePath) => {
9784
- const basename13 = path2.basename(filePath);
9785
- if (basename13.length <= LIMIT_BASENAME_LENGTH)
9784
+ const basename12 = path2.basename(filePath);
9785
+ if (basename12.length <= LIMIT_BASENAME_LENGTH)
9786
9786
  return filePath;
9787
- const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename13);
9787
+ const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename12);
9788
9788
  if (!truncable)
9789
9789
  return filePath;
9790
- const truncationLength = basename13.length - LIMIT_BASENAME_LENGTH;
9791
- return `${filePath.slice(0, -basename13.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
9790
+ const truncationLength = basename12.length - LIMIT_BASENAME_LENGTH;
9791
+ return `${filePath.slice(0, -basename12.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
9792
9792
  }
9793
9793
  };
9794
9794
  node_default(Temp.purgeSyncAll);
@@ -11088,14 +11088,14 @@ var TimeoutError = class extends Error {
11088
11088
 
11089
11089
  // node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/timeout.js
11090
11090
  async function timeout(request, init, abortController, options) {
11091
- return new Promise((resolve13, reject) => {
11091
+ return new Promise((resolve12, reject) => {
11092
11092
  const timeoutId = setTimeout(() => {
11093
11093
  if (abortController) {
11094
11094
  abortController.abort();
11095
11095
  }
11096
11096
  reject(new TimeoutError(request));
11097
11097
  }, options.timeout);
11098
- void options.fetch(request, init).then(resolve13).catch(reject).then(() => {
11098
+ void options.fetch(request, init).then(resolve12).catch(reject).then(() => {
11099
11099
  clearTimeout(timeoutId);
11100
11100
  });
11101
11101
  });
@@ -11103,7 +11103,7 @@ async function timeout(request, init, abortController, options) {
11103
11103
 
11104
11104
  // node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/delay.js
11105
11105
  async function delay(ms, { signal }) {
11106
- return new Promise((resolve13, reject) => {
11106
+ return new Promise((resolve12, reject) => {
11107
11107
  if (signal) {
11108
11108
  signal.throwIfAborted();
11109
11109
  signal.addEventListener("abort", abortHandler, { once: true });
@@ -11114,7 +11114,7 @@ async function delay(ms, { signal }) {
11114
11114
  }
11115
11115
  const timeoutId = setTimeout(() => {
11116
11116
  signal?.removeEventListener("abort", abortHandler);
11117
- resolve13();
11117
+ resolve12();
11118
11118
  }, ms);
11119
11119
  });
11120
11120
  }
@@ -12817,14 +12817,7 @@ import { basename as basename2 } from "node:path";
12817
12817
  // src/config.ts
12818
12818
  import { mkdir, readFile, writeFile } from "node:fs/promises";
12819
12819
  import { homedir } from "node:os";
12820
- import {
12821
- dirname,
12822
- isAbsolute,
12823
- join,
12824
- normalize,
12825
- resolve,
12826
- win32
12827
- } from "node:path";
12820
+ import { dirname, join, resolve } from "node:path";
12828
12821
  var CONFIG_FILE_NAME = ".gji.json";
12829
12822
  var GLOBAL_CONFIG_DIRECTORY = ".config/gji";
12830
12823
  var GLOBAL_CONFIG_NAME = "config.json";
@@ -12836,8 +12829,6 @@ var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set([
12836
12829
  "hooks",
12837
12830
  "installSaveTarget",
12838
12831
  "shellIntegration",
12839
- "skipInstallPrompt",
12840
- "syncDirs",
12841
12832
  "syncDefaultBranch",
12842
12833
  "syncFiles",
12843
12834
  "syncRemote",
@@ -12847,22 +12838,19 @@ var KNOWN_GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set([
12847
12838
  ...KNOWN_CONFIG_KEYS,
12848
12839
  "repos"
12849
12840
  ]);
12841
+ var REMOVED_CONFIG_KEYS = /* @__PURE__ */ new Set(["syncDirs"]);
12850
12842
  var DEFAULT_CONFIG = Object.freeze({});
12851
12843
  async function loadConfig(root) {
12852
12844
  const path9 = join(root, CONFIG_FILE_NAME);
12853
12845
  return loadConfigFile(path9);
12854
12846
  }
12855
12847
  async function loadEffectiveConfig(root, home = homedir(), onWarning) {
12856
- return (await loadEffectiveConfigResult(root, home, onWarning)).config;
12857
- }
12858
- async function loadEffectiveConfigResult(root, home = homedir(), onWarning) {
12859
12848
  const [globalConfig, localConfig] = await Promise.all([
12860
12849
  loadGlobalConfig(home),
12861
12850
  loadConfig(root)
12862
12851
  ]);
12863
12852
  const repos = globalConfig.config.repos;
12864
12853
  const perRepoConfig = isPlainObject(repos) ? findPerRepoConfig(repos, root, home) : {};
12865
- validateSyncDirsConfig(perRepoConfig.syncDirs);
12866
12854
  const globalBase = { ...globalConfig.config };
12867
12855
  delete globalBase.repos;
12868
12856
  if (onWarning) {
@@ -12905,10 +12893,7 @@ async function loadEffectiveConfigResult(root, home = homedir(), onWarning) {
12905
12893
  if (Object.keys(globalHooks).length > 0 || Object.keys(perRepoHooks).length > 0 || Object.keys(localHooks).length > 0) {
12906
12894
  merged.hooks = { ...globalHooks, ...perRepoHooks, ...localHooks };
12907
12895
  }
12908
- return {
12909
- config: toEffectiveConfig(merged),
12910
- dependencyBootstrapExplicit: Object.hasOwn(globalBase, "dependencyBootstrap") || Object.hasOwn(perRepoConfig, "dependencyBootstrap") || Object.hasOwn(localConfig.config, "dependencyBootstrap")
12911
- };
12896
+ return toEffectiveConfig(merged);
12912
12897
  }
12913
12898
  async function loadGlobalConfig(home = homedir()) {
12914
12899
  return loadConfigFile(GLOBAL_CONFIG_FILE_PATH(home));
@@ -12920,15 +12905,6 @@ async function saveLocalConfig(root, config) {
12920
12905
  `, "utf8");
12921
12906
  return path9;
12922
12907
  }
12923
- async function updateLocalConfigKey(root, key, value) {
12924
- const loaded = await loadConfig(root);
12925
- const nextConfig = {
12926
- ...loaded.config,
12927
- [key]: value
12928
- };
12929
- await saveLocalConfig(root, nextConfig);
12930
- return nextConfig;
12931
- }
12932
12908
  async function saveGlobalConfig(config, home = homedir()) {
12933
12909
  validateConfigValues(config);
12934
12910
  const path9 = GLOBAL_CONFIG_FILE_PATH(home);
@@ -12945,6 +12921,7 @@ async function unsetGlobalConfigKey(key, home = homedir()) {
12945
12921
  return nextConfig;
12946
12922
  }
12947
12923
  async function updateGlobalConfigKey(key, value, home = homedir()) {
12924
+ assertConfigKeySupported(key);
12948
12925
  const loaded = await loadGlobalConfig(home);
12949
12926
  const nextConfig = {
12950
12927
  ...loaded.config,
@@ -12953,15 +12930,6 @@ async function updateGlobalConfigKey(key, value, home = homedir()) {
12953
12930
  await saveGlobalConfig(nextConfig, home);
12954
12931
  return nextConfig;
12955
12932
  }
12956
- async function updateGlobalRepoConfigKey(repoRoot, key, value, home = homedir()) {
12957
- const loaded = await loadGlobalConfig(home);
12958
- const repos = isPlainObject(loaded.config.repos) ? { ...loaded.config.repos } : {};
12959
- const existing = isPlainObject(repos[repoRoot]) ? repos[repoRoot] : {};
12960
- repos[repoRoot] = { ...existing, [key]: value };
12961
- const nextConfig = { ...loaded.config, repos };
12962
- await saveGlobalConfig(nextConfig, home);
12963
- return nextConfig;
12964
- }
12965
12933
  function GLOBAL_CONFIG_FILE_PATH(home = homedir()) {
12966
12934
  const configDir = process.env.GJI_CONFIG_DIR;
12967
12935
  if (configDir) {
@@ -12980,32 +12948,19 @@ function resolveConfigString(config, key) {
12980
12948
  const value = config[key];
12981
12949
  return typeof value === "string" && value.length > 0 ? value : void 0;
12982
12950
  }
12983
- function validateSyncDirsConfig(value) {
12984
- normalizeSyncDirsConfig(value);
12951
+ function assertConfigKeySupported(key) {
12952
+ if (REMOVED_CONFIG_KEYS.has(key)) {
12953
+ throw new Error(`configuration key "${key}" is no longer supported`);
12954
+ }
12985
12955
  }
12986
12956
  function normalizeDependencyBootstrap(value) {
12987
12957
  if (value === void 0) return;
12988
- if (value !== "off" && value !== "cow-then-repair" && value !== "install-only") {
12989
- throw new Error(
12990
- 'dependencyBootstrap: expected "off", "cow-then-repair", or "install-only"'
12991
- );
12958
+ if (value !== "off") {
12959
+ throw new Error('dependencyBootstrap: expected "off"');
12992
12960
  }
12993
12961
  return value;
12994
12962
  }
12995
- function normalizeSyncDirsConfig(value) {
12996
- if (value === void 0) return;
12997
- if (!Array.isArray(value)) {
12998
- throw new Error("syncDirs: expected an array of relative directory paths");
12999
- }
13000
- return value.map((pattern) => {
13001
- if (typeof pattern !== "string") {
13002
- throw new Error("syncDirs: every entry must be a string");
13003
- }
13004
- return validateSyncDirPattern(pattern);
13005
- });
13006
- }
13007
12963
  function validateConfigValues(config) {
13008
- validateSyncDirsConfig(config.syncDirs);
13009
12964
  normalizeDependencyBootstrap(config.dependencyBootstrap);
13010
12965
  const repos = config.repos;
13011
12966
  if (!isPlainObject(repos)) return;
@@ -13015,29 +12970,6 @@ function validateConfigValues(config) {
13015
12970
  }
13016
12971
  }
13017
12972
  }
13018
- function validateSyncDirPattern(pattern) {
13019
- if (pattern.length === 0 || isAbsolute(pattern) || win32.isAbsolute(pattern)) {
13020
- throw new Error(
13021
- `syncDirs: pattern must be a relative path, got: ${pattern}`
13022
- );
13023
- }
13024
- const segments = pattern.split(/[\\/]+/u);
13025
- if (segments.includes("..")) {
13026
- throw new Error(
13027
- `syncDirs: pattern must not contain '..' segments, got: ${pattern}`
13028
- );
13029
- }
13030
- if (segments.some((segment) => segment.toLowerCase() === ".git")) {
13031
- throw new Error(`syncDirs: pattern must not include .git, got: ${pattern}`);
13032
- }
13033
- const normalized = normalize(pattern);
13034
- if (normalized === ".") {
13035
- throw new Error(
13036
- `syncDirs: pattern must name a directory below the repository root, got: ${pattern}`
13037
- );
13038
- }
13039
- return normalized;
13040
- }
13041
12973
  async function loadConfigFile(path9) {
13042
12974
  try {
13043
12975
  const rawConfig = await readFile(path9, "utf8");
@@ -13086,12 +13018,6 @@ function toEffectiveConfig(config) {
13086
13018
  delete effective[key];
13087
13019
  }
13088
13020
  }
13089
- if (effective.skipInstallPrompt !== void 0 && typeof effective.skipInstallPrompt !== "boolean") {
13090
- delete effective.skipInstallPrompt;
13091
- }
13092
- if (config.syncDirs !== void 0) {
13093
- effective.syncDirs = normalizeSyncDirsConfig(config.syncDirs);
13094
- }
13095
13021
  if (Array.isArray(config.syncFiles)) {
13096
13022
  effective.syncFiles = config.syncFiles.filter(
13097
13023
  (value) => typeof value === "string"
@@ -13219,7 +13145,7 @@ async function runArgvHook(hookCmd, cwd, context, stderr, stdout3) {
13219
13145
  stderr("gji: hook argv command must include a non-empty command\n");
13220
13146
  return;
13221
13147
  }
13222
- await new Promise((resolve13) => {
13148
+ await new Promise((resolve12) => {
13223
13149
  const child = spawn2(command, args, {
13224
13150
  cwd,
13225
13151
  shell: false,
@@ -13239,18 +13165,18 @@ async function runArgvHook(hookCmd, cwd, context, stderr, stdout3) {
13239
13165
  `
13240
13166
  );
13241
13167
  }
13242
- resolve13();
13168
+ resolve12();
13243
13169
  });
13244
13170
  child.on("error", (err) => {
13245
13171
  stderr(`gji: hook failed to start: ${err.message}
13246
13172
  `);
13247
- resolve13();
13173
+ resolve12();
13248
13174
  });
13249
13175
  });
13250
13176
  }
13251
13177
  async function runShellHook(hookCmd, cwd, context, stderr, stdout3) {
13252
13178
  const interpolated = interpolate(hookCmd, context);
13253
- await new Promise((resolve13) => {
13179
+ await new Promise((resolve12) => {
13254
13180
  const child = spawn2(interpolated, {
13255
13181
  cwd,
13256
13182
  shell: true,
@@ -13268,12 +13194,12 @@ async function runShellHook(hookCmd, cwd, context, stderr, stdout3) {
13268
13194
  stderr(`gji: hook exited with code ${code}: ${interpolated}
13269
13195
  `);
13270
13196
  }
13271
- resolve13();
13197
+ resolve12();
13272
13198
  });
13273
13199
  child.on("error", (err) => {
13274
13200
  stderr(`gji: hook failed to start: ${err.message}
13275
13201
  `);
13276
- resolve13();
13202
+ resolve12();
13277
13203
  });
13278
13204
  });
13279
13205
  }
@@ -13330,7 +13256,7 @@ function createNavigationRepository(name, root) {
13330
13256
  // src/repo.ts
13331
13257
  import { access } from "node:fs/promises";
13332
13258
  import { homedir as homedir3 } from "node:os";
13333
- import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve3 } from "node:path";
13259
+ import { basename, dirname as dirname3, isAbsolute, join as join3, resolve as resolve3 } from "node:path";
13334
13260
 
13335
13261
  // src/git.ts
13336
13262
  import { execFile } from "node:child_process";
@@ -13480,7 +13406,7 @@ function hasExitCode(error, code) {
13480
13406
  async function detectRepository(cwd) {
13481
13407
  const currentRoot = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
13482
13408
  const rawCommonDir = await runGit(cwd, ["rev-parse", "--git-common-dir"]);
13483
- const gitCommonDir = isAbsolute2(rawCommonDir) ? rawCommonDir : resolve3(currentRoot, rawCommonDir);
13409
+ const gitCommonDir = isAbsolute(rawCommonDir) ? rawCommonDir : resolve3(currentRoot, rawCommonDir);
13484
13410
  const repoRoot = dirname3(gitCommonDir);
13485
13411
  return {
13486
13412
  currentRoot,
@@ -13492,7 +13418,7 @@ async function detectRepository(cwd) {
13492
13418
  }
13493
13419
  async function worktreeGitDir(worktreePath) {
13494
13420
  const rawGitDir = await runGit(worktreePath, ["rev-parse", "--git-dir"]);
13495
- return isAbsolute2(rawGitDir) ? rawGitDir : resolve3(worktreePath, rawGitDir);
13421
+ return isAbsolute(rawGitDir) ? rawGitDir : resolve3(worktreePath, rawGitDir);
13496
13422
  }
13497
13423
  function resolveWorktreePath(repoRoot, branch, basePath) {
13498
13424
  const segments = branch.split("/").filter(Boolean);
@@ -14743,7 +14669,7 @@ async function acquireUndoLogLock(path9) {
14743
14669
  const remainingMs = deadline - Date.now();
14744
14670
  if (remainingMs > 0)
14745
14671
  await new Promise(
14746
- (resolve13) => setTimeout(resolve13, Math.min(UNDO_LOCK_RETRY_DELAY_MS, remainingMs))
14672
+ (resolve12) => setTimeout(resolve12, Math.min(UNDO_LOCK_RETRY_DELAY_MS, remainingMs))
14747
14673
  );
14748
14674
  }
14749
14675
  }
@@ -16935,7 +16861,7 @@ function normalizeShell(value) {
16935
16861
  var TOP_LEVEL_COMMANDS = [
16936
16862
  {
16937
16863
  name: "new",
16938
- description: "create a new branch or detached linked worktree and CoW-bootstrap configured directories"
16864
+ description: "create a new branch or detached linked worktree"
16939
16865
  },
16940
16866
  {
16941
16867
  name: "done",
@@ -17087,7 +17013,7 @@ _gji_completion() {
17087
17013
 
17088
17014
  case "$command_name" in
17089
17015
  new)
17090
- COMPREPLY=( $(compgen -W "--detached --from-current --no-fetch --take --copy --task --force --open --editor --dry-run --json --help" -- "$cur") )
17016
+ COMPREPLY=( $(compgen -W "--detached --from-current --no-fetch --take --copy --task --force --open --editor --dry-run --no-install --json --help" -- "$cur") )
17091
17017
  ;;
17092
17018
  done)
17093
17019
  COMPREPLY=( $(compgen -W "--force --keep-branch --json --help" -- "$cur") )
@@ -17125,7 +17051,7 @@ _gji_completion() {
17125
17051
  COMPREPLY=( $(compgen -W "$(__gji_pr_targets) --select --help" -- "$cur") )
17126
17052
  fi
17127
17053
  else
17128
- COMPREPLY=( $(compgen -W "--dry-run --json --help" -- "$cur") )
17054
+ COMPREPLY=( $(compgen -W "--dry-run --no-install --json --help" -- "$cur") )
17129
17055
  fi
17130
17056
  ;;
17131
17057
  back)
@@ -17343,6 +17269,7 @@ complete -c gji -n '__fish_seen_subcommand_from new' -l force -d 'remove and rec
17343
17269
  complete -c gji -n '__fish_seen_subcommand_from new' -l open -d 'open the new worktree in an editor after creation'
17344
17270
  complete -c gji -n '__fish_seen_subcommand_from new' -l editor -r -d 'editor CLI to use with --open (code, cursor, zed, \u2026)'
17345
17271
  complete -c gji -n '__fish_seen_subcommand_from new' -l dry-run -d 'show what would be created without executing any git commands or writing files'
17272
+ complete -c gji -n '__fish_seen_subcommand_from new' -l no-install -d 'skip automatic dependency setup in the new worktree'
17346
17273
  complete -c gji -n '__fish_seen_subcommand_from new' -l json -d 'emit JSON on success or error instead of human-readable output'
17347
17274
 
17348
17275
  complete -c gji -n '__fish_seen_subcommand_from init' -l write -d 'write the integration to the shell config file'
@@ -17358,6 +17285,7 @@ complete -c gji -n '__fish_seen_subcommand_from completion' -a 'fish' -d 'shell'
17358
17285
  complete -c gji -n '__fish_seen_subcommand_from completion' -a 'zsh' -d 'shell'
17359
17286
 
17360
17287
  complete -c gji -n '__fish_seen_subcommand_from pr; and test (commandline -opc)[3] != open' -l dry-run -d 'show what would be created without executing any git commands or writing files'
17288
+ complete -c gji -n '__fish_seen_subcommand_from pr; and test (commandline -opc)[3] != open' -l no-install -d 'skip automatic dependency setup in the PR worktree'
17361
17289
  complete -c gji -n '__fish_seen_subcommand_from pr; and test (commandline -opc)[3] != open' -l json -d 'emit JSON on success or error instead of human-readable output'
17362
17290
  complete -c gji -n '__fish_seen_subcommand_from pr' -a 'open' -d 'open a pull request in the default browser'
17363
17291
  complete -c gji -n '__gji_should_complete_pr_select' -l select -d 'choose a pull request from any linked worktree'
@@ -17501,7 +17429,7 @@ fi
17501
17429
 
17502
17430
  case "\${words[2]}" in
17503
17431
  new)
17504
- _arguments '--detached[create a detached worktree without a branch]' '--from-current[base the new branch on the current worktree instead of the main worktree]' '--no-fetch[skip refreshing the remote default branch before creating the new branch]' '--take[move current uncommitted changes into the new worktree]' '--copy[copy current uncommitted changes instead of moving them (requires --take)]' '--task[record the purpose of the new worktree]:description:' '--force[remove and recreate the worktree if the target path already exists]' '--open[open the new worktree in an editor after creation]' '--editor[editor CLI to use with --open (code, cursor, zed, \u2026)]:editor:' '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
17432
+ _arguments '--detached[create a detached worktree without a branch]' '--from-current[base the new branch on the current worktree instead of the main worktree]' '--no-fetch[skip refreshing the remote default branch before creating the new branch]' '--take[move current uncommitted changes into the new worktree]' '--copy[copy current uncommitted changes instead of moving them (requires --take)]' '--task[record the purpose of the new worktree]:description:' '--force[remove and recreate the worktree if the target path already exists]' '--open[open the new worktree in an editor after creation]' '--editor[editor CLI to use with --open (code, cursor, zed, \u2026)]:editor:' '--dry-run[show what would be created without executing any git commands or writing files]' '--no-install[skip automatic dependency setup in the new worktree]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
17505
17433
  ;;
17506
17434
  done)
17507
17435
  _arguments '--force[remove dirty or unmerged worktrees without prompting]' '--keep-branch[remove the worktree but preserve its branch]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
@@ -17528,7 +17456,7 @@ case "\${words[2]}" in
17528
17456
  _arguments '--select[choose a pull request from any linked worktree]' '4:branch or PR number:->pr_targets'
17529
17457
  fi
17530
17458
  else
17531
- _arguments '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:ref:(open)'
17459
+ _arguments '--dry-run[show what would be created without executing any git commands or writing files]' '--no-install[skip automatic dependency setup in the PR worktree]' '--json[emit JSON on success or error instead of human-readable output]' '2:ref:(open)'
17532
17460
  fi
17533
17461
  ;;
17534
17462
  back)
@@ -17706,9 +17634,9 @@ var EDITORS = [
17706
17634
  ];
17707
17635
  async function defaultSpawnEditor(cli, args) {
17708
17636
  const child = spawn3(cli, args, { detached: true, stdio: "ignore" });
17709
- await new Promise((resolve13, reject) => {
17637
+ await new Promise((resolve12, reject) => {
17710
17638
  child.once("error", reject);
17711
- child.once("spawn", resolve13);
17639
+ child.once("spawn", resolve12);
17712
17640
  });
17713
17641
  child.unref();
17714
17642
  }
@@ -17797,8 +17725,8 @@ async function removeMissingRegistryEntries(paths, home = homedir6()) {
17797
17725
  async function withRegistryLock(registryPath, operation) {
17798
17726
  const previous = registryLocks.get(registryPath);
17799
17727
  let release;
17800
- const current = new Promise((resolve13) => {
17801
- release = resolve13;
17728
+ const current = new Promise((resolve12) => {
17729
+ release = resolve12;
17802
17730
  });
17803
17731
  registryLocks.set(registryPath, current);
17804
17732
  if (previous) await previous;
@@ -18685,7 +18613,7 @@ function toMessage3(error) {
18685
18613
  }
18686
18614
 
18687
18615
  // src/go.ts
18688
- import { basename as basename10 } from "node:path";
18616
+ import { basename as basename9 } from "node:path";
18689
18617
 
18690
18618
  // src/context-card.ts
18691
18619
  async function renderContextCard(worktreePath) {
@@ -18712,42 +18640,15 @@ function formatLastCommit2(info) {
18712
18640
  }
18713
18641
 
18714
18642
  // src/pr.ts
18715
- import { execFile as execFile7 } from "node:child_process";
18716
- import { mkdir as mkdir8 } from "node:fs/promises";
18717
- import { dirname as dirname11 } from "node:path";
18718
- import { promisify as promisify8 } from "node:util";
18719
-
18720
- // src/format-bytes.ts
18721
- function formatBytes(bytes) {
18722
- if (bytes === void 0) return "size unavailable";
18723
- if (bytes < 1024) return `${bytes} B`;
18724
- const units = ["KB", "MB", "GB", "TB"];
18725
- let value = bytes;
18726
- let unit = -1;
18727
- while (value >= 1024 && unit < units.length - 1) {
18728
- value /= 1024;
18729
- unit += 1;
18730
- }
18731
- return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unit]}`;
18732
- }
18643
+ import { execFile as execFile6 } from "node:child_process";
18644
+ import { mkdir as mkdir7 } from "node:fs/promises";
18645
+ import { dirname as dirname10 } from "node:path";
18646
+ import { promisify as promisify7 } from "node:util";
18733
18647
 
18734
18648
  // src/bootstrap-output.ts
18735
- function createBootstrapReporter(write, json, measureCloneSize = false) {
18649
+ function createBootstrapReporter(write, json) {
18736
18650
  return {
18737
- measureCloneSize: measureCloneSize && !json,
18738
18651
  write,
18739
- cloned: (directory) => {
18740
- if (json) return;
18741
- write(
18742
- `\u26A1 cloned ${directory.dir} (${formatBytes(directory.bytes)} \u2192 ${formatDuration(directory.ms)})
18743
- `
18744
- );
18745
- },
18746
- skipped: (directory) => {
18747
- if (json) return;
18748
- write(`gji: skipped ${directory.dir} \u2014 ${directory.reason}
18749
- `);
18750
- },
18751
18652
  dependency: (event) => {
18752
18653
  if (json) return;
18753
18654
  const target = event.target ? ` ${event.target}` : "";
@@ -18756,29 +18657,15 @@ function createBootstrapReporter(write, json, measureCloneSize = false) {
18756
18657
  }
18757
18658
  };
18758
18659
  }
18759
- function formatDuration(ms) {
18760
- return `${(ms / 1e3).toFixed(1)}s`;
18761
- }
18762
18660
 
18763
18661
  // src/dependency-bootstrap.ts
18764
- import { execFile as execFile6 } from "node:child_process";
18765
- import { constants as constants6 } from "node:fs";
18766
- import { lstat as lstat4, open as open3, readdir as readdir2, readFile as readFile9, realpath as realpath3, rm as rm4 } from "node:fs/promises";
18767
- import {
18768
- basename as basename7,
18769
- dirname as dirname9,
18770
- isAbsolute as isAbsolute4,
18771
- join as join12,
18772
- relative as relative3,
18773
- resolve as resolve9,
18774
- sep as sep3
18775
- } from "node:path";
18776
- import { promisify as promisify7 } from "node:util";
18662
+ import { readFile as readFile8, realpath as realpath3 } from "node:fs/promises";
18663
+ import { join as join12, relative as relative2, resolve as resolve9, sep as sep3 } from "node:path";
18777
18664
 
18778
18665
  // src/command-runner.ts
18779
18666
  import { spawn as spawn4 } from "node:child_process";
18780
18667
  var runCommand = async (command, cwd, stderr, stdout3 = (chunk) => process.stdout.write(chunk), options) => {
18781
- await new Promise((resolve13, reject) => {
18668
+ await new Promise((resolve12, reject) => {
18782
18669
  const shell = options?.shell ?? true;
18783
18670
  const [executable, args] = shell ? [command, []] : splitCommand(command);
18784
18671
  const child = spawn4(executable, args, {
@@ -18797,7 +18684,7 @@ var runCommand = async (command, cwd, stderr, stdout3 = (chunk) => process.stdou
18797
18684
  if (code !== 0) {
18798
18685
  reject(new Error(`exited with code ${code}`));
18799
18686
  } else {
18800
- resolve13();
18687
+ resolve12();
18801
18688
  }
18802
18689
  });
18803
18690
  child.on("error", reject);
@@ -18813,32 +18700,6 @@ function splitCommand(command) {
18813
18700
  ];
18814
18701
  }
18815
18702
 
18816
- // src/dir-clone.ts
18817
- import { execFile as execFile5 } from "node:child_process";
18818
- import { randomUUID as randomUUID2 } from "node:crypto";
18819
- import { constants as constants5 } from "node:fs";
18820
- import {
18821
- chmod,
18822
- cp,
18823
- lstat as lstat3,
18824
- mkdir as mkdir7,
18825
- mkdtemp,
18826
- opendir,
18827
- readdir,
18828
- readFile as readFile8,
18829
- readlink,
18830
- realpath as realpath2,
18831
- rename as rename3,
18832
- rm as rm3,
18833
- rmdir,
18834
- symlink,
18835
- unlink as unlink3,
18836
- utimes,
18837
- writeFile as writeFile8
18838
- } from "node:fs/promises";
18839
- import { basename as basename6, dirname as dirname8, join as join11, relative as relative2, resolve as resolve8, sep as sep2 } from "node:path";
18840
- import { promisify as promisify6 } from "node:util";
18841
-
18842
18703
  // src/fs-utils.ts
18843
18704
  import { lstat } from "node:fs/promises";
18844
18705
  async function pathExists2(path9) {
@@ -18866,7 +18727,7 @@ function hasErrorCode(error, code) {
18866
18727
  // src/safe-destination.ts
18867
18728
  import { constants as constants4 } from "node:fs";
18868
18729
  import { lstat as lstat2, mkdir as mkdir6, open as open2 } from "node:fs/promises";
18869
- import { isAbsolute as isAbsolute3, join as join10, relative, resolve as resolve7, sep } from "node:path";
18730
+ import { isAbsolute as isAbsolute2, join as join10, relative, resolve as resolve7, sep } from "node:path";
18870
18731
  async function openDestinationDirectory(root, path9) {
18871
18732
  const segments = destinationSegments(root, path9);
18872
18733
  if (process.platform === "darwin") {
@@ -18909,7 +18770,7 @@ async function inspectDestination(root, path9) {
18909
18770
  const resolvedPath = resolve7(path9);
18910
18771
  const resolvedRoot = resolve7(root);
18911
18772
  const distance = relative(resolvedRoot, resolvedPath);
18912
- if (isAbsolute3(distance) || distance === ".." || distance.startsWith(`..${sep}`)) {
18773
+ if (isAbsolute2(distance) || distance === ".." || distance.startsWith(`..${sep}`)) {
18913
18774
  return { kind: "unsafe", reason: "destination escapes the worktree" };
18914
18775
  }
18915
18776
  let current = resolvedRoot;
@@ -18968,7 +18829,7 @@ function destinationSegments(root, path9) {
18968
18829
  const resolvedRoot = resolve7(root);
18969
18830
  const resolvedPath = resolve7(path9);
18970
18831
  const distance = relative(resolvedRoot, resolvedPath);
18971
- if (isAbsolute3(distance) || distance === ".." || distance.startsWith(`..${sep}`)) {
18832
+ if (isAbsolute2(distance) || distance === ".." || distance.startsWith(`..${sep}`)) {
18972
18833
  throw new Error("destination escapes the worktree");
18973
18834
  }
18974
18835
  return distance.split(sep).filter(Boolean);
@@ -18987,7 +18848,7 @@ async function ensureDestinationDirectory(root, path9) {
18987
18848
  const resolvedRoot = resolve7(root);
18988
18849
  const resolvedPath = resolve7(path9);
18989
18850
  const distance = relative(resolvedRoot, resolvedPath);
18990
- if (isAbsolute3(distance) || distance === ".." || distance.startsWith(`..${sep}`)) {
18851
+ if (isAbsolute2(distance) || distance === ".." || distance.startsWith(`..${sep}`)) {
18991
18852
  throw new Error("destination escapes the worktree");
18992
18853
  }
18993
18854
  let current = resolvedRoot;
@@ -19017,625 +18878,252 @@ async function ensureDestinationDirectory(root, path9) {
19017
18878
  }
19018
18879
  }
19019
18880
 
19020
- // src/dir-clone.ts
18881
+ // src/uv-validation.ts
18882
+ import { execFile as execFile5 } from "node:child_process";
18883
+ import { constants as constants5 } from "node:fs";
18884
+ import { lstat as lstat3, open as open3, readdir, realpath as realpath2 } from "node:fs/promises";
18885
+ import { basename as basename6, dirname as dirname8, isAbsolute as isAbsolute3, join as join11, resolve as resolve8, sep as sep2 } from "node:path";
18886
+ import { promisify as promisify6, TextDecoder } from "node:util";
19021
18887
  var execFileAsync5 = promisify6(execFile5);
19022
- var CLONE_LOCK_TTL_MS = 5 * 60 * 1e3;
19023
- var CLONE_GUARD_TTL_MS = 30 * 1e3;
19024
- var CLONE_LOCK_SUFFIX = ".gji-clone-lock";
19025
- var SIZE_ESTIMATE_MAX_ENTRIES = 1e6;
19026
- var SIZE_ESTIMATE_MAX_MS = 5e3;
19027
- async function cloneDir(source, destination, options = {}) {
19028
- const platform3 = options.platform ?? process.platform;
19029
- if (!isClonePlatformSupported(platform3)) {
19030
- throw new CloneUnsupportedError(`platform ${platform3} has no CoW strategy`);
19031
- }
19032
- const sourcePath = await realpath2(source);
19033
- const sourceStats = await lstat3(sourcePath);
19034
- if (!sourceStats.isDirectory()) {
19035
- throw new Error("source is not a directory");
19036
- }
19037
- const destinationPath = await resolveProspectivePath(destination);
19038
- const destinationDistance = relative2(sourcePath, destinationPath);
19039
- if (destinationDistance === "" || destinationDistance !== ".." && !destinationDistance.startsWith(`..${sep2}`)) {
19040
- throw new Error("clone destination must not be inside its source");
19041
- }
19042
- const startedAt = Date.now();
19043
- const parent = dirname8(destination);
19044
- let safeParent;
19045
- try {
19046
- if (options.destinationRoot) {
19047
- const parentInspection = await inspectDestination(
19048
- options.destinationRoot,
19049
- parent
19050
- );
19051
- if (parentInspection.kind === "unsafe") {
19052
- throw new Error(parentInspection.reason);
19053
- }
19054
- safeParent = await openDestinationDirectory(
19055
- options.destinationRoot,
19056
- parent
19057
- );
19058
- } else {
19059
- await mkdir7(parent, { recursive: true });
19060
- }
19061
- const operationParent = safeParent?.path ?? parent;
19062
- const operationDestination = join11(operationParent, basename6(destination));
19063
- const cloneLock = await acquireCloneLock(
19064
- `${operationDestination}${CLONE_LOCK_SUFFIX}`,
19065
- destination
18888
+ var UV_VALIDATION_MAX_ENTRIES = 1e4;
18889
+ var UV_VALIDATION_MAX_FILE_BYTES = 1024 * 1024;
18890
+ var UV_VALIDATION_MAX_TOTAL_BYTES = 16 * 1024 * 1024;
18891
+ var UV_VALIDATION_TEXT_PROBE_BYTES = 4 * 1024;
18892
+ function virtualEnvironmentPrefixes(text) {
18893
+ const prefixes = [];
18894
+ for (const line of text.split(/\r?\n/u)) {
18895
+ if (!/\bVIRTUAL_ENV\b/u.test(line)) continue;
18896
+ const quotedAssignment = line.match(
18897
+ /\bVIRTUAL_ENV(?:\s*=|\s+)\s*(?:"([^"]+)"|'([^']+)')/u
19066
18898
  );
19067
- const stopLockHeartbeat = startLockHeartbeat(cloneLock);
19068
- let temporaryRoot;
19069
- let reservation;
19070
- const publishedEntries = [];
19071
- try {
19072
- if (await destinationExists(operationDestination)) {
19073
- throw new CloneDestinationExistsError(destination);
19074
- }
19075
- reservation = await reserveDestination(operationDestination);
19076
- temporaryRoot = await mkdtemp(
19077
- join11(operationParent, `.${basename6(destination)}.gji-clone-`)
19078
- );
19079
- const temporaryDestination = join11(temporaryRoot, basename6(destination));
19080
- try {
19081
- await (options.copyDirectory ?? ((sourcePath2, targetPath) => copyDirectoryWithCow(
19082
- sourcePath2,
19083
- targetPath,
19084
- platform3,
19085
- options.runLinuxCommand
19086
- )))(sourcePath, temporaryDestination);
19087
- } catch (error) {
19088
- if (error instanceof CloneUnsupportedError) throw error;
19089
- if (isUnsupportedCloneError(error)) {
19090
- throw new CloneUnsupportedError(toErrorMessage(error));
19091
- }
19092
- throw error;
19093
- }
19094
- await publishCloneContents(
19095
- temporaryDestination,
19096
- operationDestination,
19097
- reservation.path,
19098
- (entry) => publishedEntries.push(entry),
19099
- options.copyFile ?? runForcedCloneFileCopy
19100
- );
19101
- reservation = void 0;
19102
- } finally {
19103
- stopLockHeartbeat();
19104
- if (reservation) {
19105
- await cleanupReservedDestination(
19106
- operationDestination,
19107
- reservation,
19108
- publishedEntries
19109
- );
19110
- }
19111
- if (temporaryRoot) {
19112
- await rm3(temporaryRoot, { force: true, recursive: true }).catch(
19113
- () => void 0
19114
- );
19115
- }
19116
- await releaseCloneLock(cloneLock).catch(() => void 0);
19117
- }
19118
- return cloneResult(sourcePath, startedAt, options.measureBytes);
19119
- } finally {
19120
- await safeParent?.close().catch(() => void 0);
18899
+ const quotedPrefix = quotedAssignment?.[1] ?? quotedAssignment?.[2];
18900
+ if (quotedPrefix && isAbsolute3(quotedPrefix)) prefixes.push(quotedPrefix);
18901
+ const unquoted = line.match(/\bVIRTUAL_ENV(?:\s*=|\s+)\s*([^\s"']+)/u)?.[1];
18902
+ if (unquoted && isAbsolute3(unquoted)) prefixes.push(unquoted);
19121
18903
  }
18904
+ return uniquePaths(prefixes);
19122
18905
  }
19123
- async function copyDirectoryWithCow(source, destination, platform3, runLinuxCommand) {
19124
- if (platform3 === "darwin") {
19125
- await cp(source, destination, {
19126
- errorOnExist: true,
19127
- force: false,
19128
- mode: constants5.COPYFILE_FICLONE_FORCE,
19129
- preserveTimestamps: true,
19130
- recursive: true
19131
- });
19132
- return;
18906
+ function pythonPrefixFromShebang(text) {
18907
+ const command = text.match(/^#!\s*([^\r\n]+)/u)?.[1]?.trim().split(/\s+/u)[0];
18908
+ if (!command || !isAbsolute3(command)) return void 0;
18909
+ if (!/^python(?:\d+(?:\.\d+)?)?(?:\.exe)?$/iu.test(
18910
+ command.split(sep2).at(-1) ?? ""
18911
+ )) {
18912
+ return void 0;
18913
+ }
18914
+ return dirname8(dirname8(command));
18915
+ }
18916
+ function pythonPrefixesFromText(text) {
18917
+ const interpreters = [];
18918
+ for (const match of text.matchAll(/["'](\/[^"']+)["']/gu)) {
18919
+ if (match[1]) interpreters.push(match[1]);
19133
18920
  }
19134
- const strategy = cloneStrategy(platform3);
19135
- if (!strategy) {
19136
- throw new CloneUnsupportedError(`platform ${platform3} has no CoW strategy`);
18921
+ for (const match of text.matchAll(/(?:^|\s)(\/[^\s"']+)/gmu)) {
18922
+ if (match[1]) interpreters.push(match[1]);
19137
18923
  }
19138
- await (runLinuxCommand ?? runCloneCommand)(
19139
- "cp",
19140
- strategy(source, destination)
18924
+ return uniquePaths(
18925
+ interpreters.flatMap((interpreter) => {
18926
+ const name = interpreter.split(sep2).at(-1) ?? "";
18927
+ return /^python(?:\d+(?:\.\d+)?)?(?:\.exe)?$/iu.test(name) ? [dirname8(dirname8(interpreter))] : [];
18928
+ })
19141
18929
  );
19142
18930
  }
19143
- async function resolveProspectivePath(path9) {
19144
- let current = resolve8(path9);
19145
- const missing = [];
19146
- while (true) {
19147
- try {
19148
- return join11(await realpath2(current), ...missing);
19149
- } catch (error) {
19150
- if (!isNotFoundError(error)) throw error;
19151
- const parent = dirname8(current);
19152
- if (parent === current) throw error;
19153
- missing.unshift(basename6(current));
19154
- current = parent;
18931
+ async function validateUvRelocation(target, context) {
18932
+ if (context.input === "preserve") return;
18933
+ if (!target.targetPath || !target.sourcePath) {
18934
+ throw new Error("uv environment target is not project-local");
18935
+ }
18936
+ const scriptsDirectory = join11(
18937
+ target.targetPath,
18938
+ process.platform === "win32" ? "Scripts" : "bin"
18939
+ );
18940
+ const interpreter = join11(
18941
+ scriptsDirectory,
18942
+ process.platform === "win32" ? "python.exe" : "python"
18943
+ );
18944
+ const { stdout: stdout3 } = await execFileAsync5(interpreter, [
18945
+ "-c",
18946
+ "import os, sys; print(os.path.realpath(sys.prefix))"
18947
+ ]);
18948
+ const [actualPrefix, expectedPrefix] = await Promise.all([
18949
+ realpath2(stdout3.trim()),
18950
+ realpath2(target.targetPath)
18951
+ ]);
18952
+ if (actualPrefix !== expectedPrefix) {
18953
+ throw new Error(
18954
+ `uv environment still points to its source prefix: ${actualPrefix}`
18955
+ );
18956
+ }
18957
+ const sourcePaths = uniquePaths([
18958
+ target.sourcePath,
18959
+ await realpath2(target.sourcePath).catch(() => void 0) ?? ""
18960
+ ]).filter(Boolean);
18961
+ const entries = await readdir(scriptsDirectory, { withFileTypes: true });
18962
+ assertSafeUvScriptEntries(entries);
18963
+ const environmentName = basename6(target.targetPath);
18964
+ const acceptedPrefixes = /* @__PURE__ */ new Set([
18965
+ resolve8(target.targetPath),
18966
+ await realpath2(target.targetPath)
18967
+ ]);
18968
+ let validatedBytes = 0;
18969
+ for (const path9 of [
18970
+ join11(target.targetPath, "pyvenv.cfg"),
18971
+ ...entries.filter((entry) => entry.isFile()).map((entry) => join11(scriptsDirectory, entry.name))
18972
+ ]) {
18973
+ const validated = await readBoundedUvTextFile(
18974
+ path9,
18975
+ UV_VALIDATION_MAX_TOTAL_BYTES - validatedBytes
18976
+ );
18977
+ if (!validated) continue;
18978
+ validatedBytes += validated.bytes;
18979
+ if (validated.text === void 0) continue;
18980
+ const { text } = validated;
18981
+ if (sourcePaths.some((sourcePath) => text.includes(sourcePath))) {
18982
+ throw new Error(`uv environment contains a stale source path: ${path9}`);
18983
+ }
18984
+ const shebangPrefix = pythonPrefixFromShebang(text);
18985
+ if (shebangPrefix && basename6(shebangPrefix) === environmentName && !acceptedPrefixes.has(resolve8(shebangPrefix))) {
18986
+ throw new Error(`uv launcher points outside its environment: ${path9}`);
18987
+ }
18988
+ if (pythonPrefixesFromText(text).filter((prefix) => basename6(prefix) === environmentName).some((prefix) => !acceptedPrefixes.has(resolve8(prefix)))) {
18989
+ throw new Error(`uv launcher points outside its environment: ${path9}`);
18990
+ }
18991
+ if (virtualEnvironmentPrefixes(text).some(
18992
+ (prefix) => !acceptedPrefixes.has(resolve8(prefix))
18993
+ )) {
18994
+ throw new Error(
18995
+ `uv activation script points outside its environment: ${path9}`
18996
+ );
19155
18997
  }
19156
18998
  }
19157
18999
  }
19158
- async function cloneResult(source, startedAt, measureBytes) {
19159
- const bytes = measureBytes === false ? void 0 : await estimateCloneBytes(source);
19160
- return { bytes, ms: Date.now() - startedAt };
19000
+ async function validateUvInstallation(target, context) {
19001
+ await validateUvStructure(target, context);
19002
+ await validateUvRelocation(target, context);
19161
19003
  }
19162
- var CloneDestinationExistsError = class extends Error {
19163
- code = "GJI_CLONE_DESTINATION_EXISTS";
19164
- constructor(destination) {
19165
- super(`destination already exists: ${destination}`);
19166
- this.name = "CloneDestinationExistsError";
19167
- }
19168
- };
19169
- var CloneUnsupportedError = class extends Error {
19170
- code = "GJI_CLONE_UNSUPPORTED";
19171
- constructor(reason) {
19172
- super(`copy-on-write cloning is not supported: ${reason}`);
19173
- this.name = "CloneUnsupportedError";
19004
+ async function validateUvStructure(target, context) {
19005
+ if (context.input === "preserve") return;
19006
+ if (!target.targetPath)
19007
+ throw new Error("uv environment target is not project-local");
19008
+ const targetStats = await lstat3(target.targetPath).catch(() => void 0);
19009
+ if (!targetStats) return;
19010
+ if (!targetStats.isDirectory() || targetStats.isSymbolicLink()) {
19011
+ throw new Error("uv environment must be a real directory");
19174
19012
  }
19175
- };
19176
- var CloneInProgressError = class extends Error {
19177
- code = "GJI_CLONE_IN_PROGRESS";
19178
- constructor(destination) {
19179
- super(`copy-on-write clone already in progress: ${destination}`);
19180
- this.name = "CloneInProgressError";
19013
+ const configStats = await lstat3(join11(target.targetPath, "pyvenv.cfg")).catch(
19014
+ () => void 0
19015
+ );
19016
+ if (configStats && (!configStats.isFile() || configStats.isSymbolicLink())) {
19017
+ throw new Error("uv pyvenv.cfg must be a regular file");
19181
19018
  }
19182
- };
19183
- function isCloneDestinationExistsError(error) {
19184
- return error instanceof CloneDestinationExistsError;
19185
- }
19186
- function isCloneInProgressError(error) {
19187
- return error instanceof CloneInProgressError;
19188
- }
19189
- async function directorySize(path9) {
19190
- const stats = await lstat3(path9);
19191
- if (!stats.isDirectory()) return stats.size;
19192
- const pending = [path9];
19193
- let total = 0;
19194
- let entryCount = 0;
19195
- const startedAt = Date.now();
19196
- while (pending.length > 0) {
19197
- const current = pending.pop();
19198
- if (!current) continue;
19199
- const directory = await opendir(current);
19200
- try {
19201
- for await (const entry of directory) {
19202
- entryCount += 1;
19203
- if (entryCount > SIZE_ESTIMATE_MAX_ENTRIES || Date.now() - startedAt > SIZE_ESTIMATE_MAX_MS) {
19204
- throw new Error("directory size estimate exceeded its safety limit");
19205
- }
19206
- const entryPath = join11(current, entry.name);
19207
- if (entry.isDirectory()) pending.push(entryPath);
19208
- else total += (await lstat3(entryPath)).size;
19209
- }
19210
- } finally {
19211
- await directory.close().catch(() => void 0);
19212
- }
19019
+ const scriptsDirectory = join11(
19020
+ target.targetPath,
19021
+ process.platform === "win32" ? "Scripts" : "bin"
19022
+ );
19023
+ const scriptsStats = await lstat3(scriptsDirectory).catch(() => void 0);
19024
+ if (!scriptsStats) return;
19025
+ if (!scriptsStats.isDirectory() || scriptsStats.isSymbolicLink()) {
19026
+ throw new Error("uv scripts path must be a real directory");
19213
19027
  }
19214
- return total;
19028
+ assertSafeUvScriptEntries(
19029
+ await readdir(scriptsDirectory, { withFileTypes: true })
19030
+ );
19215
19031
  }
19216
- function cloneStrategy(platform3) {
19217
- if (platform3 === "linux") {
19218
- return (source, destination) => [
19219
- "-a",
19220
- "--reflink=always",
19221
- source,
19222
- destination
19223
- ];
19032
+ function assertSafeUvScriptEntries(entries) {
19033
+ if (entries.length > UV_VALIDATION_MAX_ENTRIES) {
19034
+ throw new Error("uv scripts directory exceeds the validation entry limit");
19224
19035
  }
19225
- return null;
19226
- }
19227
- function isClonePlatformSupported(platform3) {
19228
- return platform3 === "darwin" || platform3 === "linux";
19229
- }
19230
- async function estimateCloneBytes(path9) {
19231
- try {
19232
- return await directorySize(path9);
19233
- } catch {
19234
- return void 0;
19036
+ for (const entry of entries) {
19037
+ if (entry.isSymbolicLink() && !isUvInterpreterName(entry.name)) {
19038
+ throw new Error(`uv script must not be a symbolic link: ${entry.name}`);
19039
+ }
19235
19040
  }
19236
19041
  }
19237
- async function runCloneCommand(command, args) {
19042
+ async function readBoundedUvTextFile(path9, remainingBytes) {
19043
+ let handle;
19238
19044
  try {
19239
- await execFileAsync5(command, args);
19045
+ handle = await open3(
19046
+ path9,
19047
+ constants5.O_RDONLY | (process.platform === "win32" ? 0 : constants5.O_NOFOLLOW)
19048
+ );
19240
19049
  } catch (error) {
19241
- if (isUnsupportedCloneError(error)) {
19242
- throw new CloneUnsupportedError(toErrorMessage(error));
19243
- }
19050
+ if (isNotFoundError(error)) return void 0;
19244
19051
  throw error;
19245
19052
  }
19246
- }
19247
- async function acquireCloneLock(lockPath, destination) {
19248
- const releaseGuard = await acquireCloneLockGuard(lockPath, destination);
19249
19053
  try {
19250
- return await acquireCloneLockWithGuard(lockPath, destination);
19251
- } finally {
19252
- await releaseGuard();
19253
- }
19254
- }
19255
- async function acquireCloneLockWithGuard(lockPath, destination) {
19256
- const lockToken = randomUUID2();
19257
- for (let attempt = 0; attempt < 3; attempt += 1) {
19258
- const published = await publishCloneLock(lockPath, lockToken);
19259
- if (published) return published;
19260
- let staleLock;
19261
- try {
19262
- staleLock = await inspectCloneLock(lockPath);
19263
- } catch (error) {
19264
- if (isNotFoundError(error)) continue;
19265
- throw error;
19054
+ const stats = await handle.stat();
19055
+ if (!stats.isFile()) return void 0;
19056
+ const probeBytes = Math.min(UV_VALIDATION_TEXT_PROBE_BYTES, stats.size);
19057
+ if (probeBytes > remainingBytes) {
19058
+ throw new Error("uv launchers exceed the total validation size limit");
19266
19059
  }
19267
- if (staleLock.kind === "invalid" || Date.now() - staleLock.mtimeMs < CLONE_LOCK_TTL_MS) {
19268
- throw new CloneInProgressError(destination);
19060
+ const probe = await readFilePrefix(handle, probeBytes);
19061
+ if (!looksLikeUtf8Text(probe, probe.byteLength === stats.size)) {
19062
+ return { bytes: probe.byteLength };
19269
19063
  }
19270
- const stalePath = `${lockPath}.stale-${randomUUID2()}`;
19271
- try {
19272
- await rename3(lockPath, stalePath);
19273
- } catch (error) {
19274
- if (isNotFoundError(error)) continue;
19275
- throw error;
19064
+ if (stats.size > UV_VALIDATION_MAX_FILE_BYTES) {
19065
+ throw new Error(`uv launcher exceeds the validation size limit: ${path9}`);
19276
19066
  }
19277
- try {
19278
- const replacement = await publishCloneLock(lockPath, lockToken);
19279
- if (!replacement) {
19280
- await cleanupStaleCloneLock(stalePath, staleLock);
19281
- continue;
19282
- }
19283
- try {
19284
- await cleanupStaleCloneLock(stalePath, staleLock);
19285
- } catch (cleanupError) {
19286
- await releaseCloneLockWithGuard(replacement).catch(() => void 0);
19287
- throw cleanupError;
19288
- }
19289
- return replacement;
19290
- } catch (error) {
19291
- await cleanupStaleCloneLock(stalePath, staleLock).catch(() => void 0);
19292
- if (isAlreadyExistsError(error)) continue;
19293
- throw error;
19067
+ if (stats.size > remainingBytes) {
19068
+ throw new Error("uv launchers exceed the total validation size limit");
19294
19069
  }
19295
- }
19296
- throw new CloneInProgressError(destination);
19297
- }
19298
- async function acquireCloneLockGuard(lockPath, destination) {
19299
- const guardPath = `${lockPath}.guard`;
19300
- const deadline = Date.now() + 5e3;
19301
- while (true) {
19302
- try {
19303
- await mkdir7(guardPath, { mode: 448 });
19304
- const markerName = `${process.pid}-${randomUUID2()}`;
19305
- const markerPath = join11(guardPath, markerName);
19306
- try {
19307
- await writeFile8(markerPath, `${markerName}
19308
- `, {
19309
- flag: "wx",
19310
- mode: 384
19311
- });
19312
- } catch (error) {
19313
- await rmdir(guardPath).catch(() => void 0);
19314
- throw error;
19315
- }
19316
- return async () => {
19317
- await unlink3(markerPath).catch((error) => {
19318
- if (!isNotFoundError(error)) throw error;
19319
- });
19320
- await rmdir(guardPath).catch((error) => {
19321
- if (!isNotFoundError(error)) throw error;
19322
- });
19323
- };
19324
- } catch (error) {
19325
- if (!isAlreadyExistsError(error)) throw error;
19326
- if (await reclaimAbandonedCloneGuard(guardPath)) continue;
19327
- if (Date.now() >= deadline) {
19328
- throw new CloneInProgressError(destination);
19329
- }
19330
- await new Promise((resolve13) => setTimeout(resolve13, 10));
19331
- }
19332
- }
19333
- }
19334
- async function reclaimAbandonedCloneGuard(guardPath) {
19335
- let expectedMarker;
19336
- try {
19337
- const entries = await readdir(guardPath);
19338
- if (entries.length > 1) return false;
19339
- expectedMarker = entries[0];
19340
- const freshnessPath = expectedMarker ? join11(guardPath, expectedMarker) : guardPath;
19341
- const stats = await lstat3(freshnessPath);
19342
- if (Date.now() - stats.mtimeMs < CLONE_GUARD_TTL_MS) return false;
19343
- if (expectedMarker) {
19344
- const ownerPid = Number(expectedMarker.split("-", 1)[0]);
19345
- if (!Number.isSafeInteger(ownerPid) || ownerPid <= 0) return false;
19346
- if (processIsAlive(ownerPid)) return false;
19347
- }
19348
- } catch (error) {
19349
- return isNotFoundError(error);
19350
- }
19351
- const stalePath = `${guardPath}.stale-${randomUUID2()}`;
19352
- try {
19353
- await rename3(guardPath, stalePath);
19354
- } catch (error) {
19355
- if (isNotFoundError(error)) return true;
19356
- throw error;
19357
- }
19358
- const movedEntries = await readdir(stalePath).catch(() => []);
19359
- if (movedEntries.length !== (expectedMarker ? 1 : 0) || expectedMarker && movedEntries[0] !== expectedMarker) {
19360
- await rename3(stalePath, guardPath).catch(() => void 0);
19361
- return false;
19362
- }
19363
- if (expectedMarker) await unlink3(join11(stalePath, expectedMarker));
19364
- await rmdir(stalePath);
19365
- return true;
19366
- }
19367
- function processIsAlive(pid) {
19368
- try {
19369
- process.kill(pid, 0);
19370
- return true;
19371
- } catch (error) {
19372
- return error.code === "EPERM";
19373
- }
19374
- }
19375
- async function publishCloneLock(lockPath, lockToken) {
19376
- try {
19377
- await mkdir7(lockPath, { mode: 448 });
19378
- } catch (error) {
19379
- if (isAlreadyExistsError(error)) return void 0;
19380
- throw error;
19381
- }
19382
- const markerPath = join11(lockPath, lockToken);
19383
- try {
19384
- await writeFile8(markerPath, `${lockToken}
19385
- `, { flag: "wx", mode: 384 });
19386
- return {
19387
- lockPath,
19388
- markerName: lockToken,
19389
- markerPath,
19390
- token: lockToken
19391
- };
19392
- } catch (error) {
19393
- await rmdir(lockPath).catch(() => void 0);
19394
- throw error;
19395
- }
19396
- }
19397
- async function inspectCloneLock(lockPath) {
19398
- try {
19399
- const stats = await lstat3(lockPath);
19400
- if (!stats.isDirectory() || stats.isSymbolicLink()) {
19401
- return { kind: "invalid", mtimeMs: stats.mtimeMs };
19402
- }
19403
- const entries = await readdir(lockPath);
19404
- if (entries.length === 0) return { kind: "empty", mtimeMs: stats.mtimeMs };
19405
- if (entries.length !== 1) {
19406
- return { kind: "invalid", mtimeMs: stats.mtimeMs };
19407
- }
19408
- const markerName = entries[0];
19409
- const token = markerName?.startsWith("owner-") ? markerName.slice("owner-".length) : markerName;
19410
- if (!token || !/^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/iu.test(token)) {
19411
- return { kind: "invalid", mtimeMs: stats.mtimeMs };
19412
- }
19413
- const markerPath = join11(lockPath, markerName);
19414
- const markerStats = await lstat3(markerPath);
19415
- if (!markerStats.isFile() || markerStats.isSymbolicLink()) {
19416
- return { kind: "invalid", mtimeMs: markerStats.mtimeMs };
19417
- }
19418
- if ((await readFile8(markerPath, "utf8")).trim() !== token) {
19419
- return { kind: "invalid", mtimeMs: markerStats.mtimeMs };
19420
- }
19421
- return {
19422
- kind: "owned",
19423
- lockPath,
19424
- markerName,
19425
- markerPath,
19426
- mtimeMs: markerStats.mtimeMs,
19427
- token
19428
- };
19429
- } catch (error) {
19430
- if (isNotFoundError(error)) throw error;
19431
- return { kind: "invalid", mtimeMs: Date.now() };
19432
- }
19433
- }
19434
- async function cleanupStaleCloneLock(lockPath, inspection) {
19435
- if (inspection.kind === "owned") {
19436
- await unlink3(join11(lockPath, inspection.markerName));
19437
- }
19438
- await rmdir(lockPath);
19439
- }
19440
- async function publishCloneContents(temporaryDestination, destination, reservationPath, onEntryPublished, copyFileEntry) {
19441
- const reservationName = basename6(reservationPath);
19442
- const destinationEntries = await readdir(destination);
19443
- if (destinationEntries.length !== 1 || destinationEntries[0] !== reservationName) {
19444
- throw new CloneDestinationExistsError(destination);
19445
- }
19446
- const temporaryEntries = await readdir(temporaryDestination);
19447
- for (const entry of temporaryEntries) {
19448
- await publishCloneEntry(
19449
- join11(temporaryDestination, entry),
19450
- join11(destination, entry),
19451
- (entry2) => onEntryPublished(entry2),
19452
- copyFileEntry
19453
- );
19454
- }
19455
- await unlink3(reservationPath);
19456
- }
19457
- async function publishCloneEntry(source, destination, onCreated, copyFileEntry) {
19458
- const sourceStats = await lstat3(source);
19459
- if (sourceStats.isDirectory()) {
19460
- try {
19461
- await mkdir7(destination);
19462
- } catch (error) {
19463
- if (isAlreadyExistsError(error)) {
19464
- throw new CloneDestinationExistsError(destination);
19465
- }
19466
- throw error;
19467
- }
19468
- onCreated(await inspectOwnedCloneEntry(destination, "directory"));
19469
- for (const entry of await readdir(source)) {
19470
- await publishCloneEntry(
19471
- join11(source, entry),
19472
- join11(destination, entry),
19473
- () => void 0,
19474
- copyFileEntry
19475
- );
19476
- }
19477
- await copyCloneMetadata(sourceStats, destination);
19478
- return;
19479
- }
19480
- if (sourceStats.isSymbolicLink()) {
19481
- try {
19482
- await symlink(await readlink(source), destination);
19483
- } catch (error) {
19484
- if (isAlreadyExistsError(error)) {
19485
- throw new CloneDestinationExistsError(destination);
19486
- }
19487
- throw error;
19488
- }
19489
- onCreated(await inspectOwnedCloneEntry(destination, "symlink"));
19490
- return;
19491
- }
19492
- if (!sourceStats.isFile()) {
19493
- throw new Error(`unsupported clone entry type: ${source}`);
19494
- }
19495
- try {
19496
- await copyFileEntry(source, destination);
19497
- } catch (error) {
19498
- if (isAlreadyExistsError(error)) {
19499
- throw new CloneDestinationExistsError(destination);
19070
+ const readLimit = Math.min(UV_VALIDATION_MAX_FILE_BYTES, remainingBytes);
19071
+ const contents = await readFilePrefix(handle, readLimit + 1);
19072
+ if (contents.byteLength > UV_VALIDATION_MAX_FILE_BYTES) {
19073
+ throw new Error(`uv launcher exceeds the validation size limit: ${path9}`);
19500
19074
  }
19501
- if (isUnsupportedCloneError(error)) {
19502
- throw new CloneUnsupportedError(toErrorMessage(error));
19075
+ if (contents.byteLength > remainingBytes) {
19076
+ throw new Error("uv launchers exceed the total validation size limit");
19503
19077
  }
19504
- throw error;
19078
+ const text = contents.toString("utf8");
19079
+ return looksLikeUtf8Text(contents, true) ? { bytes: contents.byteLength, text } : { bytes: contents.byteLength };
19080
+ } finally {
19081
+ await handle.close();
19505
19082
  }
19506
- onCreated(await inspectOwnedCloneEntry(destination, "file"));
19507
- await copyCloneMetadata(sourceStats, destination);
19508
- }
19509
- async function runForcedCloneFileCopy(source, destination) {
19510
- await cp(source, destination, {
19511
- errorOnExist: true,
19512
- force: false,
19513
- mode: constants5.COPYFILE_FICLONE_FORCE,
19514
- preserveTimestamps: true
19515
- });
19516
19083
  }
19517
- async function copyCloneMetadata(sourceStats, destination) {
19518
- await chmod(destination, Number(sourceStats.mode) & 4095);
19519
- await utimes(destination, sourceStats.atime, sourceStats.mtime);
19520
- }
19521
- async function reserveDestination(destination) {
19522
- try {
19523
- await mkdir7(destination);
19524
- } catch (error) {
19525
- if (isAlreadyExistsError(error)) {
19526
- throw new CloneDestinationExistsError(destination);
19527
- }
19528
- throw error;
19529
- }
19530
- const reservationPath = join11(
19531
- destination,
19532
- `.gji-clone-reservation-${randomUUID2()}`
19533
- );
19084
+ function looksLikeUtf8Text(contents, complete) {
19085
+ if (contents.includes(0)) return false;
19534
19086
  try {
19535
- await writeFile8(reservationPath, "gji clone reservation\n", {
19536
- flag: "wx"
19087
+ new TextDecoder("utf-8", { fatal: true }).decode(contents, {
19088
+ stream: !complete
19537
19089
  });
19538
- return await inspectOwnedCloneEntry(reservationPath, "file");
19539
- } catch (error) {
19540
- await rmdir(destination).catch(() => void 0);
19541
- throw error;
19542
- }
19543
- }
19544
- async function cleanupReservedDestination(destination, reservation, publishedEntries) {
19545
- for (const entry of [...publishedEntries, reservation].reverse()) {
19546
- await removeOwnedCloneEntry(entry);
19547
- }
19548
- await rmdir(destination).catch(() => void 0);
19549
- }
19550
- async function inspectOwnedCloneEntry(path9, kind) {
19551
- const stats = await lstat3(path9, { bigint: true });
19552
- return { dev: stats.dev, ino: stats.ino, kind, path: path9 };
19553
- }
19554
- async function removeOwnedCloneEntry(entry) {
19555
- try {
19556
- const current = await lstat3(entry.path, { bigint: true });
19557
- if (current.dev !== entry.dev || current.ino !== entry.ino) return;
19558
- if (entry.kind === "directory") await rmdir(entry.path);
19559
- else await unlink3(entry.path);
19560
- } catch {
19561
- }
19562
- }
19563
- async function destinationExists(path9) {
19564
- try {
19565
- await lstat3(path9);
19566
19090
  return true;
19567
- } catch (error) {
19568
- if (isNotFoundError(error)) return false;
19569
- throw error;
19570
- }
19571
- }
19572
- function startLockHeartbeat(lock) {
19573
- const timer = setInterval(() => {
19574
- void refreshCloneLock(lock);
19575
- }, CLONE_LOCK_TTL_MS / 3);
19576
- timer.unref?.();
19577
- return () => clearInterval(timer);
19578
- }
19579
- async function refreshCloneLock(lock) {
19580
- try {
19581
- const now = /* @__PURE__ */ new Date();
19582
- await utimes(lock.markerPath, now, now);
19583
19091
  } catch {
19092
+ return false;
19584
19093
  }
19585
19094
  }
19586
- async function releaseCloneLock(lock) {
19587
- const releaseGuard = await acquireCloneLockGuard(
19588
- lock.lockPath,
19589
- lock.lockPath
19590
- );
19591
- try {
19592
- await releaseCloneLockWithGuard(lock);
19593
- } finally {
19594
- await releaseGuard();
19595
- }
19596
- }
19597
- async function releaseCloneLockWithGuard(lock) {
19598
- try {
19599
- await unlink3(join11(lock.lockPath, lock.token));
19600
- } catch (error) {
19601
- if (!isNotFoundError(error)) throw error;
19602
- }
19603
- try {
19604
- await rmdir(lock.lockPath);
19605
- } catch (error) {
19606
- const code = "code" in error ? error.code : void 0;
19607
- if (!isNotFoundError(error) && code !== "ENOTEMPTY" && code !== "EEXIST") {
19608
- throw error;
19609
- }
19095
+ async function readFilePrefix(handle, maxBytes) {
19096
+ const contents = Buffer.allocUnsafe(maxBytes);
19097
+ let offset = 0;
19098
+ while (offset < contents.byteLength) {
19099
+ const { bytesRead } = await handle.read(
19100
+ contents,
19101
+ offset,
19102
+ contents.byteLength - offset,
19103
+ offset
19104
+ );
19105
+ if (bytesRead === 0) break;
19106
+ offset += bytesRead;
19610
19107
  }
19108
+ return contents.subarray(0, offset);
19611
19109
  }
19612
- function isUnsupportedCloneError(error) {
19613
- if (!(error instanceof Error)) return false;
19614
- const code = "code" in error ? error.code : "";
19615
- if (["EINVAL", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EXDEV"].includes(code ?? "")) {
19616
- return true;
19617
- }
19618
- return /reflink|unsupported|operation not supported|not supported|invalid cross-device|cross-device/iu.test(
19619
- error.message
19620
- );
19110
+ function isUvInterpreterName(name) {
19111
+ return /^python(?:\d+(?:\.\d+)?)?(?:\.exe)?$/iu.test(name);
19621
19112
  }
19622
- function toErrorMessage(error) {
19623
- return error instanceof Error ? error.message : String(error);
19113
+ function uniquePaths(paths) {
19114
+ return [...new Set(paths.filter((path9) => Boolean(path9)))];
19624
19115
  }
19625
19116
 
19626
19117
  // src/dependency-bootstrap.ts
19627
- var execFileAsync6 = promisify7(execFile6);
19628
- var UV_VALIDATION_MAX_ENTRIES = 1e4;
19629
- var UV_VALIDATION_MAX_FILE_BYTES = 1024 * 1024;
19630
- var UV_VALIDATION_MAX_TOTAL_BYTES = 16 * 1024 * 1024;
19118
+ function resolveDependencyBootstrapMode(dependencyBootstrap, noInstall = false) {
19119
+ if (noInstall || dependencyBootstrap === "off") return "off";
19120
+ return "install";
19121
+ }
19631
19122
  async function prepareDependencyBootstrap(mode, context) {
19632
19123
  if (mode === "off") return { mode, targets: [] };
19633
- const adapters = createBootstrapAdapters(
19634
- context.checkUvRuntime ?? defaultCheckUvRuntime,
19635
- context.cargoBuildCommand
19636
- );
19124
+ const adapters = createBootstrapAdapters(context.cargoBuildCommand);
19637
19125
  const sourceRoots = [];
19638
- for (const sourceRoot of uniquePaths([
19126
+ for (const sourceRoot of uniquePaths2([
19639
19127
  context.repoRoot,
19640
19128
  context.currentRoot
19641
19129
  ])) {
@@ -19647,8 +19135,6 @@ async function prepareDependencyBootstrap(mode, context) {
19647
19135
  const plannedRelativePaths = /* @__PURE__ */ new Set();
19648
19136
  for (const adapter of adapters) {
19649
19137
  if (plannedRelativePaths.has(adapter.relativePath)) continue;
19650
- let firstDetected;
19651
- let selected;
19652
19138
  for (const sourceRoot of sourceRoots) {
19653
19139
  const target = await adapter.detect({
19654
19140
  detectionRoot: context.detectionRoot,
@@ -19656,82 +19142,37 @@ async function prepareDependencyBootstrap(mode, context) {
19656
19142
  worktreePath: context.worktreePath
19657
19143
  });
19658
19144
  if (!target) continue;
19659
- const seedable = mode !== "install-only" && await adapter.canSeed(target);
19660
- const candidate = { target, seedable };
19661
- firstDetected ??= candidate;
19662
- if (mode === "install-only" || seedable) {
19663
- selected = candidate;
19664
- break;
19665
- }
19666
- }
19667
- const plannedTarget = selected ?? firstDetected;
19668
- if (plannedTarget) {
19669
- targets.push(plannedTarget);
19145
+ targets.push({ adapter, target });
19670
19146
  plannedRelativePaths.add(adapter.relativePath);
19147
+ break;
19671
19148
  }
19672
19149
  }
19673
19150
  return { mode, targets };
19674
19151
  }
19675
- async function detectDependencyBootstrapCandidate(context) {
19676
- const plan = await prepareDependencyBootstrap("cow-then-repair", context);
19677
- const planned = plan.targets[0];
19678
- if (!planned) return null;
19679
- return {
19680
- adapter: planned.target.adapter,
19681
- kind: planned.target.kind,
19682
- lockfile: adapterForTarget(planned.target).lockfile,
19683
- target: planned.target.relativePath,
19684
- repairCommand: planned.target.repairCommand,
19685
- seedable: planned.seedable
19686
- };
19687
- }
19688
19152
  function previewDependencyBootstrap(plan) {
19689
19153
  return {
19690
19154
  mode: plan.mode,
19691
- targets: plan.targets.map(({ target, seedable }) => ({
19692
- adapter: target.adapter,
19693
- kind: target.kind,
19155
+ targets: plan.targets.map(({ adapter, target }) => ({
19156
+ adapter: adapter.name,
19157
+ kind: adapter.kind,
19694
19158
  target: target.relativePath,
19695
- repairCommand: target.repairCommand,
19696
- seedable,
19697
- strategy: bootstrapStrategy(plan.mode, target, seedable)
19159
+ command: target.installCommand
19698
19160
  }))
19699
19161
  };
19700
19162
  }
19701
19163
  async function executeDependencyBootstrap(plan, options) {
19702
19164
  if (plan.mode === "off") return { mode: plan.mode, ready: true, events: [] };
19703
19165
  const events = [];
19704
- const cloneDirectory = options.cloneDirectory ?? cloneDir;
19705
19166
  const execution = {
19706
19167
  runCommand: options.runCommand ?? runCommand,
19707
19168
  stderr: options.stderr ?? (() => void 0),
19708
19169
  stdout: options.stdout ?? ((chunk) => process.stdout.write(chunk))
19709
19170
  };
19710
- const seededDirectories = new Set(options.seededDirectories ?? []);
19711
19171
  if (plan.targets.length === 0) {
19712
- recordBootstrapEvent(events, options.reporter, {
19713
- adapter: "none",
19714
- kind: "dependency",
19715
- reason: "no-lockfile",
19716
- state: "skipped",
19717
- target: "",
19718
- message: "no supported dependency or build-state lockfile was detected"
19719
- });
19720
19172
  return { mode: plan.mode, ready: true, events };
19721
19173
  }
19722
- for (const { target, seedable } of plan.targets) {
19723
- const adapter = adapterForTarget(target);
19724
- await executeBootstrapTarget(
19725
- adapter,
19726
- target,
19727
- seedable,
19728
- plan.mode,
19729
- cloneDirectory,
19730
- execution,
19731
- seededDirectories.has(target.relativePath),
19732
- events,
19733
- options.reporter
19734
- );
19174
+ for (const { adapter, target } of plan.targets) {
19175
+ await installTarget(adapter, target, execution, events, options.reporter);
19735
19176
  }
19736
19177
  return {
19737
19178
  mode: plan.mode,
@@ -19739,263 +19180,43 @@ async function executeDependencyBootstrap(plan, options) {
19739
19180
  events
19740
19181
  };
19741
19182
  }
19742
- async function executeBootstrapTarget(adapter, target, seedable, mode, cloneDirectory, execution, seededBySyncDirs, events, reporter) {
19743
- const destinationInspection = await inspectDestination(
19744
- target.worktreePath,
19745
- target.targetPath
19746
- );
19747
- if (destinationInspection.kind === "unsafe") {
19183
+ async function installTarget(adapter, target, execution, events, reporter) {
19184
+ const beforeInstallInspection = target.targetPath ? await inspectDestination(target.worktreePath, target.targetPath) : void 0;
19185
+ if (beforeInstallInspection?.kind === "unsafe") {
19748
19186
  recordBootstrapFailure(
19749
19187
  events,
19750
19188
  reporter,
19751
19189
  adapter,
19752
19190
  target,
19753
- destinationInspection.reason,
19191
+ beforeInstallInspection.reason,
19754
19192
  "destination-unsafe"
19755
19193
  );
19756
19194
  return;
19757
19195
  }
19758
- if (mode === "install-only") {
19759
- await repairTarget(
19760
- adapter,
19761
- target,
19762
- execution,
19763
- destinationInspection.kind === "exists" ? "preserve" : "clean",
19764
- events,
19765
- reporter
19766
- );
19767
- return;
19768
- }
19769
- let input = destinationInspection.kind === "exists" ? "preserve" : "clean";
19770
- const destinationExistsNow = destinationInspection.kind === "exists";
19771
- if (target.initialInput === "preserve" && destinationExistsNow) {
19772
- recordBootstrapEvent(events, reporter, {
19773
- adapter: adapter.name,
19774
- kind: adapter.kind,
19775
- reason: "target-exists",
19776
- state: "skipped",
19777
- target: target.relativePath,
19778
- message: "target already existed; using it as the repair input"
19779
- });
19780
- } else if (seededBySyncDirs && destinationExistsNow) {
19781
- if (seedable) {
19782
- input = "seed";
19783
- recordBootstrapEvent(events, reporter, {
19784
- adapter: adapter.name,
19785
- kind: adapter.kind,
19786
- reason: "generic-seed",
19787
- state: "seeded",
19788
- target: target.relativePath,
19789
- message: "reusing a seed created by syncDirs"
19790
- });
19791
- } else {
19792
- input = "preserve";
19793
- recordBootstrapEvent(events, reporter, {
19794
- adapter: adapter.name,
19795
- kind: adapter.kind,
19796
- reason: "generic-seed",
19797
- state: "repair-only",
19798
- target: target.relativePath,
19799
- message: "syncDirs created a generic target; this adapter uses repair without CoW"
19800
- });
19801
- }
19802
- } else if (destinationExistsNow) {
19803
- input = "preserve";
19804
- recordBootstrapEvent(events, reporter, {
19805
- adapter: adapter.name,
19806
- kind: adapter.kind,
19807
- reason: "destination-race",
19808
- state: "skipped",
19809
- target: target.relativePath,
19810
- message: "target appeared during bootstrap; preserving it as the repair input"
19811
- });
19812
- } else if (seedable) {
19813
- try {
19814
- await cloneDirectory(adapter.seedPath(target), target.targetPath, {
19815
- destinationRoot: target.worktreePath,
19816
- measureBytes: reporter.measureCloneSize
19817
- });
19818
- input = "seed";
19819
- recordBootstrapEvent(events, reporter, {
19820
- adapter: adapter.name,
19821
- kind: adapter.kind,
19822
- state: "seeded",
19823
- target: target.relativePath,
19824
- message: "seeded with copy-on-write"
19825
- });
19826
- } catch (error) {
19827
- if (isCloneDestinationExistsError(error)) {
19828
- input = "preserve";
19829
- recordBootstrapEvent(events, reporter, {
19830
- adapter: adapter.name,
19831
- kind: adapter.kind,
19832
- state: "skipped",
19833
- target: target.relativePath,
19834
- message: "target appeared during CoW seeding; preserving it as the repair input"
19835
- });
19836
- await repairTarget(adapter, target, execution, input, events, reporter);
19837
- return;
19838
- }
19839
- if (isCloneInProgressError(error)) {
19840
- recordBootstrapFailure(
19841
- events,
19842
- reporter,
19843
- adapter,
19844
- target,
19845
- `CoW seed is already in progress: ${toErrorMessage2(error)}`,
19846
- "clone-in-progress"
19847
- );
19848
- return;
19849
- }
19850
- const failedCloneInspection = await inspectDestination(
19851
- target.worktreePath,
19852
- target.targetPath
19853
- );
19854
- if (failedCloneInspection.kind === "unsafe") {
19855
- recordBootstrapFailure(
19856
- events,
19857
- reporter,
19858
- adapter,
19859
- target,
19860
- failedCloneInspection.reason,
19861
- "destination-unsafe"
19862
- );
19863
- return;
19864
- }
19865
- input = failedCloneInspection.kind === "exists" ? "preserve" : "clean";
19866
- const repairInput = input === "preserve" ? "the preserved target" : "an empty target";
19867
- recordBootstrapEvent(events, reporter, {
19868
- adapter: adapter.name,
19869
- kind: adapter.kind,
19870
- reason: "cow-seed-failed",
19871
- state: "repair-only",
19872
- target: target.relativePath,
19873
- message: `CoW seed failed; repairing from ${repairInput} (${toErrorMessage2(error)})`
19874
- });
19875
- }
19876
- } else {
19196
+ const effectiveInput = beforeInstallInspection?.kind === "exists" ? "preserve" : "clean";
19197
+ try {
19198
+ await adapter.install(target, { ...execution, input: effectiveInput });
19877
19199
  recordBootstrapEvent(events, reporter, {
19878
19200
  adapter: adapter.name,
19879
19201
  kind: adapter.kind,
19880
- reason: "seed-unavailable",
19881
- state: "repair-only",
19202
+ state: "installed",
19882
19203
  target: target.relativePath,
19883
- message: "CoW seed is unavailable; repairing from an empty target"
19204
+ message: installSuccessMessage(target, effectiveInput)
19884
19205
  });
19885
- }
19886
- await repairTarget(adapter, target, execution, input, events, reporter);
19887
- }
19888
- async function repairTarget(adapter, target, execution, input, events, reporter) {
19889
- const beforeRepairInspection = await inspectDestination(
19890
- target.worktreePath,
19891
- target.targetPath
19892
- );
19893
- if (beforeRepairInspection.kind === "unsafe") {
19206
+ } catch (error) {
19894
19207
  recordBootstrapFailure(
19895
19208
  events,
19896
19209
  reporter,
19897
19210
  adapter,
19898
19211
  target,
19899
- beforeRepairInspection.reason,
19900
- "destination-unsafe"
19901
- );
19902
- return;
19903
- }
19904
- const effectiveInput = beforeRepairInspection.kind === "exists" ? input === "seed" ? "seed" : "preserve" : "clean";
19905
- try {
19906
- await adapter.repair(target, { ...execution, input: effectiveInput });
19907
- recordBootstrapEvent(events, reporter, {
19908
- adapter: adapter.name,
19909
- kind: adapter.kind,
19910
- state: target.repairState,
19911
- target: target.relativePath,
19912
- message: repairSuccessMessage(target, effectiveInput)
19913
- });
19914
- } catch (firstError) {
19915
- const firstFailureContext = {
19916
- input: effectiveInput,
19917
- target
19918
- };
19919
- const cleanupError = await adapter.cleanupAfterRepairFailure(firstFailureContext).then(() => void 0).catch((error) => toErrorMessage2(error));
19920
- if (!adapter.shouldRetryAfterRepairFailure(firstFailureContext)) {
19921
- recordBootstrapFailure(
19922
- events,
19923
- reporter,
19924
- adapter,
19925
- target,
19926
- formatRepairFailure(
19927
- firstError,
19928
- cleanupError === void 0 ? void 0 : cleanupError
19929
- ),
19930
- "repair-failed"
19931
- );
19932
- return;
19933
- }
19934
- if (cleanupError) {
19935
- recordBootstrapFailure(
19936
- events,
19937
- reporter,
19938
- adapter,
19939
- target,
19940
- formatRepairFailure(firstError, cleanupError),
19941
- "repair-cleanup-failed"
19942
- );
19943
- return;
19944
- }
19945
- recordBootstrapEvent(events, reporter, {
19946
- adapter: adapter.name,
19947
- kind: adapter.kind,
19948
- reason: "seed-repair-failed",
19949
- state: "repair-only",
19950
- target: target.relativePath,
19951
- message: `seed repair failed; removed the seed and retrying clean (${toErrorMessage2(firstError)})`
19952
- });
19953
- const beforeRetryInspection = await inspectDestination(
19954
- target.worktreePath,
19955
- target.targetPath
19212
+ formatInstallFailure(error),
19213
+ "install-failed"
19956
19214
  );
19957
- if (beforeRetryInspection.kind === "unsafe") {
19958
- recordBootstrapFailure(
19959
- events,
19960
- reporter,
19961
- adapter,
19962
- target,
19963
- beforeRetryInspection.reason,
19964
- "destination-unsafe"
19965
- );
19966
- return;
19967
- }
19968
- const retryInput = beforeRetryInspection.kind === "exists" ? "preserve" : "clean";
19969
- try {
19970
- await adapter.repair(target, { ...execution, input: retryInput });
19971
- recordBootstrapEvent(events, reporter, {
19972
- adapter: adapter.name,
19973
- kind: adapter.kind,
19974
- reason: "repair-retry",
19975
- state: target.repairState,
19976
- target: target.relativePath,
19977
- message: repairSuccessMessage(target, retryInput)
19978
- });
19979
- } catch (secondError) {
19980
- const cleanupError2 = await adapter.cleanupAfterRepairFailure({
19981
- input: retryInput,
19982
- target
19983
- }).then(() => void 0).catch((error) => toErrorMessage2(error));
19984
- recordBootstrapFailure(
19985
- events,
19986
- reporter,
19987
- adapter,
19988
- target,
19989
- formatRepairFailure(secondError, cleanupError2),
19990
- "repair-failed"
19991
- );
19992
- }
19993
19215
  }
19994
19216
  }
19995
- function repairSuccessMessage(target, input) {
19996
- if (input === "seed") return "reused and repaired";
19997
- if (input === "preserve") return "repaired the existing target";
19998
- return target.repairState === "installed" ? "installed from a clean target" : "repaired from a clean target";
19217
+ function installSuccessMessage(_target, input) {
19218
+ if (input === "preserve") return "installed into the existing target";
19219
+ return "installed into a clean target";
19999
19220
  }
20000
19221
  function recordBootstrapFailure(events, reporter, adapter, target, message, reason) {
20001
19222
  recordBootstrapEvent(events, reporter, {
@@ -20011,169 +19232,156 @@ function recordBootstrapEvent(events, reporter, event) {
20011
19232
  events.push(event);
20012
19233
  reporter.dependency(event);
20013
19234
  }
20014
- function formatRepairFailure(error, cleanupError) {
20015
- const message = `repair failed: ${toErrorMessage2(error)}`;
20016
- return cleanupError ? `${message}; cleanup failed: ${cleanupError}` : message;
20017
- }
20018
- function bootstrapStrategy(mode, target, seedable) {
20019
- if (mode === "install-only" || target.repairState === "installed") {
20020
- return "install-only";
20021
- }
20022
- return seedable ? "cow-then-repair" : "repair-only";
20023
- }
20024
- function adapterForTarget(target) {
20025
- const adapter = createBootstrapAdapters(defaultCheckUvRuntime).find(
20026
- (candidate) => candidate.name === target.adapter
20027
- );
20028
- if (!adapter)
20029
- throw new Error(`unsupported bootstrap adapter: ${target.adapter}`);
20030
- return adapter;
19235
+ function formatInstallFailure(error) {
19236
+ return `install failed: ${toErrorMessage(error)}`;
20031
19237
  }
20032
- function createBootstrapAdapters(checkUvRuntime, cargoBuildCommand) {
19238
+ function createBootstrapAdapters(cargoBuildCommand) {
20033
19239
  return [
20034
19240
  new LockfileBootstrapAdapter({
20035
19241
  name: "pnpm",
20036
19242
  kind: "dependency",
20037
- lockfile: "pnpm-lock.yaml",
19243
+ lockfiles: ["pnpm-lock.yaml"],
20038
19244
  relativePath: "node_modules",
20039
- repairCommand: "pnpm install --frozen-lockfile",
20040
- shell: false,
20041
- beforeRepair: async (target, context) => {
20042
- if (context.input === "seed") {
20043
- await rm4(join12(target.targetPath, ".modules.yaml"), {
20044
- force: true
20045
- });
20046
- }
20047
- }
19245
+ installCommand: "pnpm install --frozen-lockfile"
20048
19246
  }),
20049
19247
  new LockfileBootstrapAdapter({
20050
19248
  name: "yarn",
20051
19249
  kind: "dependency",
20052
- lockfile: "yarn.lock",
19250
+ lockfiles: ["yarn.lock"],
20053
19251
  relativePath: "node_modules",
20054
- repairCommand: "yarn install --immutable",
20055
- shell: false,
20056
- beforeRepair: async (target, context) => {
20057
- if (context.input === "seed") {
20058
- await rm4(join12(target.targetPath, ".yarn-state.yml"), {
20059
- force: true
20060
- });
20061
- }
20062
- }
19252
+ installCommand: "yarn install --frozen-lockfile",
19253
+ selectInstallCommand: async (_target, _input) => await isYarnBerry(_target.detectionRoot ?? _target.sourceRoot) ? "yarn install --immutable" : "yarn install --frozen-lockfile"
19254
+ }),
19255
+ new LockfileBootstrapAdapter({
19256
+ name: "bun",
19257
+ kind: "dependency",
19258
+ lockfiles: ["bun.lock", "bun.lockb"],
19259
+ relativePath: "node_modules",
19260
+ installCommand: "bun install --frozen-lockfile"
20063
19261
  }),
20064
19262
  new LockfileBootstrapAdapter({
20065
19263
  name: "npm",
20066
19264
  kind: "dependency",
20067
- lockfile: "package-lock.json",
19265
+ lockfiles: ["package-lock.json"],
20068
19266
  relativePath: "node_modules",
20069
- repairCommand: "npm ci",
20070
- shell: false,
20071
- seedPolicy: "never",
20072
- selectRepairCommand: (_target, input) => input === "preserve" ? "npm install" : "npm ci",
20073
- repairState: "installed"
19267
+ installCommand: "npm ci",
19268
+ selectInstallCommand: (_target, input) => input === "preserve" ? "npm install" : "npm ci"
20074
19269
  }),
20075
19270
  new LockfileBootstrapAdapter({
20076
19271
  name: "bundler",
20077
19272
  kind: "dependency",
20078
- lockfile: "Gemfile.lock",
19273
+ lockfiles: ["Gemfile.lock"],
20079
19274
  relativePath: "vendor/bundle",
20080
- repairCommand: "bundle install",
20081
- shell: false,
19275
+ installCommand: "bundle install",
20082
19276
  commandOptions: () => ({
20083
19277
  env: { BUNDLE_PATH: "vendor/bundle" }
20084
- }),
20085
- beforeRepair: async (target, context) => {
20086
- if (context.input === "seed") {
20087
- await removeBundlerExtensionMarkers(target.targetPath);
20088
- }
20089
- }
19278
+ })
19279
+ }),
19280
+ new LockfileBootstrapAdapter({
19281
+ name: "poetry",
19282
+ kind: "dependency",
19283
+ lockfiles: ["poetry.lock"],
19284
+ relativePath: ".venv",
19285
+ installCommand: "poetry install --no-interaction",
19286
+ commandOptions: () => ({
19287
+ env: { POETRY_VIRTUALENVS_IN_PROJECT: "true" }
19288
+ })
20090
19289
  }),
20091
19290
  new LockfileBootstrapAdapter({
20092
19291
  name: "uv",
20093
19292
  kind: "dependency",
20094
- lockfile: "uv.lock",
19293
+ lockfiles: ["uv.lock"],
20095
19294
  relativePath: ".venv",
20096
- repairCommand: "uv sync --locked",
20097
- shell: false,
20098
- canSeedOverride: checkUvRuntime,
20099
- beforeRepair: validateUvStructure,
20100
- afterRepair: validateUvRelocation
19295
+ installCommand: "uv sync --locked",
19296
+ beforeInstall: validateUvStructure,
19297
+ afterInstall: validateUvInstallation
19298
+ }),
19299
+ new LockfileBootstrapAdapter({
19300
+ name: "pipenv",
19301
+ kind: "dependency",
19302
+ lockfiles: ["Pipfile.lock"],
19303
+ relativePath: ".venv",
19304
+ installCommand: "pipenv sync",
19305
+ commandOptions: () => ({
19306
+ env: { PIPENV_VENV_IN_PROJECT: "1" }
19307
+ })
19308
+ }),
19309
+ new LockfileBootstrapAdapter({
19310
+ name: "go",
19311
+ kind: "dependency",
19312
+ lockfiles: ["go.mod"],
19313
+ relativePath: "",
19314
+ installCommand: "go mod download"
19315
+ }),
19316
+ new LockfileBootstrapAdapter({
19317
+ name: "composer",
19318
+ kind: "dependency",
19319
+ lockfiles: ["composer.lock"],
19320
+ relativePath: "vendor",
19321
+ installCommand: "composer install --no-interaction --prefer-dist"
20101
19322
  }),
20102
19323
  new LockfileBootstrapAdapter({
20103
19324
  name: "cargo",
20104
19325
  kind: "build-cache",
20105
- lockfile: "Cargo.lock",
19326
+ lockfiles: ["Cargo.lock"],
20106
19327
  relativePath: "target",
20107
- repairCommand: cargoBuildCommand?.trim() || "cargo check",
19328
+ installCommand: cargoBuildCommand?.trim() || "cargo check",
20108
19329
  shell: Boolean(cargoBuildCommand)
20109
19330
  })
20110
19331
  ];
20111
19332
  }
20112
19333
  var LockfileBootstrapAdapter = class {
20113
19334
  kind;
20114
- lockfile;
20115
19335
  name;
20116
19336
  relativePath;
20117
- defaultRepairCommand;
19337
+ defaultInstallCommand;
19338
+ lockfiles;
20118
19339
  shell;
20119
- seedPolicy;
20120
- canSeedOverride;
20121
- beforeRepair;
20122
- afterRepair;
19340
+ beforeInstall;
19341
+ afterInstall;
20123
19342
  commandOptions;
20124
- selectRepairCommand;
20125
- repairState;
19343
+ selectInstallCommand;
20126
19344
  constructor(spec) {
20127
19345
  this.name = spec.name;
20128
19346
  this.kind = spec.kind;
20129
- this.lockfile = spec.lockfile;
19347
+ this.lockfiles = spec.lockfiles;
20130
19348
  this.relativePath = spec.relativePath;
20131
- this.defaultRepairCommand = spec.repairCommand;
20132
- this.shell = spec.shell;
20133
- this.seedPolicy = spec.seedPolicy ?? "always";
20134
- this.canSeedOverride = spec.canSeedOverride;
20135
- this.beforeRepair = spec.beforeRepair;
20136
- this.afterRepair = spec.afterRepair;
19349
+ this.defaultInstallCommand = spec.installCommand;
19350
+ this.shell = spec.shell ?? false;
19351
+ this.beforeInstall = spec.beforeInstall;
19352
+ this.afterInstall = spec.afterInstall;
20137
19353
  this.commandOptions = spec.commandOptions;
20138
- this.selectRepairCommand = spec.selectRepairCommand;
20139
- this.repairState = spec.repairState ?? "repaired";
19354
+ this.selectInstallCommand = spec.selectInstallCommand;
20140
19355
  }
20141
19356
  async detect(context) {
20142
19357
  const detectionRoot = context.detectionRoot ?? context.sourceRoot;
20143
- if (!await pathExists2(join12(detectionRoot, this.lockfile))) return null;
20144
- const sourcePath = join12(context.sourceRoot, this.relativePath);
20145
- const targetPath = join12(context.worktreePath, this.relativePath);
20146
- const destinationInspection = await inspectDestination(
20147
- context.worktreePath,
20148
- targetPath
20149
- );
20150
- const initialInput = destinationInspection.kind === "exists" ? "preserve" : "clean";
19358
+ const hasLockfile = await hasExistingPath(detectionRoot, this.lockfiles);
19359
+ if (!hasLockfile) return null;
19360
+ const sourcePath = this.relativePath ? join12(context.sourceRoot, this.relativePath) : void 0;
19361
+ const targetPath = this.relativePath ? join12(context.worktreePath, this.relativePath) : void 0;
19362
+ const destinationInspection = targetPath ? await inspectDestination(context.worktreePath, targetPath) : void 0;
19363
+ const initialInput = destinationInspection?.kind === "exists" ? "preserve" : "clean";
20151
19364
  const target = {
20152
- adapter: this.name,
20153
19365
  kind: this.kind,
20154
19366
  relativePath: this.relativePath,
20155
19367
  sourceRoot: context.sourceRoot,
19368
+ detectionRoot,
20156
19369
  worktreePath: context.worktreePath,
20157
19370
  sourcePath,
20158
19371
  targetPath,
20159
- repairCommand: this.defaultRepairCommand,
20160
- repairState: this.repairState,
20161
- shell: this.shell,
20162
- initialInput
19372
+ installCommand: this.defaultInstallCommand,
19373
+ shell: this.shell
20163
19374
  };
20164
19375
  return {
20165
19376
  ...target,
20166
- repairCommand: this.selectRepairCommand?.(target, initialInput) ?? target.repairCommand
19377
+ installCommand: await this.selectCommand(target, initialInput)
20167
19378
  };
20168
19379
  }
20169
- seedPath(target) {
20170
- return target.sourcePath;
20171
- }
20172
- async repair(target, context) {
20173
- await this.beforeRepair?.(target, context);
20174
- const repairCommand = this.selectRepairCommand?.(target, context.input) ?? target.repairCommand;
19380
+ async install(target, context) {
19381
+ await this.beforeInstall?.(target, context);
19382
+ const installCommand = await this.selectCommand(target, context.input);
20175
19383
  await context.runCommand(
20176
- repairCommand,
19384
+ installCommand,
20177
19385
  target.worktreePath,
20178
19386
  context.stderr,
20179
19387
  context.stdout,
@@ -20182,277 +19390,32 @@ var LockfileBootstrapAdapter = class {
20182
19390
  shell: target.shell
20183
19391
  }
20184
19392
  );
20185
- await this.afterRepair?.(target, context);
20186
- }
20187
- async canSeed(target) {
20188
- if (this.seedPolicy === "never") return false;
20189
- if (!await safeSourceDirectory(target)) return false;
20190
- return await this.canSeedOverride?.(target) ?? true;
20191
- }
20192
- shouldRetryAfterRepairFailure(context) {
20193
- return context.input === "seed";
19393
+ await this.afterInstall?.(target, context);
20194
19394
  }
20195
- async cleanupAfterRepairFailure(context) {
20196
- if (context.input !== "seed") return;
20197
- await rm4(context.target.targetPath, { force: true, recursive: true });
19395
+ async selectCommand(target, input) {
19396
+ return await this.selectInstallCommand?.(target, input) ?? target.installCommand;
20198
19397
  }
20199
19398
  };
20200
- async function safeSourceDirectory(target) {
20201
- try {
20202
- const [root, source] = await Promise.all([
20203
- realpath3(target.sourceRoot),
20204
- realpath3(target.sourcePath)
20205
- ]);
20206
- const sourceStats = await lstat4(source);
20207
- return sourceStats.isDirectory() && isWithin(root, source);
20208
- } catch {
20209
- return false;
19399
+ async function hasExistingPath(root, paths) {
19400
+ for (const path9 of paths) {
19401
+ if (await pathExists2(join12(root, path9))) return true;
20210
19402
  }
19403
+ return false;
20211
19404
  }
20212
- async function defaultCheckUvRuntime(target) {
19405
+ async function isYarnBerry(root) {
19406
+ if (await pathExists2(join12(root, ".yarnrc.yml"))) return true;
20213
19407
  try {
20214
- const config = await readFile9(
20215
- join12(target.sourcePath, "pyvenv.cfg"),
20216
- "utf8"
20217
- );
20218
- const expected = config.match(/^version(?:_info)?\s*=\s*(\d+\.\d+)/mu)?.[1];
20219
- if (!expected) return false;
20220
- const expectedImplementation = config.match(/^implementation\s*=\s*([^\s#]+)/imu)?.[1]?.toLowerCase();
20221
- const sourceInterpreter = join12(
20222
- target.sourcePath,
20223
- process.platform === "win32" ? "Scripts/python.exe" : "bin/python"
19408
+ const packageJson3 = JSON.parse(
19409
+ await readFile8(join12(root, "package.json"), "utf8")
20224
19410
  );
20225
- const fingerprintScript = "import platform, sys; print(f'{sys.version_info.major}.{sys.version_info.minor}|{platform.machine()}|{sys.implementation.name}')";
20226
- const source = await execFileAsync6(sourceInterpreter, [
20227
- "-c",
20228
- fingerprintScript
20229
- ]);
20230
- const sourceFingerprint = source.stdout.trim();
20231
- const [sourceVersion, sourceMachine, sourceImplementation, extra] = sourceFingerprint.split("|");
20232
- return extra === void 0 && sourceVersion === expected && Boolean(sourceMachine) && Boolean(sourceImplementation) && (expectedImplementation === void 0 || sourceImplementation.toLowerCase() === expectedImplementation);
19411
+ const packageManager = packageJson3.packageManager;
19412
+ const match = typeof packageManager === "string" && packageManager.match(/^yarn@(\d+)/u);
19413
+ return match ? Number(match[1]) >= 2 : false;
20233
19414
  } catch {
20234
19415
  return false;
20235
19416
  }
20236
19417
  }
20237
- function virtualEnvironmentPrefixes(text) {
20238
- const prefixes = [];
20239
- for (const line of text.split(/\r?\n/u)) {
20240
- if (!/\bVIRTUAL_ENV\b/u.test(line)) continue;
20241
- const quotedAssignment = line.match(
20242
- /\bVIRTUAL_ENV(?:\s*=|\s+)\s*(?:"([^"]+)"|'([^']+)')/u
20243
- );
20244
- const quotedPrefix = quotedAssignment?.[1] ?? quotedAssignment?.[2];
20245
- if (quotedPrefix && isAbsolute4(quotedPrefix)) prefixes.push(quotedPrefix);
20246
- const unquoted = line.match(/\bVIRTUAL_ENV(?:\s*=|\s+)\s*([^\s"']+)/u)?.[1];
20247
- if (unquoted && isAbsolute4(unquoted)) prefixes.push(unquoted);
20248
- }
20249
- return uniquePaths(prefixes);
20250
- }
20251
- function pythonPrefixFromShebang(text) {
20252
- const command = text.match(/^#!\s*([^\r\n]+)/u)?.[1]?.trim().split(/\s+/u)[0];
20253
- if (!command || !isAbsolute4(command)) return void 0;
20254
- if (!/^python(?:\d+(?:\.\d+)?)?(?:\.exe)?$/iu.test(
20255
- command.split(sep3).at(-1) ?? ""
20256
- )) {
20257
- return void 0;
20258
- }
20259
- return dirname9(dirname9(command));
20260
- }
20261
- function pythonPrefixesFromText(text) {
20262
- const interpreters = [];
20263
- for (const match of text.matchAll(/["'](\/[^"']+)["']/gu)) {
20264
- if (match[1]) interpreters.push(match[1]);
20265
- }
20266
- for (const match of text.matchAll(/(?:^|\s)(\/[^\s"']+)/gmu)) {
20267
- if (match[1]) interpreters.push(match[1]);
20268
- }
20269
- return uniquePaths(
20270
- interpreters.flatMap((interpreter) => {
20271
- const name = interpreter.split(sep3).at(-1) ?? "";
20272
- return /^python(?:\d+(?:\.\d+)?)?(?:\.exe)?$/iu.test(name) ? [dirname9(dirname9(interpreter))] : [];
20273
- })
20274
- );
20275
- }
20276
- async function validateUvRelocation(target, context) {
20277
- if (context.input === "preserve") return;
20278
- const scriptsDirectory = join12(
20279
- target.targetPath,
20280
- process.platform === "win32" ? "Scripts" : "bin"
20281
- );
20282
- const interpreter = join12(
20283
- scriptsDirectory,
20284
- process.platform === "win32" ? "python.exe" : "python"
20285
- );
20286
- const { stdout: stdout3 } = await execFileAsync6(interpreter, [
20287
- "-c",
20288
- "import os, sys; print(os.path.realpath(sys.prefix))"
20289
- ]);
20290
- const [actualPrefix, expectedPrefix] = await Promise.all([
20291
- realpath3(stdout3.trim()),
20292
- realpath3(target.targetPath)
20293
- ]);
20294
- if (actualPrefix !== expectedPrefix) {
20295
- throw new Error(
20296
- `uv environment still points to its source prefix: ${actualPrefix}`
20297
- );
20298
- }
20299
- const sourcePaths = uniquePaths([
20300
- target.sourcePath,
20301
- await realpath3(target.sourcePath).catch(() => void 0)
20302
- ]);
20303
- const entries = await readdir2(scriptsDirectory, { withFileTypes: true });
20304
- assertSafeUvScriptEntries(entries);
20305
- const environmentName = basename7(target.targetPath);
20306
- const acceptedPrefixes = /* @__PURE__ */ new Set([
20307
- resolve9(target.targetPath),
20308
- await realpath3(target.targetPath)
20309
- ]);
20310
- let validatedBytes = 0;
20311
- for (const path9 of [
20312
- join12(target.targetPath, "pyvenv.cfg"),
20313
- ...entries.filter((entry) => entry.isFile()).map((entry) => join12(scriptsDirectory, entry.name))
20314
- ]) {
20315
- const validated = await readBoundedUvTextFile(
20316
- path9,
20317
- UV_VALIDATION_MAX_TOTAL_BYTES - validatedBytes
20318
- );
20319
- if (!validated) continue;
20320
- validatedBytes += validated.bytes;
20321
- if (validated.text === void 0) continue;
20322
- const { text } = validated;
20323
- if (sourcePaths.some((sourcePath) => text.includes(sourcePath))) {
20324
- throw new Error(`uv environment contains a stale source path: ${path9}`);
20325
- }
20326
- const shebangPrefix = pythonPrefixFromShebang(text);
20327
- if (shebangPrefix && basename7(shebangPrefix) === environmentName && !acceptedPrefixes.has(resolve9(shebangPrefix))) {
20328
- throw new Error(`uv launcher points outside its environment: ${path9}`);
20329
- }
20330
- if (pythonPrefixesFromText(text).filter((prefix) => basename7(prefix) === environmentName).some((prefix) => !acceptedPrefixes.has(resolve9(prefix)))) {
20331
- throw new Error(`uv launcher points outside its environment: ${path9}`);
20332
- }
20333
- if (virtualEnvironmentPrefixes(text).some(
20334
- (prefix) => !acceptedPrefixes.has(resolve9(prefix))
20335
- )) {
20336
- throw new Error(
20337
- `uv activation script points outside its environment: ${path9}`
20338
- );
20339
- }
20340
- }
20341
- }
20342
- async function validateUvStructure(target, context) {
20343
- if (context.input === "preserve") return;
20344
- const targetStats = await lstat4(target.targetPath).catch(() => void 0);
20345
- if (!targetStats) return;
20346
- if (!targetStats.isDirectory() || targetStats.isSymbolicLink()) {
20347
- throw new Error("uv environment must be a real directory");
20348
- }
20349
- const configStats = await lstat4(join12(target.targetPath, "pyvenv.cfg")).catch(
20350
- () => void 0
20351
- );
20352
- if (configStats && (!configStats.isFile() || configStats.isSymbolicLink())) {
20353
- throw new Error("uv pyvenv.cfg must be a regular file");
20354
- }
20355
- const scriptsDirectory = join12(
20356
- target.targetPath,
20357
- process.platform === "win32" ? "Scripts" : "bin"
20358
- );
20359
- const scriptsStats = await lstat4(scriptsDirectory).catch(() => void 0);
20360
- if (!scriptsStats) return;
20361
- if (!scriptsStats.isDirectory() || scriptsStats.isSymbolicLink()) {
20362
- throw new Error("uv scripts path must be a real directory");
20363
- }
20364
- assertSafeUvScriptEntries(
20365
- await readdir2(scriptsDirectory, { withFileTypes: true })
20366
- );
20367
- }
20368
- function assertSafeUvScriptEntries(entries) {
20369
- if (entries.length > UV_VALIDATION_MAX_ENTRIES) {
20370
- throw new Error("uv scripts directory exceeds the validation entry limit");
20371
- }
20372
- for (const entry of entries) {
20373
- if (entry.isSymbolicLink() && !isUvInterpreterName(entry.name)) {
20374
- throw new Error(`uv script must not be a symbolic link: ${entry.name}`);
20375
- }
20376
- }
20377
- }
20378
- async function readBoundedUvTextFile(path9, remainingBytes) {
20379
- let handle;
20380
- try {
20381
- handle = await open3(
20382
- path9,
20383
- constants6.O_RDONLY | (process.platform === "win32" ? 0 : constants6.O_NOFOLLOW)
20384
- );
20385
- } catch (error) {
20386
- if (isNotFoundError(error)) return void 0;
20387
- throw error;
20388
- }
20389
- try {
20390
- const stats = await handle.stat();
20391
- if (!stats.isFile()) return void 0;
20392
- if (stats.size > UV_VALIDATION_MAX_FILE_BYTES) {
20393
- throw new Error(`uv launcher exceeds the validation size limit: ${path9}`);
20394
- }
20395
- if (stats.size > remainingBytes) {
20396
- throw new Error("uv launchers exceed the total validation size limit");
20397
- }
20398
- const readLimit = Math.min(UV_VALIDATION_MAX_FILE_BYTES, remainingBytes);
20399
- const contents = await readFilePrefix(handle, readLimit + 1);
20400
- if (contents.byteLength > UV_VALIDATION_MAX_FILE_BYTES) {
20401
- throw new Error(`uv launcher exceeds the validation size limit: ${path9}`);
20402
- }
20403
- if (contents.byteLength > remainingBytes) {
20404
- throw new Error("uv launchers exceed the total validation size limit");
20405
- }
20406
- const text = contents.toString("utf8");
20407
- return Buffer.from(text, "utf8").equals(contents) ? { bytes: contents.byteLength, text } : { bytes: contents.byteLength };
20408
- } finally {
20409
- await handle.close();
20410
- }
20411
- }
20412
- async function readFilePrefix(handle, maxBytes) {
20413
- const contents = Buffer.allocUnsafe(maxBytes);
20414
- let offset = 0;
20415
- while (offset < contents.byteLength) {
20416
- const { bytesRead } = await handle.read(
20417
- contents,
20418
- offset,
20419
- contents.byteLength - offset,
20420
- offset
20421
- );
20422
- if (bytesRead === 0) break;
20423
- offset += bytesRead;
20424
- }
20425
- return contents.subarray(0, offset);
20426
- }
20427
- function isUvInterpreterName(name) {
20428
- return /^python(?:\d+(?:\.\d+)?)?(?:\.exe)?$/iu.test(name);
20429
- }
20430
- async function removeBundlerExtensionMarkers(root) {
20431
- const pending = [root];
20432
- while (pending.length > 0) {
20433
- const current = pending.pop();
20434
- if (!current) continue;
20435
- let entries;
20436
- try {
20437
- entries = await readdir2(current, { withFileTypes: true });
20438
- } catch (error) {
20439
- if (isNotFoundError(error)) continue;
20440
- throw error;
20441
- }
20442
- for (const entry of entries) {
20443
- const path9 = join12(current, entry.name);
20444
- if (entry.isDirectory()) pending.push(path9);
20445
- else if (entry.isFile() && isBundlerExtensionMarker(root, path9)) {
20446
- await rm4(path9, { force: true });
20447
- }
20448
- }
20449
- }
20450
- }
20451
- function isBundlerExtensionMarker(root, path9) {
20452
- const segments = relative3(root, path9).split(sep3);
20453
- return segments.length >= 7 && segments[0] === "ruby" && segments[2] === "extensions" && segments.at(-1) === "gem.build_complete";
20454
- }
20455
- function uniquePaths(paths) {
19418
+ function uniquePaths2(paths) {
20456
19419
  return [...new Set(paths.filter((path9) => Boolean(path9)))];
20457
19420
  }
20458
19421
  async function isAllowedSourceRoot(repoRoot, sourceRoot) {
@@ -20462,7 +19425,7 @@ async function isAllowedSourceRoot(repoRoot, sourceRoot) {
20462
19425
  realpath3(sourceRoot)
20463
19426
  ]);
20464
19427
  if (resolvedRepoRoot === resolvedSourceRoot) return true;
20465
- const gitFile = await readFile9(join12(resolvedSourceRoot, ".git"), "utf8");
19428
+ const gitFile = await readFile8(join12(resolvedSourceRoot, ".git"), "utf8");
20466
19429
  const gitDirectory = gitFile.match(/^gitdir:\s*(.+)$/mu)?.[1];
20467
19430
  if (!gitDirectory) return false;
20468
19431
  return isWithin(
@@ -20474,10 +19437,10 @@ async function isAllowedSourceRoot(repoRoot, sourceRoot) {
20474
19437
  }
20475
19438
  }
20476
19439
  function isWithin(root, candidate) {
20477
- const distance = relative3(root, candidate);
19440
+ const distance = relative2(root, candidate);
20478
19441
  return distance === "" || distance !== ".." && !distance.startsWith(`..${sep3}`);
20479
19442
  }
20480
- function toErrorMessage2(error) {
19443
+ function toErrorMessage(error) {
20481
19444
  return error instanceof Error ? error.message : String(error);
20482
19445
  }
20483
19446
 
@@ -20490,27 +19453,17 @@ async function createDependencyBootstrapPreview(mode, context) {
20490
19453
  function formatDependencyBootstrapPreview(preview) {
20491
19454
  if (!preview) return "";
20492
19455
  return preview.targets.map(
20493
- ({ adapter, target, strategy }) => `Would ${formatBootstrapStrategy(strategy)} ${target} with ${adapter}
19456
+ ({ adapter, target, command }) => `Would install ${target || "dependencies"} with ${adapter}: ${command}
20494
19457
  `
20495
19458
  ).join("");
20496
19459
  }
20497
- function formatBootstrapStrategy(strategy) {
20498
- switch (strategy) {
20499
- case "cow-then-repair":
20500
- return "seed and repair";
20501
- case "repair-only":
20502
- return "repair";
20503
- case "install-only":
20504
- return "install";
20505
- }
20506
- }
20507
19460
 
20508
19461
  // src/conflict.ts
20509
- import { constants as constants7 } from "node:fs";
19462
+ import { constants as constants6 } from "node:fs";
20510
19463
  import { access as access7 } from "node:fs/promises";
20511
19464
  async function pathExists3(path9) {
20512
19465
  try {
20513
- await access7(path9, constants7.F_OK);
19466
+ await access7(path9, constants6.F_OK);
20514
19467
  return true;
20515
19468
  } catch {
20516
19469
  return false;
@@ -20525,257 +19478,41 @@ async function promptForPathConflict(path9) {
20525
19478
  label: "Abort",
20526
19479
  hint: "Keep the existing directory untouched"
20527
19480
  },
20528
- {
20529
- value: "reuse",
20530
- label: "Reuse path",
20531
- hint: "Print the existing path and stop"
20532
- }
20533
- ]
20534
- });
20535
- if (pD(choice)) {
20536
- return "abort";
20537
- }
20538
- return choice;
20539
- }
20540
-
20541
- // src/dependency-bootstrap-prompt.ts
20542
- async function resolveDependencyBootstrapPolicy(context, config, dependencyBootstrapExplicit, options) {
20543
- if (dependencyBootstrapExplicit) {
20544
- return {
20545
- mode: config.dependencyBootstrap ?? "off",
20546
- prompted: false,
20547
- source: "explicit"
20548
- };
20549
- }
20550
- if (options.dryRun || options.nonInteractive || isHeadless()) {
20551
- return { mode: "off", prompted: false, source: "default" };
20552
- }
20553
- if (options.legacyInstallPromptConfigured && !options.dependencies?.promptForDependencyBootstrap) {
20554
- return { mode: "off", prompted: false, source: "legacy" };
20555
- }
20556
- let candidate;
20557
- try {
20558
- candidate = await detectDependencyBootstrapCandidate(context);
20559
- } catch (error) {
20560
- options.stderr(
20561
- `gji: dependency setup detection failed: ${toErrorMessage3(error)}
20562
- `
20563
- );
20564
- return { mode: "off", prompted: false, source: "default" };
20565
- }
20566
- if (!candidate) return { mode: "off", prompted: false, source: "default" };
20567
- const prompt = options.dependencies?.promptForDependencyBootstrap ?? defaultPromptForDependencyBootstrap;
20568
- const choice = await prompt(candidate);
20569
- if (choice === null)
20570
- return { mode: "off", prompted: true, source: "prompted" };
20571
- const mode = choice;
20572
- await persistDependencyBootstrapPolicy(
20573
- context.repoRoot,
20574
- config,
20575
- mode,
20576
- options.dependencies,
20577
- options.stderr
20578
- );
20579
- return { mode, prompted: true, source: "prompted" };
20580
- }
20581
- async function persistDependencyBootstrapPolicy(repoRoot, config, mode, dependencies, stderr) {
20582
- const writeLocal = dependencies?.writeConfigKey ?? (async (root, key, value) => {
20583
- await updateLocalConfigKey(root, key, value);
20584
- });
20585
- const writeGlobal = dependencies?.writeGlobalRepoConfigKey ?? (async (root, key, value) => {
20586
- await updateGlobalRepoConfigKey(root, key, value);
20587
- });
20588
- try {
20589
- if (config.installSaveTarget === "global") {
20590
- await writeGlobal(repoRoot, "dependencyBootstrap", mode);
20591
- } else {
20592
- await writeLocal(repoRoot, "dependencyBootstrap", mode);
20593
- }
20594
- } catch (error) {
20595
- stderr(
20596
- `gji: failed to save dependencyBootstrap: ${toErrorMessage3(error)}
20597
- `
20598
- );
20599
- }
20600
- }
20601
- async function defaultPromptForDependencyBootstrap(candidate) {
20602
- const reuseHint = formatReuseHint(candidate);
20603
- const subject = candidate.kind === "build-cache" ? "dependencies and build state" : "dependencies";
20604
- const choice = await ve({
20605
- message: `Set up ${candidate.adapter} ${subject} for new worktrees? (${candidate.lockfile})`,
20606
- options: [
20607
- {
20608
- value: "cow-then-repair",
20609
- label: "Reuse and repair (recommended)",
20610
- hint: reuseHint
20611
- },
20612
- {
20613
- value: "install-only",
20614
- label: "Install fresh each time",
20615
- hint: candidate.repairCommand
20616
- },
20617
- {
20618
- value: "off",
20619
- label: "Skip dependency setup",
20620
- hint: "leave dependencies to project hooks or manual setup"
20621
- }
20622
- ]
20623
- });
20624
- if (pD(choice)) return null;
20625
- return choice;
20626
- }
20627
- function formatReuseHint(candidate) {
20628
- if (candidate.adapter === "pnpm") {
20629
- return "reuse local node_modules through CoW, then run pnpm install --frozen-lockfile";
20630
- }
20631
- if (candidate.adapter === "npm") {
20632
- return "npm uses a clean install because node_modules is not seeded";
20633
- }
20634
- if (!candidate.seedable) {
20635
- return `repair ${candidate.target} with ${candidate.repairCommand}`;
20636
- }
20637
- return `reuse ${candidate.target} through CoW, then run ${candidate.repairCommand}`;
20638
- }
20639
- function toErrorMessage3(error) {
20640
- return error instanceof Error ? error.message : String(error);
20641
- }
20642
-
20643
- // src/sync-plan.ts
20644
- import { lstat as lstat5, realpath as realpath4 } from "node:fs/promises";
20645
- import { isAbsolute as isAbsolute5, join as join13, relative as relative4, resolve as resolve10, sep as sep4 } from "node:path";
20646
- async function prepareSyncDirectoryPlan(repoRoot, worktreePath, directories) {
20647
- const normalizedDirectories = directories.map(validateSyncDirPattern).sort(directoryDepthAscending);
20648
- const plan = [];
20649
- for (const directory of normalizedDirectories) {
20650
- const destination = join13(worktreePath, directory);
20651
- const destinationState = await inspectDestination(
20652
- worktreePath,
20653
- destination
20654
- );
20655
- const destinationWasPresent = destinationState.kind === "exists";
20656
- const destinationWarning = destinationState.kind === "unsafe" ? destinationState.reason : void 0;
20657
- try {
20658
- const source = await resolveSyncDirectorySource(repoRoot, directory);
20659
- if (!source) {
20660
- plan.push({
20661
- directory,
20662
- destination,
20663
- worktreePath,
20664
- destinationWasPresent,
20665
- destinationWarning
20666
- });
20667
- } else if ("warning" in source) {
20668
- plan.push({
20669
- directory,
20670
- destination,
20671
- worktreePath,
20672
- destinationWasPresent,
20673
- destinationWarning,
20674
- warning: source.warning
20675
- });
20676
- } else {
20677
- plan.push({
20678
- directory,
20679
- destination,
20680
- worktreePath,
20681
- destinationWasPresent,
20682
- destinationWarning,
20683
- source: source.path
20684
- });
20685
- }
20686
- } catch (error) {
20687
- plan.push({
20688
- directory,
20689
- destination,
20690
- worktreePath,
20691
- destinationWasPresent,
20692
- destinationWarning,
20693
- warning: `could not inspect ${directory}: ${toErrorMessage4(error)}`
20694
- });
20695
- }
20696
- }
20697
- return plan;
20698
- }
20699
- async function estimateSyncDirectoryPlan(plan) {
20700
- const estimates = [];
20701
- for (const entry of plan) {
20702
- if (entry.destinationWasPresent || entry.destinationWarning || !entry.source || entry.warning || isCoveredByCloneAncestor(entry, plan)) {
20703
- continue;
20704
- }
20705
- try {
20706
- estimates.push({
20707
- bytes: await directorySize(entry.source),
20708
- dir: entry.directory
20709
- });
20710
- } catch {
20711
- }
20712
- }
20713
- return estimates;
20714
- }
20715
- async function estimateSyncDirectories(repoRoot, worktreePath, directories) {
20716
- const plan = await prepareSyncDirectoryPlan(
20717
- repoRoot,
20718
- worktreePath,
20719
- directories
20720
- );
20721
- return estimateSyncDirectoryPlan(plan);
20722
- }
20723
- async function resolveSyncDirectorySource(repoRoot, directory) {
20724
- const source = join13(repoRoot, directory);
20725
- let resolvedSource;
20726
- try {
20727
- resolvedSource = await realpath4(source);
20728
- } catch (error) {
20729
- if (isNotFoundError(error)) return null;
20730
- throw error;
20731
- }
20732
- if (!isPathInside2(repoRoot, resolvedSource)) {
20733
- return {
20734
- warning: `source symlink resolves outside the repository (${resolvedSource})`
20735
- };
19481
+ {
19482
+ value: "reuse",
19483
+ label: "Reuse path",
19484
+ hint: "Print the existing path and stop"
19485
+ }
19486
+ ]
19487
+ });
19488
+ if (pD(choice)) {
19489
+ return "abort";
20736
19490
  }
20737
- const stats = await lstat5(resolvedSource);
20738
- if (!stats.isDirectory()) return { warning: "source is not a directory" };
20739
- return { path: resolvedSource };
20740
- }
20741
- function isCoveredByCloneAncestor(entry, plan) {
20742
- return plan.some(
20743
- (candidate) => candidate !== entry && !candidate.destinationWasPresent && !candidate.destinationWarning && !!candidate.source && isPathInside2(candidate.directory, entry.directory)
20744
- );
20745
- }
20746
- function directoryDepthAscending(left, right) {
20747
- return left.split(/[\\/]+/u).length - right.split(/[\\/]+/u).length;
20748
- }
20749
- function isPathInside2(parent, child) {
20750
- const relativePath = relative4(resolve10(parent), resolve10(child));
20751
- return relativePath !== "" && !isAbsolute5(relativePath) && relativePath !== ".." && !relativePath.startsWith(`..${sep4}`);
20752
- }
20753
- function toErrorMessage4(error) {
20754
- return error instanceof Error ? error.message : String(error);
19491
+ return choice;
20755
19492
  }
20756
19493
 
20757
19494
  // src/worktree-bootstrap.ts
20758
- import { basename as basename9 } from "node:path";
19495
+ import { basename as basename8 } from "node:path";
20759
19496
 
20760
19497
  // src/file-sync.ts
20761
- import { constants as constants8 } from "node:fs";
20762
- import { copyFile, lstat as lstat6, realpath as realpath5 } from "node:fs/promises";
19498
+ import { constants as constants7 } from "node:fs";
19499
+ import { copyFile, lstat as lstat4, realpath as realpath4 } from "node:fs/promises";
20763
19500
  import {
20764
- basename as basename8,
20765
- dirname as dirname10,
20766
- isAbsolute as isAbsolute6,
20767
- join as join14,
20768
- normalize as normalize2,
20769
- relative as relative5,
20770
- resolve as resolve11,
20771
- sep as sep5
19501
+ basename as basename7,
19502
+ dirname as dirname9,
19503
+ isAbsolute as isAbsolute4,
19504
+ join as join13,
19505
+ normalize,
19506
+ relative as relative3,
19507
+ resolve as resolve10,
19508
+ sep as sep4
20772
19509
  } from "node:path";
20773
19510
  async function syncFiles(mainRoot, targetPath, patterns) {
20774
19511
  for (const pattern of patterns) {
20775
19512
  const normalized = validateSyncFilePattern(pattern);
20776
19513
  const sourcePath = await resolveSyncFileSource(mainRoot, normalized);
20777
19514
  if (!sourcePath) continue;
20778
- const destPath = join14(targetPath, normalized);
19515
+ const destPath = join13(targetPath, normalized);
20779
19516
  const existingDestination = await readDestinationEntry(destPath);
20780
19517
  if (existingDestination?.isSymbolicLink()) {
20781
19518
  throw new Error(`destination is a symbolic link: ${destPath}`);
@@ -20783,7 +19520,7 @@ async function syncFiles(mainRoot, targetPath, patterns) {
20783
19520
  if (existingDestination) {
20784
19521
  continue;
20785
19522
  }
20786
- const destinationParent = dirname10(destPath);
19523
+ const destinationParent = dirname9(destPath);
20787
19524
  const beforeCreate = await inspectDestination(
20788
19525
  targetPath,
20789
19526
  destinationParent
@@ -20796,14 +19533,14 @@ async function syncFiles(mainRoot, targetPath, patterns) {
20796
19533
  destinationParent
20797
19534
  );
20798
19535
  try {
20799
- const safeDestination = join14(safeParent.path, basename8(destPath));
19536
+ const safeDestination = join13(safeParent.path, basename7(destPath));
20800
19537
  const safeExistingDestination = await readDestinationEntry(safeDestination);
20801
19538
  if (safeExistingDestination?.isSymbolicLink()) {
20802
19539
  throw new Error(`destination is a symbolic link: ${destPath}`);
20803
19540
  }
20804
19541
  if (safeExistingDestination) continue;
20805
19542
  try {
20806
- await copyFile(sourcePath, safeDestination, constants8.COPYFILE_EXCL);
19543
+ await copyFile(sourcePath, safeDestination, constants7.COPYFILE_EXCL);
20807
19544
  } catch (error) {
20808
19545
  if (!isAlreadyExistsError(error)) throw error;
20809
19546
  }
@@ -20813,12 +19550,12 @@ async function syncFiles(mainRoot, targetPath, patterns) {
20813
19550
  }
20814
19551
  }
20815
19552
  function validateSyncFilePattern(pattern) {
20816
- if (isAbsolute6(pattern)) {
19553
+ if (isAbsolute4(pattern)) {
20817
19554
  throw new Error(
20818
19555
  `syncFiles: pattern must be a relative path, got: ${pattern}`
20819
19556
  );
20820
19557
  }
20821
- const normalized = normalize2(pattern);
19558
+ const normalized = normalize(pattern);
20822
19559
  if (normalized.startsWith("..")) {
20823
19560
  throw new Error(
20824
19561
  `syncFiles: pattern must not contain '..' segments, got: ${pattern}`
@@ -20831,424 +19568,42 @@ async function resolveSyncFileSource(mainRoot, pattern) {
20831
19568
  let resolvedSource;
20832
19569
  try {
20833
19570
  [resolvedRoot, resolvedSource] = await Promise.all([
20834
- realpath5(mainRoot),
20835
- realpath5(join14(mainRoot, pattern))
19571
+ realpath4(mainRoot),
19572
+ realpath4(join13(mainRoot, pattern))
20836
19573
  ]);
20837
19574
  } catch (error) {
20838
19575
  if (isNotFoundError(error)) return void 0;
20839
19576
  throw error;
20840
19577
  }
20841
- if (!isPathInside3(resolvedRoot, resolvedSource)) {
19578
+ if (!isPathInside2(resolvedRoot, resolvedSource)) {
20842
19579
  throw new Error(
20843
19580
  `syncFiles: source symlink resolves outside the repository: ${resolvedSource}`
20844
19581
  );
20845
19582
  }
20846
- const sourceStats = await lstat6(resolvedSource);
19583
+ const sourceStats = await lstat4(resolvedSource);
20847
19584
  if (!sourceStats.isFile()) {
20848
19585
  throw new Error(
20849
- `syncFiles: source is not a file: ${join14(mainRoot, pattern)}`
19586
+ `syncFiles: source is not a file: ${join13(mainRoot, pattern)}`
20850
19587
  );
20851
19588
  }
20852
19589
  return resolvedSource;
20853
19590
  }
20854
19591
  async function readDestinationEntry(path9) {
20855
19592
  try {
20856
- return await lstat6(path9);
19593
+ return await lstat4(path9);
20857
19594
  } catch (error) {
20858
19595
  if (isNotFoundError(error)) return void 0;
20859
19596
  throw error;
20860
19597
  }
20861
19598
  }
20862
- function isPathInside3(root, candidate) {
20863
- const distance = relative5(resolve11(root), resolve11(candidate));
20864
- return distance === "" || !isAbsolute6(distance) && distance !== ".." && !distance.startsWith(`..${sep5}`);
20865
- }
20866
-
20867
- // src/package-manager.ts
20868
- import { access as access8, readdir as readdir3 } from "node:fs/promises";
20869
- import { join as join15 } from "node:path";
20870
- var ENTRIES = [
20871
- // JavaScript / TypeScript
20872
- { name: "pnpm", signals: ["pnpm-lock.yaml"], command: "pnpm install" },
20873
- { name: "yarn", signals: ["yarn.lock"], command: "yarn install" },
20874
- { name: "bun", signals: ["bun.lockb"], command: "bun install" },
20875
- { name: "npm", signals: ["package-lock.json"], command: "npm install" },
20876
- { name: "deno", signals: ["deno.json", "deno.jsonc"], command: "deno cache" },
20877
- // Python
20878
- { name: "poetry", signals: ["poetry.lock"], command: "poetry install" },
20879
- { name: "uv", signals: ["uv.lock"], command: "uv sync" },
20880
- { name: "pipenv", signals: ["Pipfile.lock"], command: "pipenv install" },
20881
- { name: "pdm", signals: ["pdm.lock"], command: "pdm install" },
20882
- {
20883
- name: "conda-lock",
20884
- signals: ["conda-lock.yml"],
20885
- command: "conda-lock install"
20886
- },
20887
- {
20888
- name: "conda",
20889
- signals: ["environment.yml"],
20890
- command: "conda env update --file environment.yml"
20891
- },
20892
- // R
20893
- {
20894
- name: "renv",
20895
- signals: ["renv.lock"],
20896
- command: "Rscript -e 'renv::restore()'"
20897
- },
20898
- // Rust
20899
- { name: "cargo", signals: ["Cargo.lock"], command: "cargo build" },
20900
- // Go
20901
- { name: "go", signals: ["go.sum"], command: "go mod download" },
20902
- // Ruby
20903
- { name: "bundler", signals: ["Gemfile.lock"], command: "bundle install" },
20904
- // PHP
20905
- { name: "composer", signals: ["composer.lock"], command: "composer install" },
20906
- // Elixir / Erlang
20907
- { name: "mix", signals: ["mix.lock"], command: "mix deps.get" },
20908
- { name: "rebar3", signals: ["rebar.lock"], command: "rebar3 deps" },
20909
- // Dart / Flutter
20910
- { name: "dart", signals: ["pubspec.lock"], command: "dart pub get" },
20911
- // Java / Kotlin / Scala
20912
- { name: "maven", signals: ["pom.xml"], command: "mvn install" },
20913
- { name: "gradle", signals: ["gradlew"], command: "./gradlew build" },
20914
- {
20915
- name: "gradle",
20916
- signals: ["build.gradle", "build.gradle.kts"],
20917
- command: "gradle build"
20918
- },
20919
- { name: "sbt", signals: ["build.sbt"], command: "sbt compile" },
20920
- // .NET (C# / F# / VB)
20921
- {
20922
- name: "dotnet",
20923
- signals: ["*.sln", "*.csproj", "*.fsproj", "*.vbproj"],
20924
- command: "dotnet restore",
20925
- glob: true
20926
- },
20927
- // Swift
20928
- {
20929
- name: "swift",
20930
- signals: ["Package.swift"],
20931
- command: "swift package resolve"
20932
- },
20933
- // Haskell
20934
- { name: "stack", signals: ["stack.yaml"], command: "stack build" },
20935
- {
20936
- name: "cabal",
20937
- signals: ["cabal.project"],
20938
- command: "cabal install --only-dependencies"
20939
- },
20940
- {
20941
- name: "cabal",
20942
- signals: ["*.cabal"],
20943
- command: "cabal install --only-dependencies",
20944
- glob: true
20945
- },
20946
- // Clojure
20947
- { name: "clojure", signals: ["deps.edn"], command: "clojure -P" },
20948
- { name: "leiningen", signals: ["project.clj"], command: "lein deps" },
20949
- // OCaml
20950
- { name: "dune", signals: ["dune-project"], command: "dune build" },
20951
- // Julia
20952
- {
20953
- name: "julia",
20954
- signals: ["Manifest.toml"],
20955
- command: "julia --project -e 'using Pkg; Pkg.instantiate()'"
20956
- },
20957
- // Nim
20958
- {
20959
- name: "nimble",
20960
- signals: ["*.nimble"],
20961
- command: "nimble install",
20962
- glob: true
20963
- },
20964
- // Crystal
20965
- { name: "shards", signals: ["shard.yml"], command: "shards install" },
20966
- // Perl
20967
- { name: "cpanm", signals: ["cpanfile"], command: "cpanm --installdeps ." },
20968
- // Zig
20969
- { name: "zig", signals: ["build.zig.zon"], command: "zig build" },
20970
- // C / C++
20971
- { name: "vcpkg", signals: ["vcpkg.json"], command: "vcpkg install" },
20972
- {
20973
- name: "conan",
20974
- signals: ["conanfile.py", "conanfile.txt"],
20975
- command: "conan install ."
20976
- },
20977
- // Nix
20978
- { name: "nix", signals: ["flake.nix"], command: "nix develop" },
20979
- { name: "nix-shell", signals: ["shell.nix"], command: "nix-shell" },
20980
- // Terraform / OpenTofu
20981
- {
20982
- name: "terraform",
20983
- signals: ["terraform.lock.hcl"],
20984
- command: "terraform init"
20985
- }
20986
- ];
20987
- async function detectPackageManager(repoRoot) {
20988
- for (const entry of ENTRIES) {
20989
- const matched = entry.glob ? await matchesGlob(repoRoot, entry.signals) : await matchesExact(repoRoot, entry.signals);
20990
- if (matched) {
20991
- return { name: entry.name, installCommand: entry.command };
20992
- }
20993
- }
20994
- return null;
20995
- }
20996
- async function matchesExact(repoRoot, signals) {
20997
- for (const signal of signals) {
20998
- try {
20999
- await access8(join15(repoRoot, signal));
21000
- return true;
21001
- } catch {
21002
- }
21003
- }
21004
- return false;
21005
- }
21006
- async function matchesGlob(repoRoot, patterns) {
21007
- let files;
21008
- try {
21009
- files = await readdir3(repoRoot);
21010
- } catch {
21011
- return false;
21012
- }
21013
- const regexes = patterns.map(patternToRegex);
21014
- return files.some((file) => regexes.some((re) => re.test(file)));
21015
- }
21016
- function patternToRegex(pattern) {
21017
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*");
21018
- return new RegExp(`^${escaped}$`);
21019
- }
21020
-
21021
- // src/install-prompt.ts
21022
- async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dependencies = {}, nonInteractive = false) {
21023
- if (isHeadless() || nonInteractive) {
21024
- return;
21025
- }
21026
- if (extractHooks(config)["after-create"]) {
21027
- return;
21028
- }
21029
- if (config.skipInstallPrompt === true) {
21030
- return;
21031
- }
21032
- const detect = dependencies.detectInstallPackageManager ?? detectPackageManager;
21033
- const pm = await detect(worktreePath);
21034
- if (!pm) {
21035
- return;
21036
- }
21037
- const prompt = dependencies.promptForInstallChoice ?? defaultPromptForInstallChoice;
21038
- const choice = await prompt(pm);
21039
- if (!choice || choice === "no") {
21040
- return;
21041
- }
21042
- if (choice === "yes" || choice === "always") {
21043
- const runner = dependencies.runInstallCommand ?? runInstallCommand;
21044
- try {
21045
- await runner(pm.installCommand, worktreePath, stderr);
21046
- } catch (error) {
21047
- stderr(
21048
- `gji: install command failed: ${error instanceof Error ? error.message : String(error)}
21049
- `
21050
- );
21051
- }
21052
- }
21053
- const saveGlobal = config.installSaveTarget === "global";
21054
- const writeKey = dependencies.writeConfigKey ?? defaultWriteConfigKey;
21055
- const writeGlobalKey = dependencies.writeGlobalRepoConfigKey ?? defaultWriteGlobalRepoConfigKey;
21056
- if (choice === "always") {
21057
- try {
21058
- if (saveGlobal) {
21059
- const existingRaw = await loadExistingGlobalRepoHooks(repoRoot);
21060
- const existing = extractHooks({ hooks: existingRaw });
21061
- await writeGlobalKey(repoRoot, "hooks", {
21062
- ...existing["after-enter"] !== void 0 && {
21063
- "after-enter": existing["after-enter"]
21064
- },
21065
- ...existing["before-remove"] !== void 0 && {
21066
- "before-remove": existing["before-remove"]
21067
- },
21068
- "after-create": pm.installCommand
21069
- });
21070
- } else {
21071
- const { config: localConfig } = await loadConfig(repoRoot);
21072
- const existing = extractHooks(localConfig);
21073
- await writeKey(repoRoot, "hooks", {
21074
- ...existing["after-enter"] !== void 0 && {
21075
- "after-enter": existing["after-enter"]
21076
- },
21077
- ...existing["before-remove"] !== void 0 && {
21078
- "before-remove": existing["before-remove"]
21079
- },
21080
- "after-create": pm.installCommand
21081
- });
21082
- }
21083
- } catch (error) {
21084
- stderr(
21085
- `gji: failed to save config: ${error instanceof Error ? error.message : String(error)}
21086
- `
21087
- );
21088
- }
21089
- }
21090
- if (choice === "never") {
21091
- try {
21092
- if (saveGlobal) {
21093
- await writeGlobalKey(repoRoot, "skipInstallPrompt", true);
21094
- } else {
21095
- await writeKey(repoRoot, "skipInstallPrompt", true);
21096
- }
21097
- } catch (error) {
21098
- stderr(
21099
- `gji: failed to save config: ${error instanceof Error ? error.message : String(error)}
21100
- `
21101
- );
21102
- }
21103
- }
21104
- }
21105
- var runInstallCommand = runCommand;
21106
- async function defaultWriteConfigKey(root, key, value) {
21107
- await updateLocalConfigKey(root, key, value);
21108
- }
21109
- async function defaultWriteGlobalRepoConfigKey(repoRoot, key, value) {
21110
- await updateGlobalRepoConfigKey(repoRoot, key, value);
21111
- }
21112
- async function loadExistingGlobalRepoHooks(repoRoot) {
21113
- const { config: globalConfig } = await loadGlobalConfig();
21114
- const repos = isPlainObject2(globalConfig.repos) ? globalConfig.repos : {};
21115
- const perRepo = isPlainObject2(repos[repoRoot]) ? repos[repoRoot] : {};
21116
- return isPlainObject2(perRepo.hooks) ? perRepo.hooks : {};
21117
- }
21118
- async function defaultPromptForInstallChoice(pm) {
21119
- const choice = await ve({
21120
- message: `Run \`${pm.installCommand}\` in the new worktree?`,
21121
- options: [
21122
- { value: "yes", label: "Yes", hint: "run once" },
21123
- { value: "no", label: "No", hint: "skip this time" },
21124
- { value: "always", label: "Always", hint: "save as after-create hook" },
21125
- {
21126
- value: "never",
21127
- label: "Never",
21128
- hint: "disable this prompt for this repo"
21129
- }
21130
- ]
21131
- });
21132
- if (pD(choice)) {
21133
- return null;
21134
- }
21135
- return choice;
21136
- }
21137
- function isPlainObject2(value) {
21138
- return typeof value === "object" && value !== null && !Array.isArray(value);
21139
- }
21140
-
21141
- // src/sync-directories.ts
21142
- async function executeSyncDirectoryPlan(plan, options) {
21143
- const outcomes = [];
21144
- for (const entry of plan) {
21145
- if (entry.destinationWarning) {
21146
- recordSkipped(
21147
- outcomes,
21148
- options.reporter,
21149
- entry.directory,
21150
- entry.destinationWarning
21151
- );
21152
- continue;
21153
- }
21154
- const destinationState = await inspectDestination(
21155
- entry.worktreePath,
21156
- entry.destination
21157
- );
21158
- if (destinationState.kind === "exists") {
21159
- recordSkipped(
21160
- outcomes,
21161
- options.reporter,
21162
- entry.directory,
21163
- "destination already exists"
21164
- );
21165
- continue;
21166
- }
21167
- if (destinationState.kind === "unsafe") {
21168
- const reason = destinationState.reason;
21169
- recordSkipped(outcomes, options.reporter, entry.directory, reason);
21170
- continue;
21171
- }
21172
- if (entry.warning) {
21173
- recordSkipped(outcomes, options.reporter, entry.directory, entry.warning);
21174
- continue;
21175
- }
21176
- if (!entry.source) {
21177
- recordSkipped(
21178
- outcomes,
21179
- options.reporter,
21180
- entry.directory,
21181
- "source does not exist"
21182
- );
21183
- continue;
21184
- }
21185
- const refreshedDestinationState = await inspectDestination(
21186
- entry.worktreePath,
21187
- entry.destination
21188
- );
21189
- if (refreshedDestinationState.kind !== "missing") {
21190
- recordSkipped(
21191
- outcomes,
21192
- options.reporter,
21193
- entry.directory,
21194
- refreshedDestinationState.kind === "unsafe" ? refreshedDestinationState.reason : "destination already exists"
21195
- );
21196
- continue;
21197
- }
21198
- let result;
21199
- try {
21200
- const cloneOptions = {
21201
- destinationRoot: entry.worktreePath,
21202
- measureBytes: options.reporter.measureCloneSize
21203
- };
21204
- result = await options.cloneDirectory(
21205
- entry.source,
21206
- entry.destination,
21207
- cloneOptions
21208
- );
21209
- } catch (error) {
21210
- if (isCloneDestinationExistsError(error)) {
21211
- recordSkipped(
21212
- outcomes,
21213
- options.reporter,
21214
- entry.directory,
21215
- "destination already exists"
21216
- );
21217
- continue;
21218
- }
21219
- if (isCloneInProgressError(error)) {
21220
- const reason2 = "copy-on-write clone already in progress";
21221
- recordSkipped(outcomes, options.reporter, entry.directory, reason2);
21222
- continue;
21223
- }
21224
- const reason = toErrorMessage5(error);
21225
- recordSkipped(outcomes, options.reporter, entry.directory, reason);
21226
- continue;
21227
- }
21228
- const clonedDirectory = {
21229
- bytes: result.bytes,
21230
- dir: entry.directory,
21231
- ms: result.ms
21232
- };
21233
- const outcome = { kind: "cloned", directory: clonedDirectory };
21234
- outcomes.push(outcome);
21235
- options.reporter.cloned(clonedDirectory);
21236
- }
21237
- return outcomes;
21238
- }
21239
- function recordSkipped(outcomes, reporter, dir, reason) {
21240
- outcomes.push({ kind: "skipped", dir, reason });
21241
- if (reporter.skipped) reporter.skipped({ dir, reason });
21242
- else reporter.write(`syncDirs: ${reason}, skipped ${dir}
21243
- `);
21244
- }
21245
- function toErrorMessage5(error) {
21246
- return error instanceof Error ? error.message : String(error);
19599
+ function isPathInside2(root, candidate) {
19600
+ const distance = relative3(resolve10(root), resolve10(candidate));
19601
+ return distance === "" || !isAbsolute4(distance) && distance !== ".." && !distance.startsWith(`..${sep4}`);
21247
19602
  }
21248
19603
 
21249
19604
  // src/worktree-bootstrap.ts
21250
19605
  async function bootstrapWorktree(options) {
21251
- const dependencyMode = options.config.dependencyBootstrap ?? "off";
19606
+ const dependencyMode = options.dependencyMode;
21252
19607
  const dependencyPlan = await prepareDependencyBootstrap(dependencyMode, {
21253
19608
  currentRoot: options.currentRoot,
21254
19609
  detectionRoot: options.dependencyDetectionRoot,
@@ -21256,27 +19611,12 @@ async function bootstrapWorktree(options) {
21256
19611
  cargoBuildCommand: options.config.dependencyBuildCommand,
21257
19612
  worktreePath: options.worktreePath
21258
19613
  });
21259
- const syncPlan = await prepareSyncDirectoryPlan(
21260
- options.repoRoot,
21261
- options.worktreePath,
21262
- options.config.syncDirs ?? []
21263
- );
21264
- const outcomes = await executeSyncDirectoryPlan(syncPlan, {
21265
- cloneDirectory: options.cloneDirectory ?? cloneDir,
21266
- reporter: options.reporter
21267
- });
21268
- const clonedDirs = outcomes.flatMap(
21269
- (outcome) => outcome.kind === "cloned" ? [outcome.directory] : []
21270
- );
21271
- const skippedDirs = outcomes.flatMap(
21272
- (outcome) => outcome.kind === "skipped" ? [{ dir: outcome.dir, reason: outcome.reason }] : []
21273
- );
21274
19614
  const syncFileFailures = [];
21275
19615
  for (const pattern of options.config.syncFiles ?? []) {
21276
19616
  try {
21277
19617
  await syncFiles(options.repoRoot, options.worktreePath, [pattern]);
21278
19618
  } catch (error) {
21279
- const message = `failed to sync file "${pattern}": ${toErrorMessage6(error)}`;
19619
+ const message = `failed to sync file "${pattern}": ${toErrorMessage2(error)}`;
21280
19620
  if (!options.json) options.reporter.write(`Warning: ${message}
21281
19621
  `);
21282
19622
  syncFileFailures.push({
@@ -21293,34 +19633,18 @@ async function bootstrapWorktree(options) {
21293
19633
  for (const event of syncFileFailures) options.reporter.dependency(event);
21294
19634
  }
21295
19635
  const dependencyBootstrap = syncFileFailures.length > 0 ? { mode: dependencyMode, ready: false, events: [] } : await executeDependencyBootstrap(dependencyPlan, {
21296
- cloneDirectory: options.cloneDirectory,
21297
19636
  reporter: options.reporter,
21298
19637
  stderr: options.commandStderr ?? options.reporter.write,
21299
19638
  stdout: options.commandStdout,
21300
- seededDirectories: clonedDirs.map(({ dir }) => dir),
21301
19639
  runCommand: options.runCommand
21302
19640
  });
21303
19641
  if (!dependencyBootstrap.ready) {
21304
19642
  return {
21305
- clonedDirs,
21306
19643
  dependencyBootstrap,
21307
19644
  ready: false,
21308
- syncFileFailures,
21309
- skippedDirs
19645
+ syncFileFailures
21310
19646
  };
21311
19647
  }
21312
- if (dependencyMode === "off" && ["default", "legacy"].includes(
21313
- options.dependencyBootstrapPolicy?.source ?? "default"
21314
- )) {
21315
- await maybeRunInstallPrompt(
21316
- options.worktreePath,
21317
- options.repoRoot,
21318
- options.config,
21319
- options.reporter.write,
21320
- options.installDependencies,
21321
- options.nonInteractive
21322
- );
21323
- }
21324
19648
  const hooks = extractHooks(options.config);
21325
19649
  const slot = await assignWorktreeSlot(options.repoRoot, options.worktreePath);
21326
19650
  await runHook(
@@ -21329,26 +19653,24 @@ async function bootstrapWorktree(options) {
21329
19653
  {
21330
19654
  branch: options.branch,
21331
19655
  path: options.worktreePath,
21332
- repo: basename9(options.repoRoot),
19656
+ repo: basename8(options.repoRoot),
21333
19657
  slot
21334
19658
  },
21335
19659
  options.reporter.write,
21336
19660
  options.json ? () => void 0 : options.commandStdout ?? ((chunk) => process.stdout.write(chunk))
21337
19661
  );
21338
19662
  return {
21339
- clonedDirs,
21340
19663
  dependencyBootstrap,
21341
19664
  ready: true,
21342
- syncFileFailures: [],
21343
- skippedDirs
19665
+ syncFileFailures: []
21344
19666
  };
21345
19667
  }
21346
- function toErrorMessage6(error) {
19668
+ function toErrorMessage2(error) {
21347
19669
  return error instanceof Error ? error.message : String(error);
21348
19670
  }
21349
19671
 
21350
19672
  // src/pr.ts
21351
- var execFileAsync7 = promisify8(execFile7);
19673
+ var execFileAsync6 = promisify7(execFile6);
21352
19674
  var PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
21353
19675
  function parsePrInput(input) {
21354
19676
  if (/^\d+$/.test(input)) return input;
@@ -21377,15 +19699,12 @@ function createPrCommand(dependencies = {}) {
21377
19699
  }
21378
19700
  const repository = await detectRepository(options.cwd);
21379
19701
  let config;
21380
- let dependencyBootstrapExplicit;
21381
19702
  try {
21382
- const loaded = await loadEffectiveConfigResult(
19703
+ config = await loadEffectiveConfig(
21383
19704
  repository.repoRoot,
21384
19705
  void 0,
21385
19706
  options.json ? void 0 : options.stderr
21386
19707
  );
21387
- config = loaded.config;
21388
- dependencyBootstrapExplicit = loaded.dependencyBootstrapExplicit;
21389
19708
  } catch (error) {
21390
19709
  const message = error instanceof Error ? error.message : String(error);
21391
19710
  if (options.json) {
@@ -21436,47 +19755,16 @@ function createPrCommand(dependencies = {}) {
21436
19755
  );
21437
19756
  return 1;
21438
19757
  }
21439
- let dependencyPolicy = {
21440
- mode: config.dependencyBootstrap ?? "off",
21441
- prompted: false,
21442
- source: dependencyBootstrapExplicit ? "explicit" : "default"
21443
- };
21444
- if (options.dryRun) {
21445
- dependencyPolicy = await resolveDependencyBootstrapPolicy(
21446
- {
21447
- currentRoot: repository.currentRoot,
21448
- repoRoot: repository.repoRoot,
21449
- worktreePath
21450
- },
21451
- config,
21452
- dependencyBootstrapExplicit,
21453
- {
21454
- dependencies,
21455
- dryRun: true,
21456
- legacyInstallPromptConfigured: dependencies.promptForInstallChoice !== void 0,
21457
- nonInteractive: !!options.json,
21458
- stderr: options.stderr
21459
- }
21460
- );
21461
- config = {
21462
- ...config,
21463
- dependencyBootstrap: dependencyPolicy.mode
21464
- };
21465
- }
21466
- const dryRunSyncDirs = options.dryRun ? await estimateSyncDirectories(
21467
- repository.repoRoot,
21468
- worktreePath,
21469
- config.syncDirs ?? []
21470
- ) : [];
21471
- const dryRunDependencyBootstrap = options.dryRun ? await createDependencyBootstrapPreview(
21472
- config.dependencyBootstrap ?? "off",
21473
- {
21474
- currentRoot: repository.currentRoot,
21475
- repoRoot: repository.repoRoot,
21476
- cargoBuildCommand: config.dependencyBuildCommand,
21477
- worktreePath
21478
- }
21479
- ) : void 0;
19758
+ const dependencyMode = resolveDependencyBootstrapMode(
19759
+ config.dependencyBootstrap,
19760
+ options.noInstall
19761
+ );
19762
+ const dryRunDependencyBootstrap = options.dryRun ? await createDependencyBootstrapPreview(dependencyMode, {
19763
+ currentRoot: repository.currentRoot,
19764
+ repoRoot: repository.repoRoot,
19765
+ cargoBuildCommand: config.dependencyBuildCommand,
19766
+ worktreePath
19767
+ }) : void 0;
21480
19768
  if (options.dryRun) {
21481
19769
  if (options.json) {
21482
19770
  const output = {
@@ -21490,7 +19778,6 @@ function createPrCommand(dependencies = {}) {
21490
19778
  ),
21491
19779
  dryRun: true
21492
19780
  };
21493
- if (dryRunSyncDirs.length > 0) output.syncDirs = dryRunSyncDirs;
21494
19781
  if (dryRunDependencyBootstrap?.targets.length)
21495
19782
  output.dependencyBootstrap = dryRunDependencyBootstrap;
21496
19783
  options.stdout(`${JSON.stringify(output, null, 2)}
@@ -21498,8 +19785,7 @@ function createPrCommand(dependencies = {}) {
21498
19785
  } else {
21499
19786
  options.stdout(
21500
19787
  `Would create worktree at ${worktreePath} (branch: ${branchName})
21501
- ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(bytes)})
21502
- `).join("")}${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
19788
+ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
21503
19789
  );
21504
19790
  }
21505
19791
  return 0;
@@ -21526,57 +19812,31 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
21526
19812
  }
21527
19813
  return 1;
21528
19814
  }
21529
- await mkdir8(dirname11(worktreePath), { recursive: true });
19815
+ await mkdir7(dirname10(worktreePath), { recursive: true });
21530
19816
  const branchAlreadyExists = await localBranchExists(
21531
19817
  repository.repoRoot,
21532
19818
  branchName
21533
19819
  );
21534
19820
  const worktreeArgs = branchAlreadyExists ? ["worktree", "add", worktreePath, branchName] : ["worktree", "add", "-b", branchName, worktreePath, remoteRef];
21535
- await execFileAsync7("git", worktreeArgs, { cwd: repository.repoRoot });
21536
- if (!dependencyBootstrapExplicit) {
21537
- dependencyPolicy = await resolveDependencyBootstrapPolicy(
21538
- {
21539
- currentRoot: repository.currentRoot,
21540
- detectionRoot: worktreePath,
21541
- repoRoot: repository.repoRoot,
21542
- worktreePath
21543
- },
21544
- config,
21545
- false,
21546
- {
21547
- dependencies,
21548
- legacyInstallPromptConfigured: dependencies.promptForInstallChoice !== void 0,
21549
- nonInteractive: !!options.json,
21550
- stderr: options.stderr
21551
- }
21552
- );
21553
- config = {
21554
- ...config,
21555
- dependencyBootstrap: dependencyPolicy.mode
21556
- };
21557
- }
19821
+ await execFileAsync6("git", worktreeArgs, { cwd: repository.repoRoot });
21558
19822
  const bootstrap = await bootstrapWorktree({
21559
19823
  branch: branchName,
21560
- cloneDirectory: dependencies.cloneDir,
21561
19824
  config,
21562
19825
  currentRoot: repository.currentRoot,
21563
19826
  dependencyDetectionRoot: worktreePath,
21564
- nonInteractive: !!options.json,
19827
+ dependencyMode,
21565
19828
  repoRoot: repository.repoRoot,
21566
19829
  reporter: createBootstrapReporter(options.stderr, !!options.json),
21567
- runCommand: dependencies.runInstallCommand,
19830
+ runCommand: dependencies.runCommand,
21568
19831
  commandStdout: options.json ? () => void 0 : options.stdout,
21569
19832
  commandStderr: options.json ? () => void 0 : options.stderr,
21570
19833
  json: options.json,
21571
- worktreePath,
21572
- installDependencies: dependencies,
21573
- dependencyBootstrapPolicy: dependencyPolicy
19834
+ worktreePath
21574
19835
  });
21575
19836
  if (!bootstrap.ready) {
21576
19837
  const details = {
21577
19838
  dependencyBootstrap: bootstrap.dependencyBootstrap,
21578
19839
  path: worktreePath,
21579
- skipped: bootstrap.skippedDirs,
21580
19840
  syncFiles: bootstrap.syncFileFailures
21581
19841
  };
21582
19842
  if (options.json) {
@@ -21604,15 +19864,7 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
21604
19864
  branchName
21605
19865
  )
21606
19866
  };
21607
- if (bootstrap.clonedDirs.length > 0) {
21608
- output.cloned = bootstrap.clonedDirs.map(({ dir, ms }) => ({
21609
- dir,
21610
- ms
21611
- }));
21612
- }
21613
- if (bootstrap.skippedDirs.length > 0)
21614
- output.skipped = bootstrap.skippedDirs;
21615
- if (bootstrap.dependencyBootstrap.mode !== "off")
19867
+ if (bootstrap.dependencyBootstrap.events.length > 0)
21616
19868
  output.dependencyBootstrap = bootstrap.dependencyBootstrap;
21617
19869
  options.stdout(`${JSON.stringify(output, null, 2)}
21618
19870
  `);
@@ -21625,7 +19877,7 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
21625
19877
  }
21626
19878
  async function localBranchExists(repoRoot, branchName) {
21627
19879
  try {
21628
- await execFileAsync7(
19880
+ await execFileAsync6(
21629
19881
  "git",
21630
19882
  ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
21631
19883
  { cwd: repoRoot }
@@ -21639,7 +19891,7 @@ var runPrCommand = createPrCommand();
21639
19891
  async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef) {
21640
19892
  for (const sourceRef of listPullRequestSourceRefs(input, prNumber)) {
21641
19893
  try {
21642
- await execFileAsync7(
19894
+ await execFileAsync6(
21643
19895
  "git",
21644
19896
  ["fetch", "origin", `${sourceRef}:${remoteRef}`],
21645
19897
  { cwd: repoRoot }
@@ -21942,15 +20194,14 @@ function normalizeRemoteUrl(value) {
21942
20194
  }
21943
20195
 
21944
20196
  // src/new.ts
21945
- import { execFile as execFile8 } from "node:child_process";
21946
- import { access as access9, mkdir as mkdir9 } from "node:fs/promises";
21947
- import { dirname as dirname12, resolve as resolve12 } from "node:path";
21948
- import { promisify as promisify9 } from "node:util";
21949
- var execFileAsync8 = promisify9(execFile8);
20197
+ import { execFile as execFile7 } from "node:child_process";
20198
+ import { access as access8, mkdir as mkdir8 } from "node:fs/promises";
20199
+ import { dirname as dirname11, resolve as resolve11 } from "node:path";
20200
+ import { promisify as promisify8 } from "node:util";
20201
+ var execFileAsync7 = promisify8(execFile7);
21950
20202
  var NEW_OUTPUT_FILE_ENV = "GJI_NEW_OUTPUT_FILE";
21951
20203
  function createNewCommand(dependencies = {}) {
21952
20204
  const createBranchPlaceholder = dependencies.createBranchPlaceholder ?? generateBranchPlaceholder;
21953
- const cloneDirectory = dependencies.cloneDir ?? cloneDir;
21954
20205
  const promptForBranch = dependencies.promptForBranch ?? defaultPromptForBranch;
21955
20206
  const promptForFetchFailure = dependencies.promptForFetchFailure ?? defaultPromptForFetchFailure;
21956
20207
  const prompt = dependencies.promptForPathConflict ?? promptForPathConflict;
@@ -21971,15 +20222,12 @@ function createNewCommand(dependencies = {}) {
21971
20222
  }
21972
20223
  const repository = await detectRepository(options.cwd);
21973
20224
  let config;
21974
- let dependencyBootstrapExplicit;
21975
20225
  try {
21976
- const loaded = await loadEffectiveConfigResult(
20226
+ config = await loadEffectiveConfig(
21977
20227
  repository.repoRoot,
21978
20228
  void 0,
21979
20229
  options.json ? void 0 : options.stderr
21980
20230
  );
21981
- config = loaded.config;
21982
- dependencyBootstrapExplicit = loaded.dependencyBootstrapExplicit;
21983
20231
  } catch (error) {
21984
20232
  return emitNewError(
21985
20233
  options,
@@ -22044,7 +20292,7 @@ function createNewCommand(dependencies = {}) {
22044
20292
  if (options.force) {
22045
20293
  if (!options.dryRun) {
22046
20294
  try {
22047
- await execFileAsync8(
20295
+ await execFileAsync7(
22048
20296
  "git",
22049
20297
  ["worktree", "remove", "--force", worktreePath],
22050
20298
  { cwd: repository.repoRoot }
@@ -22065,7 +20313,7 @@ function createNewCommand(dependencies = {}) {
22065
20313
  }
22066
20314
  if (!options.detached) {
22067
20315
  try {
22068
- await execFileAsync8("git", ["branch", "-D", worktreeName], {
20316
+ await execFileAsync7("git", ["branch", "-D", worktreeName], {
22069
20317
  cwd: repository.repoRoot
22070
20318
  });
22071
20319
  } catch {
@@ -22106,26 +20354,10 @@ function createNewCommand(dependencies = {}) {
22106
20354
  return 1;
22107
20355
  }
22108
20356
  }
22109
- const dependencyPolicy = await resolveDependencyBootstrapPolicy(
22110
- {
22111
- currentRoot: repository.currentRoot,
22112
- repoRoot: repository.repoRoot,
22113
- worktreePath
22114
- },
22115
- config,
22116
- dependencyBootstrapExplicit,
22117
- {
22118
- dependencies,
22119
- dryRun: options.dryRun,
22120
- legacyInstallPromptConfigured: dependencies.promptForInstallChoice !== void 0,
22121
- nonInteractive: !!options.json,
22122
- stderr: options.stderr
22123
- }
20357
+ const dependencyMode = resolveDependencyBootstrapMode(
20358
+ config.dependencyBootstrap,
20359
+ options.noInstall
22124
20360
  );
22125
- config = {
22126
- ...config,
22127
- dependencyBootstrap: dependencyPolicy.mode
22128
- };
22129
20361
  if (options.dryRun) {
22130
20362
  if (options.take) {
22131
20363
  const changedFiles = await listTakeFiles(options.cwd);
@@ -22156,13 +20388,8 @@ ${transferableFiles.map((file) => ` ${file}
22156
20388
  }
22157
20389
  return 0;
22158
20390
  }
22159
- const dryRunSyncDirs = await estimateSyncDirectories(
22160
- repository.repoRoot,
22161
- worktreePath,
22162
- config.syncDirs ?? []
22163
- );
22164
20391
  const dryRunDependencyBootstrap = await createDependencyBootstrapPreview(
22165
- config.dependencyBootstrap ?? "off",
20392
+ dependencyMode,
22166
20393
  {
22167
20394
  currentRoot: repository.currentRoot,
22168
20395
  repoRoot: repository.repoRoot,
@@ -22182,7 +20409,6 @@ ${transferableFiles.map((file) => ` ${file}
22182
20409
  ),
22183
20410
  dryRun: true
22184
20411
  };
22185
- if (dryRunSyncDirs.length > 0) output.syncDirs = dryRunSyncDirs;
22186
20412
  if (dryRunDependencyBootstrap.targets.length > 0)
22187
20413
  output.dependencyBootstrap = dryRunDependencyBootstrap;
22188
20414
  options.stdout(`${JSON.stringify(output, null, 2)}
@@ -22192,13 +20418,12 @@ ${transferableFiles.map((file) => ` ${file}
22192
20418
  const openNote = resolvedEditor ? `, then open in ${resolvedEditor}` : "";
22193
20419
  options.stdout(
22194
20420
  `Would create worktree at ${worktreePath} (branch: ${worktreeName}${openNote})
22195
- ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(bytes)})
22196
- `).join("")}${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
20421
+ ${formatDependencyBootstrapPreview(dryRunDependencyBootstrap)}`
22197
20422
  );
22198
20423
  }
22199
20424
  return 0;
22200
20425
  }
22201
- await mkdir9(dirname12(worktreePath), { recursive: true });
20426
+ await mkdir8(dirname11(worktreePath), { recursive: true });
22202
20427
  const startPoint = options.fromCurrent && !options.detached ? await resolveCurrentWorktreeHead(options.cwd) : void 0;
22203
20428
  const takeStartPoint = options.take ? await resolveCurrentWorktreeHead(options.cwd) : void 0;
22204
20429
  const changedTakeFiles = options.take ? await listTakeFiles(options.cwd) : [];
@@ -22269,7 +20494,7 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
22269
20494
  ...takeStartPoint ? [takeStartPoint] : startPoint ? [startPoint] : freshBaseRef ? [freshBaseRef] : []
22270
20495
  ];
22271
20496
  try {
22272
- await execFileAsync8("git", gitArgs, { cwd: repository.repoRoot });
20497
+ await execFileAsync7("git", gitArgs, { cwd: repository.repoRoot });
22273
20498
  } catch (error) {
22274
20499
  if (stashSha)
22275
20500
  await restoreTakeStash(options.cwd, stashSha, options.stderr);
@@ -22286,7 +20511,7 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
22286
20511
  options.stderr
22287
20512
  )) {
22288
20513
  try {
22289
- await execFileAsync8(
20514
+ await execFileAsync7(
22290
20515
  "git",
22291
20516
  ["worktree", "remove", "--force", worktreePath],
22292
20517
  { cwd: repository.repoRoot }
@@ -22310,25 +20535,22 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
22310
20535
  }
22311
20536
  const bootstrap = await bootstrapWorktree({
22312
20537
  branch: worktreeName,
22313
- cloneDirectory,
22314
20538
  config,
22315
20539
  currentRoot: repository.currentRoot,
22316
- nonInteractive: !!options.json,
20540
+ dependencyDetectionRoot: worktreePath,
20541
+ dependencyMode,
22317
20542
  repoRoot: repository.repoRoot,
22318
20543
  reporter: createBootstrapReporter(options.stderr, !!options.json),
22319
- runCommand: dependencies.runInstallCommand,
20544
+ runCommand: dependencies.runCommand,
22320
20545
  commandStdout: options.json ? () => void 0 : options.stdout,
22321
20546
  commandStderr: options.json ? () => void 0 : options.stderr,
22322
20547
  json: options.json,
22323
- worktreePath,
22324
- installDependencies: dependencies,
22325
- dependencyBootstrapPolicy: dependencyPolicy
20548
+ worktreePath
22326
20549
  });
22327
20550
  if (!bootstrap.ready) {
22328
20551
  return emitNewError(options, "worktree bootstrap failed", {
22329
20552
  dependencyBootstrap: bootstrap.dependencyBootstrap,
22330
20553
  path: worktreePath,
22331
- skipped: bootstrap.skippedDirs,
22332
20554
  syncFiles: bootstrap.syncFileFailures
22333
20555
  });
22334
20556
  }
@@ -22346,15 +20568,7 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
22346
20568
  submodules: submoduleFiles
22347
20569
  }
22348
20570
  } : { ...navigation };
22349
- if (bootstrap.clonedDirs.length > 0) {
22350
- output.cloned = bootstrap.clonedDirs.map(({ dir, ms }) => ({
22351
- dir,
22352
- ms
22353
- }));
22354
- }
22355
- if (bootstrap.skippedDirs.length > 0)
22356
- output.skipped = bootstrap.skippedDirs;
22357
- if (bootstrap.dependencyBootstrap.mode !== "off")
20571
+ if (bootstrap.dependencyBootstrap.events.length > 0)
22358
20572
  output.dependencyBootstrap = bootstrap.dependencyBootstrap;
22359
20573
  options.stdout(`${JSON.stringify(output, null, 2)}
22360
20574
  `);
@@ -22583,7 +20797,7 @@ async function defaultPromptForBranch(placeholder) {
22583
20797
  }
22584
20798
  async function localBranchExists2(repoRoot, branchName) {
22585
20799
  try {
22586
- await execFileAsync8(
20800
+ await execFileAsync7(
22587
20801
  "git",
22588
20802
  ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
22589
20803
  { cwd: repoRoot }
@@ -22594,18 +20808,18 @@ async function localBranchExists2(repoRoot, branchName) {
22594
20808
  }
22595
20809
  }
22596
20810
  async function resolveCurrentWorktreeHead(cwd) {
22597
- return execFileAsync8("git", ["rev-parse", "--verify", "HEAD"], { cwd }).then(
20811
+ return execFileAsync7("git", ["rev-parse", "--verify", "HEAD"], { cwd }).then(
22598
20812
  ({ stdout: stdout3 }) => stdout3.trim()
22599
20813
  );
22600
20814
  }
22601
20815
  async function createTakeStash(cwd, worktreeName) {
22602
20816
  const message = `gji-take: ${worktreeName}:${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
22603
- await execFileAsync8(
20817
+ await execFileAsync7(
22604
20818
  "git",
22605
20819
  ["stash", "push", "--include-untracked", "-m", message],
22606
20820
  { cwd }
22607
20821
  );
22608
- const { stdout: stdout3 } = await execFileAsync8(
20822
+ const { stdout: stdout3 } = await execFileAsync7(
22609
20823
  "git",
22610
20824
  ["stash", "list", "--format=%H%x00%s"],
22611
20825
  { cwd }
@@ -22626,12 +20840,12 @@ async function detectInProgressGitState(cwd) {
22626
20840
  ];
22627
20841
  for (const [marker, name] of markers) {
22628
20842
  try {
22629
- const { stdout: stdout3 } = await execFileAsync8(
20843
+ const { stdout: stdout3 } = await execFileAsync7(
22630
20844
  "git",
22631
20845
  ["rev-parse", "--git-path", marker],
22632
20846
  { cwd }
22633
20847
  );
22634
- await access9(resolve12(cwd, stdout3.trim()));
20848
+ await access8(resolve11(cwd, stdout3.trim()));
22635
20849
  return name;
22636
20850
  } catch {
22637
20851
  }
@@ -22678,7 +20892,7 @@ async function openWorktree(worktreePath, editorCli, spawnFn, stderr) {
22678
20892
  }
22679
20893
  async function listTakeFiles(cwd) {
22680
20894
  try {
22681
- const { stdout: stdout3 } = await execFileAsync8(
20895
+ const { stdout: stdout3 } = await execFileAsync7(
22682
20896
  "git",
22683
20897
  ["status", "--porcelain=v1"],
22684
20898
  { cwd }
@@ -22690,7 +20904,7 @@ async function listTakeFiles(cwd) {
22690
20904
  }
22691
20905
  async function listIgnoredFiles(cwd) {
22692
20906
  try {
22693
- const { stdout: stdout3 } = await execFileAsync8(
20907
+ const { stdout: stdout3 } = await execFileAsync7(
22694
20908
  "git",
22695
20909
  ["status", "--porcelain=v1", "--ignored", "--untracked-files=all"],
22696
20910
  { cwd }
@@ -22703,7 +20917,7 @@ async function listIgnoredFiles(cwd) {
22703
20917
  async function listSubmoduleFiles(cwd, changedFiles) {
22704
20918
  if (changedFiles.length === 0) return [];
22705
20919
  try {
22706
- const { stdout: stdout3 } = await execFileAsync8("git", ["ls-files", "--stage"], {
20920
+ const { stdout: stdout3 } = await execFileAsync7("git", ["ls-files", "--stage"], {
22707
20921
  cwd
22708
20922
  });
22709
20923
  const submodules = new Set(
@@ -22716,7 +20930,7 @@ async function listSubmoduleFiles(cwd, changedFiles) {
22716
20930
  }
22717
20931
  async function countUntrackedFiles(cwd) {
22718
20932
  try {
22719
- const { stdout: stdout3 } = await execFileAsync8(
20933
+ const { stdout: stdout3 } = await execFileAsync7(
22720
20934
  "git",
22721
20935
  ["status", "--porcelain=v1", "--untracked-files=all"],
22722
20936
  { cwd }
@@ -22728,7 +20942,7 @@ async function countUntrackedFiles(cwd) {
22728
20942
  }
22729
20943
  async function applyTakeStash(worktreePath, sourcePath, stashSha, copy, stderr) {
22730
20944
  try {
22731
- await execFileAsync8("git", ["stash", "apply", stashSha], {
20945
+ await execFileAsync7("git", ["stash", "apply", stashSha], {
22732
20946
  cwd: worktreePath
22733
20947
  });
22734
20948
  } catch (error) {
@@ -22739,7 +20953,7 @@ async function applyTakeStash(worktreePath, sourcePath, stashSha, copy, stderr)
22739
20953
  }
22740
20954
  if (copy) {
22741
20955
  try {
22742
- await execFileAsync8("git", ["stash", "apply", stashSha], {
20956
+ await execFileAsync7("git", ["stash", "apply", stashSha], {
22743
20957
  cwd: sourcePath
22744
20958
  });
22745
20959
  } catch (error) {
@@ -22751,7 +20965,7 @@ async function applyTakeStash(worktreePath, sourcePath, stashSha, copy, stderr)
22751
20965
  }
22752
20966
  }
22753
20967
  try {
22754
- const { stdout: stdout3 } = await execFileAsync8(
20968
+ const { stdout: stdout3 } = await execFileAsync7(
22755
20969
  "git",
22756
20970
  ["stash", "list", "--format=%H %gd"],
22757
20971
  { cwd: sourcePath }
@@ -22759,14 +20973,14 @@ async function applyTakeStash(worktreePath, sourcePath, stashSha, copy, stderr)
22759
20973
  const line = stdout3.split("\n").find((candidate) => candidate.startsWith(stashSha));
22760
20974
  const ref = line?.trim().split(/\s+/).at(1);
22761
20975
  if (ref)
22762
- await execFileAsync8("git", ["stash", "drop", ref], { cwd: sourcePath });
20976
+ await execFileAsync7("git", ["stash", "drop", ref], { cwd: sourcePath });
22763
20977
  } catch {
22764
20978
  }
22765
20979
  return true;
22766
20980
  }
22767
20981
  async function restoreTakeStash(sourcePath, stashSha, stderr) {
22768
20982
  try {
22769
- await execFileAsync8("git", ["stash", "apply", stashSha], {
20983
+ await execFileAsync7("git", ["stash", "apply", stashSha], {
22770
20984
  cwd: sourcePath
22771
20985
  });
22772
20986
  } catch {
@@ -23045,7 +21259,7 @@ async function navigateToExistingWorktree(options, path9, worktree) {
23045
21259
  {
23046
21260
  branch: worktree?.branch ?? void 0,
23047
21261
  path: path9,
23048
- repo: basename10(repository.repoRoot),
21262
+ repo: basename9(repository.repoRoot),
23049
21263
  slot: await getWorktreeSlot(path9)
23050
21264
  },
23051
21265
  options.stderr
@@ -23183,7 +21397,7 @@ async function runHistoryCommand(options) {
23183
21397
  }
23184
21398
 
23185
21399
  // src/hub.ts
23186
- import { basename as basename11 } from "node:path";
21400
+ import { basename as basename10 } from "node:path";
23187
21401
  import { stdin as stdin2, stdout as stdout2 } from "node:process";
23188
21402
  var MAX_HUB_REPOSITORY_CONCURRENCY = 4;
23189
21403
  var defaultDependencies = {
@@ -23206,7 +21420,7 @@ async function buildHubData(cwd, dependencies = {}) {
23206
21420
  existing.sources.push(source);
23207
21421
  } else {
23208
21422
  groups.set(repoRoot, {
23209
- name: source.repoName || basename11(repoRoot),
21423
+ name: source.repoName || basename10(repoRoot),
23210
21424
  sources: [source]
23211
21425
  });
23212
21426
  }
@@ -23468,10 +21682,10 @@ async function mapWithConcurrency4(items, limit, mapper) {
23468
21682
  }
23469
21683
 
23470
21684
  // src/init.ts
23471
- import { constants as constants9 } from "node:fs";
23472
- import { access as access10, mkdir as mkdir10, readFile as readFile10, writeFile as writeFile9 } from "node:fs/promises";
21685
+ import { constants as constants8 } from "node:fs";
21686
+ import { access as access9, mkdir as mkdir9, readFile as readFile9, writeFile as writeFile8 } from "node:fs/promises";
23473
21687
  import { homedir as homedir8 } from "node:os";
23474
- import { dirname as dirname13 } from "node:path";
21688
+ import { dirname as dirname12 } from "node:path";
23475
21689
  var ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
23476
21690
  var SHELL_WRAPPED_COMMANDS = [
23477
21691
  {
@@ -23595,8 +21809,8 @@ async function runDefaultOnboarding(context) {
23595
21809
  const installCompletion = await promptForCompletion(shell, completionPath);
23596
21810
  if (installCompletion === null) return abortDefaultOnboarding();
23597
21811
  if (installCompletion) {
23598
- await mkdir10(dirname13(completionPath), { recursive: true });
23599
- await writeFile9(completionPath, renderShellCompletion(shell), "utf8");
21812
+ await mkdir9(dirname12(completionPath), { recursive: true });
21813
+ await writeFile8(completionPath, renderShellCompletion(shell), "utf8");
23600
21814
  }
23601
21815
  if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
23602
21816
  await ensureZshCompletionPath(rcPath);
@@ -23688,8 +21902,8 @@ async function applyOnboardingResult(result, home) {
23688
21902
  const completionAlreadyInstalled = await fileExists(completionPath);
23689
21903
  await applyShellIntegration(result.shell, result.shellIntegration, home);
23690
21904
  if (result.installCompletion) {
23691
- await mkdir10(dirname13(completionPath), { recursive: true });
23692
- await writeFile9(
21905
+ await mkdir9(dirname12(completionPath), { recursive: true });
21906
+ await writeFile8(
23693
21907
  completionPath,
23694
21908
  renderShellCompletion(result.shell),
23695
21909
  "utf8"
@@ -23704,17 +21918,17 @@ async function applyOnboardingResult(result, home) {
23704
21918
  }
23705
21919
  async function installOnboardingShellIntegration(shell, home) {
23706
21920
  const rcPath = resolveShellConfigPath(shell, home);
23707
- await mkdir10(dirname13(rcPath), { recursive: true });
21921
+ await mkdir9(dirname12(rcPath), { recursive: true });
23708
21922
  const current = await readExistingConfig(rcPath);
23709
21923
  if (shell === "fish") {
23710
- await writeFile9(
21924
+ await writeFile8(
23711
21925
  rcPath,
23712
21926
  upsertShellIntegration(current, renderShellIntegration(shell)),
23713
21927
  "utf8"
23714
21928
  );
23715
21929
  return;
23716
21930
  }
23717
- await writeFile9(
21931
+ await writeFile8(
23718
21932
  rcPath,
23719
21933
  upsertOnboardingShellIntegration(current, shell),
23720
21934
  "utf8"
@@ -23748,7 +21962,7 @@ async function ensureZshCompletionPath(rcPath) {
23748
21962
  const current = await readExistingConfig(rcPath);
23749
21963
  const next = upsertZshCompletionPath(current);
23750
21964
  if (next !== current) {
23751
- await writeFile9(rcPath, next, "utf8");
21965
+ await writeFile8(rcPath, next, "utf8");
23752
21966
  }
23753
21967
  }
23754
21968
  function upsertZshCompletionPath(existingConfig) {
@@ -23771,7 +21985,7 @@ ${suffix}`
23771
21985
  }
23772
21986
  async function fileExists(path9) {
23773
21987
  try {
23774
- await access10(path9, constants9.F_OK);
21988
+ await access9(path9, constants8.F_OK);
23775
21989
  return true;
23776
21990
  } catch {
23777
21991
  return false;
@@ -23792,10 +22006,10 @@ async function runLegacyInitCommand(options) {
23792
22006
  return 0;
23793
22007
  }
23794
22008
  const rcPath = resolveShellConfigPath(shell, home);
23795
- await mkdir10(dirname13(rcPath), { recursive: true });
22009
+ await mkdir9(dirname12(rcPath), { recursive: true });
23796
22010
  const current = await readExistingConfig(rcPath);
23797
22011
  const next = upsertShellIntegration(current, script);
23798
- await writeFile9(rcPath, next, "utf8");
22012
+ await writeFile8(rcPath, next, "utf8");
23799
22013
  options.stdout(`${rcPath}
23800
22014
  `);
23801
22015
  const { config: globalConfig } = await loadGlobalConfig(home);
@@ -23886,7 +22100,7 @@ async function saveWizardConfig(result, cwd, home) {
23886
22100
  }
23887
22101
  async function readExistingConfig(path9) {
23888
22102
  try {
23889
- return await readFile10(path9, "utf8");
22103
+ return await readFile9(path9, "utf8");
23890
22104
  } catch (error) {
23891
22105
  if (isMissingFileError3(error)) {
23892
22106
  return "";
@@ -24135,11 +22349,11 @@ function sortWorktrees(worktrees) {
24135
22349
  }
24136
22350
 
24137
22351
  // src/open.ts
24138
- import { execFile as execFile9 } from "node:child_process";
24139
- import { access as access11, writeFile as writeFile10 } from "node:fs/promises";
24140
- import { join as join16 } from "node:path";
24141
- import { promisify as promisify10 } from "node:util";
24142
- var execFileAsync9 = promisify10(execFile9);
22352
+ import { execFile as execFile8 } from "node:child_process";
22353
+ import { access as access10, writeFile as writeFile9 } from "node:fs/promises";
22354
+ import { join as join14 } from "node:path";
22355
+ import { promisify as promisify9 } from "node:util";
22356
+ var execFileAsync8 = promisify9(execFile8);
24143
22357
  function createOpenCommand(dependencies = {}) {
24144
22358
  const detectEditors = dependencies.detectEditors ?? detectInstalledEditors;
24145
22359
  const promptForEditor2 = dependencies.promptForEditor ?? defaultPromptForEditor;
@@ -24286,7 +22500,7 @@ async function detectInstalledEditors() {
24286
22500
  }
24287
22501
  async function isCommandAvailable(command) {
24288
22502
  try {
24289
- await execFileAsync9("which", [command]);
22503
+ await execFileAsync8("which", [command]);
24290
22504
  return true;
24291
22505
  } catch {
24292
22506
  return false;
@@ -24304,14 +22518,14 @@ async function defaultPromptForEditor(editors) {
24304
22518
  return choice;
24305
22519
  }
24306
22520
  async function ensureWorkspaceFile(worktreePath, repoName) {
24307
- const workspacePath = join16(worktreePath, `${repoName}.code-workspace`);
22521
+ const workspacePath = join14(worktreePath, `${repoName}.code-workspace`);
24308
22522
  try {
24309
- await access11(workspacePath);
22523
+ await access10(workspacePath);
24310
22524
  return workspacePath;
24311
22525
  } catch {
24312
22526
  }
24313
22527
  const workspace = { folders: [{ path: "." }], settings: {} };
24314
- await writeFile10(
22528
+ await writeFile9(
24315
22529
  workspacePath,
24316
22530
  `${JSON.stringify(workspace, null, 2)}
24317
22531
  `,
@@ -24321,10 +22535,10 @@ async function ensureWorkspaceFile(worktreePath, repoName) {
24321
22535
  }
24322
22536
 
24323
22537
  // src/browser.ts
24324
- import { execFile as execFile10 } from "node:child_process";
22538
+ import { execFile as execFile9 } from "node:child_process";
24325
22539
  import { platform as platform2 } from "node:process";
24326
- import { promisify as promisify11 } from "node:util";
24327
- var execFileAsync10 = promisify11(execFile10);
22540
+ import { promisify as promisify10 } from "node:util";
22541
+ var execFileAsync9 = promisify10(execFile9);
24328
22542
  function createBrowserOpener(dependencies = {}) {
24329
22543
  const currentPlatform = dependencies.platform ?? platform2;
24330
22544
  const runCommand2 = dependencies.runCommand ?? defaultRunCommand2;
@@ -24354,7 +22568,7 @@ function validateBrowserUrl(url) {
24354
22568
  }
24355
22569
  }
24356
22570
  async function defaultRunCommand2(command, args) {
24357
- await execFileAsync10(command, args, { timeout: 1e4 });
22571
+ await execFileAsync9(command, args, { timeout: 1e4 });
24358
22572
  }
24359
22573
 
24360
22574
  // src/pr-open.ts
@@ -24719,7 +22933,7 @@ function formatError(error) {
24719
22933
  }
24720
22934
 
24721
22935
  // src/remove.ts
24722
- import { basename as basename12 } from "node:path";
22936
+ import { basename as basename11 } from "node:path";
24723
22937
  var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
24724
22938
  function createRemoveCommand(dependencies = {}) {
24725
22939
  const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree3;
@@ -24825,7 +23039,7 @@ function createRemoveCommand(dependencies = {}) {
24825
23039
  {
24826
23040
  branch: worktree.branch ?? void 0,
24827
23041
  path: worktree.path,
24828
- repo: basename12(repository.repoRoot)
23042
+ repo: basename11(repository.repoRoot)
24829
23043
  },
24830
23044
  options.stderr
24831
23045
  );
@@ -25224,7 +23438,7 @@ function resolveConfiguredString2(value) {
25224
23438
 
25225
23439
  // src/sync-files-command.ts
25226
23440
  import { homedir as homedir9 } from "node:os";
25227
- import { join as join17 } from "node:path";
23441
+ import { join as join15 } from "node:path";
25228
23442
  async function runSyncFilesCommand(options) {
25229
23443
  const repository = await detectRepository(options.cwd);
25230
23444
  const home = options.home ?? homedir9();
@@ -25271,9 +23485,9 @@ async function runSyncFilesCommand(options) {
25271
23485
  }
25272
23486
  function findRepoConfigEntry(config, repoRoot, home) {
25273
23487
  const repos = config.repos;
25274
- if (!isPlainObject3(repos)) return null;
23488
+ if (!isPlainObject2(repos)) return null;
25275
23489
  for (const [key, value] of Object.entries(repos)) {
25276
- if (expandTilde3(key, home) === repoRoot && isPlainObject3(value)) {
23490
+ if (expandTilde3(key, home) === repoRoot && isPlainObject2(value)) {
25277
23491
  return { config: value, key };
25278
23492
  }
25279
23493
  }
@@ -25330,8 +23544,8 @@ function removeSyncFiles(existing, removals) {
25330
23544
  return existing.filter((path9) => !removalSet.has(path9));
25331
23545
  }
25332
23546
  async function saveRepoSyncFiles(config, repoKey, syncFiles2, home) {
25333
- const repos = isPlainObject3(config.repos) ? { ...config.repos } : {};
25334
- const repoConfig = isPlainObject3(repos[repoKey]) ? repos[repoKey] : {};
23547
+ const repos = isPlainObject2(config.repos) ? { ...config.repos } : {};
23548
+ const repoConfig = isPlainObject2(repos[repoKey]) ? repos[repoKey] : {};
25335
23549
  const nextRepoConfig = { ...repoConfig };
25336
23550
  if (syncFiles2.length > 0) {
25337
23551
  nextRepoConfig.syncFiles = syncFiles2;
@@ -25345,7 +23559,7 @@ async function saveRepoSyncFiles(config, repoKey, syncFiles2, home) {
25345
23559
  }
25346
23560
  await saveGlobalConfig({ ...config, repos }, home);
25347
23561
  }
25348
- function isPlainObject3(value) {
23562
+ function isPlainObject2(value) {
25349
23563
  return typeof value === "object" && value !== null && !Array.isArray(value);
25350
23564
  }
25351
23565
  function writeError(options, message) {
@@ -25359,7 +23573,7 @@ function writeError(options, message) {
25359
23573
  }
25360
23574
  function expandTilde3(value, home) {
25361
23575
  if (value === "~") return home;
25362
- if (value.startsWith("~/")) return join17(home, value.slice(2));
23576
+ if (value.startsWith("~/")) return join15(home, value.slice(2));
25363
23577
  return value;
25364
23578
  }
25365
23579
 
@@ -25624,9 +23838,7 @@ async function maybeRegisterCurrentRepo(cwd) {
25624
23838
  }
25625
23839
  }
25626
23840
  function registerCommands(program2) {
25627
- program2.command("new [branch]").description(
25628
- "create a new branch or detached linked worktree and CoW-bootstrap configured directories"
25629
- ).option(
23841
+ program2.command("new [branch]").description("create a new branch or detached linked worktree").option(
25630
23842
  "-f, --force",
25631
23843
  "remove and recreate the worktree if the target path already exists"
25632
23844
  ).option("--detached", "create a detached worktree without a branch").option(
@@ -25644,6 +23856,9 @@ function registerCommands(program2) {
25644
23856
  ).option(
25645
23857
  "--dry-run",
25646
23858
  "show what would be created without executing any git commands or writing files"
23859
+ ).option(
23860
+ "--no-install",
23861
+ "skip automatic dependency setup in the new worktree"
25647
23862
  ).option(
25648
23863
  "--json",
25649
23864
  "emit JSON on success or error instead of human-readable output"
@@ -25666,6 +23881,9 @@ function registerCommands(program2) {
25666
23881
  ).option(
25667
23882
  "--dry-run",
25668
23883
  "show what would be created without executing any git commands or writing files"
23884
+ ).option(
23885
+ "--no-install",
23886
+ "skip automatic dependency setup in the PR worktree"
25669
23887
  ).option(
25670
23888
  "--json",
25671
23889
  "emit JSON on success or error instead of human-readable output"
@@ -25743,6 +23961,7 @@ function attachCommandActions(program2, options) {
25743
23961
  force: commandOptions2.force,
25744
23962
  noFetch: commandOptions2.fetch === false,
25745
23963
  json: commandOptions2.json,
23964
+ noInstall: commandOptions2.install === false,
25746
23965
  open: commandOptions2.open,
25747
23966
  take: commandOptions2.take,
25748
23967
  task: commandOptions2.task
@@ -25825,6 +24044,7 @@ function attachCommandActions(program2, options) {
25825
24044
  cwd: options.cwd,
25826
24045
  dryRun: commandOptions2.dryRun,
25827
24046
  json: commandOptions2.json,
24047
+ noInstall: commandOptions2.install === false,
25828
24048
  number,
25829
24049
  stderr: options.stderr,
25830
24050
  stdout: options.stdout