@solaqua/gji 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -30
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +37 -0
- package/dist/clean.js +34 -19
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +59 -4
- package/dist/doctor.d.ts +28 -0
- package/dist/doctor.js +454 -0
- package/dist/gji-bundle.mjs +2514 -378
- package/dist/go.d.ts +2 -1
- package/dist/go.js +4 -1
- package/dist/index.js +12 -6
- package/dist/init.d.ts +15 -0
- package/dist/init.js +232 -16
- package/dist/open.d.ts +2 -1
- package/dist/open.js +2 -1
- package/dist/pr-open.d.ts +19 -0
- package/dist/pr-open.js +290 -0
- package/dist/pull-requests.d.ts +33 -0
- package/dist/pull-requests.js +384 -0
- package/dist/remove.js +2 -2
- package/dist/repo-registry.d.ts +5 -0
- package/dist/repo-registry.js +87 -14
- package/dist/shell-completion.js +278 -10
- package/dist/shell-setup.d.ts +9 -0
- package/dist/shell-setup.js +56 -0
- package/dist/warp.d.ts +3 -0
- package/dist/warp.js +8 -3
- package/dist/worktree-management.d.ts +2 -1
- package/dist/worktree-management.js +23 -7
- package/dist/worktree-picker.d.ts +12 -1
- package/dist/worktree-picker.js +84 -6
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +1 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-doctor.1 +19 -0
- package/man/man1/gji-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +6 -3
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +1 -1
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +5 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +6 -2
- package/package.json +1 -1
package/dist/gji-bundle.mjs
CHANGED
|
@@ -3462,10 +3462,10 @@ 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
|
|
3465
|
+
var constants6 = __require("constants");
|
|
3466
3466
|
var origCwd = process.cwd;
|
|
3467
3467
|
var cwd = null;
|
|
3468
|
-
var
|
|
3468
|
+
var platform3 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
|
|
3469
3469
|
process.cwd = function() {
|
|
3470
3470
|
if (!cwd)
|
|
3471
3471
|
cwd = origCwd.call(process);
|
|
@@ -3486,7 +3486,7 @@ var require_polyfills = __commonJS({
|
|
|
3486
3486
|
var chdir;
|
|
3487
3487
|
module.exports = patch;
|
|
3488
3488
|
function patch(fs5) {
|
|
3489
|
-
if (
|
|
3489
|
+
if (constants6.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
|
|
3490
3490
|
patchLchmod(fs5);
|
|
3491
3491
|
}
|
|
3492
3492
|
if (!fs5.lutimes) {
|
|
@@ -3524,9 +3524,9 @@ var require_polyfills = __commonJS({
|
|
|
3524
3524
|
fs5.lchownSync = function() {
|
|
3525
3525
|
};
|
|
3526
3526
|
}
|
|
3527
|
-
if (
|
|
3527
|
+
if (platform3 === "win32") {
|
|
3528
3528
|
fs5.rename = typeof fs5.rename !== "function" ? fs5.rename : (function(fs$rename) {
|
|
3529
|
-
function
|
|
3529
|
+
function rename2(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(
|
|
3550
|
-
return
|
|
3549
|
+
if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
|
|
3550
|
+
return rename2;
|
|
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
|
-
|
|
3591
|
+
constants6.O_WRONLY | constants6.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,
|
|
3607
|
+
var fd = fs6.openSync(path9, constants6.O_WRONLY | constants6.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 (
|
|
3627
|
+
if (constants6.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
|
|
3628
3628
|
fs6.lutimes = function(path9, at, mt, cb) {
|
|
3629
|
-
fs6.open(path9,
|
|
3629
|
+
fs6.open(path9, constants6.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,
|
|
3642
|
+
var fd = fs6.openSync(path9, constants6.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 =
|
|
3955
|
-
function
|
|
3954
|
+
fs6.readFile = readFile6;
|
|
3955
|
+
function readFile6(path9, options, cb) {
|
|
3956
3956
|
if (typeof options === "function")
|
|
3957
3957
|
cb = options, options = null;
|
|
3958
3958
|
return go$readFile(path9, options, cb);
|
|
@@ -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
|
|
5389
|
+
var join11 = __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
|
-
|
|
5415
|
-
|
|
5414
|
+
join11(etc, name, "config"),
|
|
5415
|
+
join11(etc, name + "rc")
|
|
5416
5416
|
].forEach(addConfigFile);
|
|
5417
5417
|
if (home)
|
|
5418
5418
|
[
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5419
|
+
join11(home, ".config", name, "config"),
|
|
5420
|
+
join11(home, ".config", name),
|
|
5421
|
+
join11(home, "." + name, "config"),
|
|
5422
|
+
join11(home, "." + name + "rc")
|
|
5423
5423
|
].forEach(addConfigFile);
|
|
5424
5424
|
addConfigFile(cc.find("." + name + "rc"));
|
|
5425
5425
|
if (env5.config) addConfigFile(env5.config);
|
|
@@ -5436,10 +5436,10 @@ 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
|
|
5439
|
+
var constants6 = __require("constants");
|
|
5440
5440
|
var origCwd = process.cwd;
|
|
5441
5441
|
var cwd = null;
|
|
5442
|
-
var
|
|
5442
|
+
var platform3 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
|
|
5443
5443
|
process.cwd = function() {
|
|
5444
5444
|
if (!cwd)
|
|
5445
5445
|
cwd = origCwd.call(process);
|
|
@@ -5460,7 +5460,7 @@ var require_polyfills2 = __commonJS({
|
|
|
5460
5460
|
var chdir;
|
|
5461
5461
|
module.exports = patch;
|
|
5462
5462
|
function patch(fs5) {
|
|
5463
|
-
if (
|
|
5463
|
+
if (constants6.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
|
|
5464
5464
|
patchLchmod(fs5);
|
|
5465
5465
|
}
|
|
5466
5466
|
if (!fs5.lutimes) {
|
|
@@ -5498,9 +5498,9 @@ var require_polyfills2 = __commonJS({
|
|
|
5498
5498
|
fs5.lchownSync = function() {
|
|
5499
5499
|
};
|
|
5500
5500
|
}
|
|
5501
|
-
if (
|
|
5501
|
+
if (platform3 === "win32") {
|
|
5502
5502
|
fs5.rename = typeof fs5.rename !== "function" ? fs5.rename : (function(fs$rename) {
|
|
5503
|
-
function
|
|
5503
|
+
function rename2(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(
|
|
5524
|
-
return
|
|
5523
|
+
if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
|
|
5524
|
+
return rename2;
|
|
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
|
-
|
|
5565
|
+
constants6.O_WRONLY | constants6.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,
|
|
5581
|
+
var fd = fs6.openSync(path9, constants6.O_WRONLY | constants6.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 (
|
|
5601
|
+
if (constants6.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
|
|
5602
5602
|
fs6.lutimes = function(path9, at, mt, cb) {
|
|
5603
|
-
fs6.open(path9,
|
|
5603
|
+
fs6.open(path9, constants6.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,
|
|
5616
|
+
var fd = fs6.openSync(path9, constants6.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 =
|
|
5929
|
-
function
|
|
5928
|
+
fs6.readFile = readFile6;
|
|
5929
|
+
function readFile6(path9, options, cb) {
|
|
5930
5930
|
if (typeof options === "function")
|
|
5931
5931
|
cb = options, options = null;
|
|
5932
5932
|
return go$readFile(path9, options, cb);
|
|
@@ -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
|
|
6761
|
-
if (
|
|
6760
|
+
const dirname10 = path9.dirname(name2);
|
|
6761
|
+
if (dirname10 === name2) {
|
|
6762
6762
|
return original;
|
|
6763
6763
|
}
|
|
6764
|
-
return find(
|
|
6764
|
+
return find(dirname10, original);
|
|
6765
6765
|
} catch (error) {
|
|
6766
6766
|
if (name2 === original) {
|
|
6767
6767
|
if (error.code === "ENOENT") {
|
|
@@ -7950,20 +7950,20 @@ var require_range = __commonJS({
|
|
|
7950
7950
|
};
|
|
7951
7951
|
var replaceTilde = (comp, options) => {
|
|
7952
7952
|
const r2 = options.loose ? re[t.TILDELOOSE] : re[t.TILDE];
|
|
7953
|
-
return comp.replace(r2, (_3,
|
|
7954
|
-
debug("tilde", comp, _3,
|
|
7953
|
+
return comp.replace(r2, (_3, M3, m2, p2, pr) => {
|
|
7954
|
+
debug("tilde", comp, _3, M3, m2, p2, pr);
|
|
7955
7955
|
let ret;
|
|
7956
|
-
if (isX(
|
|
7956
|
+
if (isX(M3)) {
|
|
7957
7957
|
ret = "";
|
|
7958
7958
|
} else if (isX(m2)) {
|
|
7959
|
-
ret = `>=${
|
|
7959
|
+
ret = `>=${M3}.0.0 <${+M3 + 1}.0.0-0`;
|
|
7960
7960
|
} else if (isX(p2)) {
|
|
7961
|
-
ret = `>=${
|
|
7961
|
+
ret = `>=${M3}.${m2}.0 <${M3}.${+m2 + 1}.0-0`;
|
|
7962
7962
|
} else if (pr) {
|
|
7963
7963
|
debug("replaceTilde pr", pr);
|
|
7964
|
-
ret = `>=${
|
|
7964
|
+
ret = `>=${M3}.${m2}.${p2}-${pr} <${M3}.${+m2 + 1}.0-0`;
|
|
7965
7965
|
} else {
|
|
7966
|
-
ret = `>=${
|
|
7966
|
+
ret = `>=${M3}.${m2}.${p2} <${M3}.${+m2 + 1}.0-0`;
|
|
7967
7967
|
}
|
|
7968
7968
|
debug("tilde return", ret);
|
|
7969
7969
|
return ret;
|
|
@@ -7976,40 +7976,40 @@ var require_range = __commonJS({
|
|
|
7976
7976
|
debug("caret", comp, options);
|
|
7977
7977
|
const r2 = options.loose ? re[t.CARETLOOSE] : re[t.CARET];
|
|
7978
7978
|
const z2 = options.includePrerelease ? "-0" : "";
|
|
7979
|
-
return comp.replace(r2, (_3,
|
|
7980
|
-
debug("caret", comp, _3,
|
|
7979
|
+
return comp.replace(r2, (_3, M3, m2, p2, pr) => {
|
|
7980
|
+
debug("caret", comp, _3, M3, m2, p2, pr);
|
|
7981
7981
|
let ret;
|
|
7982
|
-
if (isX(
|
|
7982
|
+
if (isX(M3)) {
|
|
7983
7983
|
ret = "";
|
|
7984
7984
|
} else if (isX(m2)) {
|
|
7985
|
-
ret = `>=${
|
|
7985
|
+
ret = `>=${M3}.0.0${z2} <${+M3 + 1}.0.0-0`;
|
|
7986
7986
|
} else if (isX(p2)) {
|
|
7987
|
-
if (
|
|
7988
|
-
ret = `>=${
|
|
7987
|
+
if (M3 === "0") {
|
|
7988
|
+
ret = `>=${M3}.${m2}.0${z2} <${M3}.${+m2 + 1}.0-0`;
|
|
7989
7989
|
} else {
|
|
7990
|
-
ret = `>=${
|
|
7990
|
+
ret = `>=${M3}.${m2}.0${z2} <${+M3 + 1}.0.0-0`;
|
|
7991
7991
|
}
|
|
7992
7992
|
} else if (pr) {
|
|
7993
7993
|
debug("replaceCaret pr", pr);
|
|
7994
|
-
if (
|
|
7994
|
+
if (M3 === "0") {
|
|
7995
7995
|
if (m2 === "0") {
|
|
7996
|
-
ret = `>=${
|
|
7996
|
+
ret = `>=${M3}.${m2}.${p2}-${pr} <${M3}.${m2}.${+p2 + 1}-0`;
|
|
7997
7997
|
} else {
|
|
7998
|
-
ret = `>=${
|
|
7998
|
+
ret = `>=${M3}.${m2}.${p2}-${pr} <${M3}.${+m2 + 1}.0-0`;
|
|
7999
7999
|
}
|
|
8000
8000
|
} else {
|
|
8001
|
-
ret = `>=${
|
|
8001
|
+
ret = `>=${M3}.${m2}.${p2}-${pr} <${+M3 + 1}.0.0-0`;
|
|
8002
8002
|
}
|
|
8003
8003
|
} else {
|
|
8004
8004
|
debug("no pr");
|
|
8005
|
-
if (
|
|
8005
|
+
if (M3 === "0") {
|
|
8006
8006
|
if (m2 === "0") {
|
|
8007
|
-
ret = `>=${
|
|
8007
|
+
ret = `>=${M3}.${m2}.${p2}${z2} <${M3}.${m2}.${+p2 + 1}-0`;
|
|
8008
8008
|
} else {
|
|
8009
|
-
ret = `>=${
|
|
8009
|
+
ret = `>=${M3}.${m2}.${p2}${z2} <${M3}.${+m2 + 1}.0-0`;
|
|
8010
8010
|
}
|
|
8011
8011
|
} else {
|
|
8012
|
-
ret = `>=${
|
|
8012
|
+
ret = `>=${M3}.${m2}.${p2} <${+M3 + 1}.0.0-0`;
|
|
8013
8013
|
}
|
|
8014
8014
|
}
|
|
8015
8015
|
debug("caret return", ret);
|
|
@@ -8023,9 +8023,9 @@ var require_range = __commonJS({
|
|
|
8023
8023
|
var replaceXRange = (comp, options) => {
|
|
8024
8024
|
comp = comp.trim();
|
|
8025
8025
|
const r2 = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE];
|
|
8026
|
-
return comp.replace(r2, (ret, gtlt,
|
|
8027
|
-
debug("xRange", comp, ret, gtlt,
|
|
8028
|
-
const xM = isX(
|
|
8026
|
+
return comp.replace(r2, (ret, gtlt, M3, m2, p2, pr) => {
|
|
8027
|
+
debug("xRange", comp, ret, gtlt, M3, m2, p2, pr);
|
|
8028
|
+
const xM = isX(M3);
|
|
8029
8029
|
const xm = xM || isX(m2);
|
|
8030
8030
|
const xp = xm || isX(p2);
|
|
8031
8031
|
const anyX = xp;
|
|
@@ -8047,7 +8047,7 @@ var require_range = __commonJS({
|
|
|
8047
8047
|
if (gtlt === ">") {
|
|
8048
8048
|
gtlt = ">=";
|
|
8049
8049
|
if (xm) {
|
|
8050
|
-
|
|
8050
|
+
M3 = +M3 + 1;
|
|
8051
8051
|
m2 = 0;
|
|
8052
8052
|
p2 = 0;
|
|
8053
8053
|
} else {
|
|
@@ -8057,7 +8057,7 @@ var require_range = __commonJS({
|
|
|
8057
8057
|
} else if (gtlt === "<=") {
|
|
8058
8058
|
gtlt = "<";
|
|
8059
8059
|
if (xm) {
|
|
8060
|
-
|
|
8060
|
+
M3 = +M3 + 1;
|
|
8061
8061
|
} else {
|
|
8062
8062
|
m2 = +m2 + 1;
|
|
8063
8063
|
}
|
|
@@ -8065,11 +8065,11 @@ var require_range = __commonJS({
|
|
|
8065
8065
|
if (gtlt === "<") {
|
|
8066
8066
|
pr = "-0";
|
|
8067
8067
|
}
|
|
8068
|
-
ret = `${gtlt +
|
|
8068
|
+
ret = `${gtlt + M3}.${m2}.${p2}${pr}`;
|
|
8069
8069
|
} else if (xm) {
|
|
8070
|
-
ret = `>=${
|
|
8070
|
+
ret = `>=${M3}.0.0${pr} <${+M3 + 1}.0.0-0`;
|
|
8071
8071
|
} else if (xp) {
|
|
8072
|
-
ret = `>=${
|
|
8072
|
+
ret = `>=${M3}.${m2}.0${pr} <${M3}.${+m2 + 1}.0-0`;
|
|
8073
8073
|
}
|
|
8074
8074
|
debug("xRange return", ret);
|
|
8075
8075
|
return ret;
|
|
@@ -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
|
|
8732
|
+
var constants6 = 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:
|
|
8815
|
-
RELEASE_TYPES:
|
|
8814
|
+
SEMVER_SPEC_VERSION: constants6.SEMVER_SPEC_VERSION,
|
|
8815
|
+
RELEASE_TYPES: constants6.RELEASE_TYPES,
|
|
8816
8816
|
compareIdentifiers: identifiers.compareIdentifiers,
|
|
8817
8817
|
rcompareIdentifiers: identifiers.rcompareIdentifiers
|
|
8818
8818
|
};
|
|
@@ -9422,7 +9422,7 @@ var require_picocolors = __commonJS({
|
|
|
9422
9422
|
});
|
|
9423
9423
|
|
|
9424
9424
|
// src/index.ts
|
|
9425
|
-
import { homedir as
|
|
9425
|
+
import { homedir as homedir8 } from "node:os";
|
|
9426
9426
|
|
|
9427
9427
|
// src/cli.ts
|
|
9428
9428
|
import { createRequire } from "node:module";
|
|
@@ -9781,14 +9781,14 @@ var Temp = {
|
|
|
9781
9781
|
}
|
|
9782
9782
|
},
|
|
9783
9783
|
truncate: (filePath) => {
|
|
9784
|
-
const
|
|
9785
|
-
if (
|
|
9784
|
+
const basename10 = path2.basename(filePath);
|
|
9785
|
+
if (basename10.length <= LIMIT_BASENAME_LENGTH)
|
|
9786
9786
|
return filePath;
|
|
9787
|
-
const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(
|
|
9787
|
+
const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename10);
|
|
9788
9788
|
if (!truncable)
|
|
9789
9789
|
return filePath;
|
|
9790
|
-
const truncationLength =
|
|
9791
|
-
return `${filePath.slice(0, -
|
|
9790
|
+
const truncationLength = basename10.length - LIMIT_BASENAME_LENGTH;
|
|
9791
|
+
return `${filePath.slice(0, -basename10.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
|
|
9792
9792
|
}
|
|
9793
9793
|
};
|
|
9794
9794
|
node_default(Temp.purgeSyncAll);
|
|
@@ -14000,6 +14000,29 @@ ${import_picocolors2.default.gray(d2)} ${t}
|
|
|
14000
14000
|
|
|
14001
14001
|
`);
|
|
14002
14002
|
};
|
|
14003
|
+
var M2 = { message: (t = "", { symbol: n = import_picocolors2.default.gray(o) } = {}) => {
|
|
14004
|
+
const r2 = [`${import_picocolors2.default.gray(o)}`];
|
|
14005
|
+
if (t) {
|
|
14006
|
+
const [i, ...s] = t.split(`
|
|
14007
|
+
`);
|
|
14008
|
+
r2.push(`${n} ${i}`, ...s.map((c) => `${import_picocolors2.default.gray(o)} ${c}`));
|
|
14009
|
+
}
|
|
14010
|
+
process.stdout.write(`${r2.join(`
|
|
14011
|
+
`)}
|
|
14012
|
+
`);
|
|
14013
|
+
}, info: (t) => {
|
|
14014
|
+
M2.message(t, { symbol: import_picocolors2.default.blue(q) });
|
|
14015
|
+
}, success: (t) => {
|
|
14016
|
+
M2.message(t, { symbol: import_picocolors2.default.green(D) });
|
|
14017
|
+
}, step: (t) => {
|
|
14018
|
+
M2.message(t, { symbol: import_picocolors2.default.green(C) });
|
|
14019
|
+
}, warn: (t) => {
|
|
14020
|
+
M2.message(t, { symbol: import_picocolors2.default.yellow(U) });
|
|
14021
|
+
}, warning: (t) => {
|
|
14022
|
+
M2.warn(t);
|
|
14023
|
+
}, error: (t) => {
|
|
14024
|
+
M2.message(t, { symbol: import_picocolors2.default.red(K2) });
|
|
14025
|
+
} };
|
|
14003
14026
|
var J2 = `${import_picocolors2.default.gray(o)} `;
|
|
14004
14027
|
|
|
14005
14028
|
// src/headless.ts
|
|
@@ -14127,6 +14150,7 @@ function formatRelativeAge(timestampSeconds) {
|
|
|
14127
14150
|
|
|
14128
14151
|
// src/worktree-management.ts
|
|
14129
14152
|
import { execFile as execFile2 } from "node:child_process";
|
|
14153
|
+
import { rm } from "node:fs/promises";
|
|
14130
14154
|
import { promisify as promisify3 } from "node:util";
|
|
14131
14155
|
var execFileAsync2 = promisify3(execFile2);
|
|
14132
14156
|
var GIT_ENV = { ...process.env, LC_ALL: "C" };
|
|
@@ -14147,10 +14171,24 @@ async function removeWorktree(repoRoot, worktreePath) {
|
|
|
14147
14171
|
});
|
|
14148
14172
|
}
|
|
14149
14173
|
async function forceRemoveWorktree(repoRoot, worktreePath) {
|
|
14150
|
-
|
|
14151
|
-
|
|
14152
|
-
|
|
14153
|
-
|
|
14174
|
+
try {
|
|
14175
|
+
await execFileAsync2(
|
|
14176
|
+
"git",
|
|
14177
|
+
["worktree", "remove", "--force", worktreePath],
|
|
14178
|
+
{
|
|
14179
|
+
cwd: repoRoot,
|
|
14180
|
+
env: GIT_ENV
|
|
14181
|
+
}
|
|
14182
|
+
);
|
|
14183
|
+
} catch (error) {
|
|
14184
|
+
const worktreeIsStillRegistered = (await listWorktrees(repoRoot)).some(
|
|
14185
|
+
(worktree) => worktree.path === worktreePath
|
|
14186
|
+
);
|
|
14187
|
+
if (worktreeIsStillRegistered) {
|
|
14188
|
+
throw error;
|
|
14189
|
+
}
|
|
14190
|
+
await rm(worktreePath, { force: true, recursive: true });
|
|
14191
|
+
}
|
|
14154
14192
|
}
|
|
14155
14193
|
async function deleteBranch(repoRoot, branch) {
|
|
14156
14194
|
await execFileAsync2("git", ["branch", "-d", branch], {
|
|
@@ -14164,8 +14202,14 @@ async function forceDeleteBranch(repoRoot, branch) {
|
|
|
14164
14202
|
env: GIT_ENV
|
|
14165
14203
|
});
|
|
14166
14204
|
}
|
|
14167
|
-
function
|
|
14168
|
-
|
|
14205
|
+
function isWorktreeForceRemovalError(error) {
|
|
14206
|
+
if (!hasStderr(error)) {
|
|
14207
|
+
return false;
|
|
14208
|
+
}
|
|
14209
|
+
return error.stderr.includes("contains modified or untracked files") || isWorktreeDeletionError(error);
|
|
14210
|
+
}
|
|
14211
|
+
function isWorktreeDeletionError(error) {
|
|
14212
|
+
return hasStderr(error) && error.stderr.includes("failed to delete");
|
|
14169
14213
|
}
|
|
14170
14214
|
function isBranchUnmergedError(error) {
|
|
14171
14215
|
return hasStderr(error) && error.stderr.includes("is not fully merged");
|
|
@@ -14176,16 +14220,394 @@ function hasStderr(error) {
|
|
|
14176
14220
|
|
|
14177
14221
|
// src/worktree-picker.ts
|
|
14178
14222
|
import { env as env4, platform, stdin, stdout } from "node:process";
|
|
14179
|
-
|
|
14180
|
-
|
|
14223
|
+
|
|
14224
|
+
// src/pull-requests.ts
|
|
14225
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
14226
|
+
import { promisify as promisify4 } from "node:util";
|
|
14227
|
+
var execFileAsync3 = promisify4(execFile3);
|
|
14228
|
+
var PULL_REQUEST_QUERY_TIMEOUT_MS = 2500;
|
|
14229
|
+
function parsePullRequestRemote(remoteUrl) {
|
|
14230
|
+
const parsed = parseRemoteUrl(remoteUrl.trim());
|
|
14231
|
+
if (parsed === null) return null;
|
|
14232
|
+
const forge = detectForge(parsed.host);
|
|
14233
|
+
if (forge === null) return null;
|
|
14234
|
+
if (forge === "bitbucket" && parsed.host !== "bitbucket.org") return null;
|
|
14235
|
+
const path9 = parsed.path.replace(/^\/+/, "").replace(/\.git$/, "").split("/").filter(Boolean);
|
|
14236
|
+
if (path9.length < 2) return null;
|
|
14237
|
+
if (forge === "gitlab") {
|
|
14238
|
+
const repository2 = path9.at(-1);
|
|
14239
|
+
const namespace2 = path9.slice(0, -1).join("/");
|
|
14240
|
+
if (repository2 === void 0 || namespace2.length === 0) return null;
|
|
14241
|
+
return {
|
|
14242
|
+
forge,
|
|
14243
|
+
host: parsed.host,
|
|
14244
|
+
namespace: namespace2,
|
|
14245
|
+
repository: repository2,
|
|
14246
|
+
webBaseUrl: `https://${parsed.host}`
|
|
14247
|
+
};
|
|
14248
|
+
}
|
|
14249
|
+
const namespace = path9.at(-2);
|
|
14250
|
+
const repository = path9.at(-1);
|
|
14251
|
+
if (namespace === void 0 || repository === void 0) return null;
|
|
14252
|
+
return {
|
|
14253
|
+
forge,
|
|
14254
|
+
host: parsed.host,
|
|
14255
|
+
namespace,
|
|
14256
|
+
repository,
|
|
14257
|
+
webBaseUrl: `https://${parsed.host}`
|
|
14258
|
+
};
|
|
14259
|
+
}
|
|
14260
|
+
function createPullRequestQuery(dependencies = {}) {
|
|
14261
|
+
const runCommand = dependencies.runCommand ?? defaultRunCommand;
|
|
14262
|
+
const fetcher = dependencies.fetch ?? fetch;
|
|
14263
|
+
return {
|
|
14264
|
+
findOpenPullRequest: (repoRoot, number) => findOpenPullRequest(repoRoot, number, runCommand, fetcher),
|
|
14265
|
+
listOpenPullRequests: (repoRoot, sourceBranch) => listOpenPullRequests(repoRoot, sourceBranch, runCommand, fetcher),
|
|
14266
|
+
listOpenPullRequestsForRepository: (repoRoot) => listOpenPullRequestsForRepository(repoRoot, runCommand, fetcher)
|
|
14267
|
+
};
|
|
14268
|
+
}
|
|
14269
|
+
async function listOpenPullRequests(repoRoot, sourceBranch, runCommand, fetcher) {
|
|
14270
|
+
const remote = await readPullRequestRemote(repoRoot, runCommand);
|
|
14271
|
+
const cliResult = await queryProviderCli(
|
|
14272
|
+
remote,
|
|
14273
|
+
{ kind: "branch", sourceBranch },
|
|
14274
|
+
repoRoot,
|
|
14275
|
+
runCommand
|
|
14276
|
+
);
|
|
14277
|
+
if (cliResult !== null) return sortPullRequests(cliResult);
|
|
14278
|
+
const apiResult = await queryPublicApi(
|
|
14279
|
+
remote,
|
|
14280
|
+
{ kind: "branch", sourceBranch },
|
|
14281
|
+
fetcher
|
|
14282
|
+
);
|
|
14283
|
+
return sortPullRequests(apiResult);
|
|
14284
|
+
}
|
|
14285
|
+
async function listOpenPullRequestsForRepository(repoRoot, runCommand, fetcher) {
|
|
14286
|
+
const remote = await readPullRequestRemote(repoRoot, runCommand);
|
|
14287
|
+
const cliResult = await queryProviderCli(
|
|
14288
|
+
remote,
|
|
14289
|
+
{ kind: "repository" },
|
|
14290
|
+
repoRoot,
|
|
14291
|
+
runCommand
|
|
14292
|
+
);
|
|
14293
|
+
if (cliResult !== null) return sortPullRequests(cliResult);
|
|
14294
|
+
const apiResult = await queryPublicApi(
|
|
14295
|
+
remote,
|
|
14296
|
+
{ kind: "repository" },
|
|
14297
|
+
fetcher
|
|
14298
|
+
);
|
|
14299
|
+
return sortPullRequests(apiResult);
|
|
14300
|
+
}
|
|
14301
|
+
async function findOpenPullRequest(repoRoot, number, runCommand, fetcher) {
|
|
14302
|
+
const remote = await readPullRequestRemote(repoRoot, runCommand);
|
|
14303
|
+
const cliResult = await queryProviderCli(
|
|
14304
|
+
remote,
|
|
14305
|
+
{ kind: "number", number },
|
|
14306
|
+
repoRoot,
|
|
14307
|
+
runCommand
|
|
14308
|
+
);
|
|
14309
|
+
if (cliResult !== null) return cliResult[0] ?? null;
|
|
14310
|
+
const apiResult = await queryPublicApi(
|
|
14311
|
+
remote,
|
|
14312
|
+
{ kind: "number", number },
|
|
14313
|
+
fetcher
|
|
14314
|
+
);
|
|
14315
|
+
return apiResult[0] ?? null;
|
|
14316
|
+
}
|
|
14317
|
+
async function readPullRequestRemote(repoRoot, runCommand) {
|
|
14318
|
+
const result = await runCommand(
|
|
14319
|
+
"git",
|
|
14320
|
+
["remote", "get-url", "origin"],
|
|
14321
|
+
commandOptions(repoRoot)
|
|
14322
|
+
);
|
|
14323
|
+
const remote = parsePullRequestRemote(result.stdout);
|
|
14324
|
+
if (remote === null) {
|
|
14325
|
+
throw new Error("origin remote is unsupported or missing");
|
|
14326
|
+
}
|
|
14327
|
+
return remote;
|
|
14328
|
+
}
|
|
14329
|
+
async function queryProviderCli(remote, query, repoRoot, runCommand) {
|
|
14330
|
+
const providerCommand = providerCliCommand(remote, query);
|
|
14331
|
+
try {
|
|
14332
|
+
const result = await runCommand(
|
|
14333
|
+
providerCommand.command,
|
|
14334
|
+
providerCommand.args,
|
|
14335
|
+
commandOptions(repoRoot)
|
|
14336
|
+
);
|
|
14337
|
+
return parseProviderOutput(remote, result.stdout, query);
|
|
14338
|
+
} catch {
|
|
14339
|
+
return null;
|
|
14340
|
+
}
|
|
14341
|
+
}
|
|
14342
|
+
async function queryPublicApi(remote, query, fetcher) {
|
|
14343
|
+
const url = publicApiUrl(remote, query);
|
|
14344
|
+
const controller = new AbortController();
|
|
14345
|
+
const timer = setTimeout(
|
|
14346
|
+
() => controller.abort(),
|
|
14347
|
+
PULL_REQUEST_QUERY_TIMEOUT_MS
|
|
14348
|
+
);
|
|
14349
|
+
try {
|
|
14350
|
+
const response = await fetcher(url, {
|
|
14351
|
+
headers: { Accept: "application/json" },
|
|
14352
|
+
signal: controller.signal
|
|
14353
|
+
});
|
|
14354
|
+
if (response.status === 404 && query.kind === "number") return [];
|
|
14355
|
+
if (!response.ok) {
|
|
14356
|
+
throw new Error(`public API returned HTTP ${response.status}`);
|
|
14357
|
+
}
|
|
14358
|
+
const payload = await response.json();
|
|
14359
|
+
return parseApiOutput(remote, payload, query);
|
|
14360
|
+
} finally {
|
|
14361
|
+
clearTimeout(timer);
|
|
14362
|
+
}
|
|
14363
|
+
}
|
|
14364
|
+
function parseRemoteUrl(remoteUrl) {
|
|
14365
|
+
const scpMatch = remoteUrl.match(/^[^@]+@([^:]+):(.+)$/);
|
|
14366
|
+
if (scpMatch) {
|
|
14367
|
+
return { host: scpMatch[1].toLowerCase(), path: scpMatch[2] };
|
|
14368
|
+
}
|
|
14369
|
+
try {
|
|
14370
|
+
const url = new URL(remoteUrl);
|
|
14371
|
+
if (!url.hostname) return null;
|
|
14372
|
+
return { host: url.hostname.toLowerCase(), path: url.pathname };
|
|
14373
|
+
} catch {
|
|
14374
|
+
return null;
|
|
14375
|
+
}
|
|
14376
|
+
}
|
|
14377
|
+
function detectForge(host) {
|
|
14378
|
+
if (host === "github.com" || host.includes("github")) return "github";
|
|
14379
|
+
if (host === "gitlab.com" || host.includes("gitlab")) return "gitlab";
|
|
14380
|
+
if (host === "bitbucket.org" || host.includes("bitbucket")) {
|
|
14381
|
+
return "bitbucket";
|
|
14382
|
+
}
|
|
14383
|
+
return null;
|
|
14384
|
+
}
|
|
14385
|
+
function providerCliCommand(remote, query) {
|
|
14386
|
+
const coordinate = providerRepositoryCoordinate(remote);
|
|
14387
|
+
const sourceBranch = query.sourceBranch ?? "";
|
|
14388
|
+
switch (remote.forge) {
|
|
14389
|
+
case "github": {
|
|
14390
|
+
if (query.kind === "number") {
|
|
14391
|
+
return {
|
|
14392
|
+
args: [
|
|
14393
|
+
"pr",
|
|
14394
|
+
"view",
|
|
14395
|
+
String(query.number),
|
|
14396
|
+
"--json",
|
|
14397
|
+
"number,url,headRefName,state",
|
|
14398
|
+
"--repo",
|
|
14399
|
+
coordinate
|
|
14400
|
+
],
|
|
14401
|
+
command: "gh"
|
|
14402
|
+
};
|
|
14403
|
+
}
|
|
14404
|
+
return {
|
|
14405
|
+
args: [
|
|
14406
|
+
"pr",
|
|
14407
|
+
"list",
|
|
14408
|
+
"--state",
|
|
14409
|
+
"open",
|
|
14410
|
+
...query.kind === "branch" ? ["--head", sourceBranch] : [],
|
|
14411
|
+
"--json",
|
|
14412
|
+
"number,url,headRefName,state",
|
|
14413
|
+
"--limit",
|
|
14414
|
+
"100",
|
|
14415
|
+
"--repo",
|
|
14416
|
+
coordinate
|
|
14417
|
+
],
|
|
14418
|
+
command: "gh"
|
|
14419
|
+
};
|
|
14420
|
+
}
|
|
14421
|
+
case "gitlab": {
|
|
14422
|
+
if (query.kind === "number") {
|
|
14423
|
+
return {
|
|
14424
|
+
args: [
|
|
14425
|
+
"mr",
|
|
14426
|
+
"view",
|
|
14427
|
+
String(query.number),
|
|
14428
|
+
"--output",
|
|
14429
|
+
"json",
|
|
14430
|
+
"--repo",
|
|
14431
|
+
coordinate
|
|
14432
|
+
],
|
|
14433
|
+
command: "glab"
|
|
14434
|
+
};
|
|
14435
|
+
}
|
|
14436
|
+
return {
|
|
14437
|
+
args: [
|
|
14438
|
+
"mr",
|
|
14439
|
+
"list",
|
|
14440
|
+
"--state",
|
|
14441
|
+
"opened",
|
|
14442
|
+
...query.kind === "branch" ? ["--source-branch", sourceBranch] : [],
|
|
14443
|
+
"--output",
|
|
14444
|
+
"json",
|
|
14445
|
+
"--per-page",
|
|
14446
|
+
"100",
|
|
14447
|
+
"--repo",
|
|
14448
|
+
coordinate
|
|
14449
|
+
],
|
|
14450
|
+
command: "glab"
|
|
14451
|
+
};
|
|
14452
|
+
}
|
|
14453
|
+
case "bitbucket": {
|
|
14454
|
+
if (query.kind === "number") {
|
|
14455
|
+
return {
|
|
14456
|
+
args: [
|
|
14457
|
+
"pr",
|
|
14458
|
+
"view",
|
|
14459
|
+
String(query.number),
|
|
14460
|
+
"--format",
|
|
14461
|
+
"json",
|
|
14462
|
+
"--repo",
|
|
14463
|
+
coordinate
|
|
14464
|
+
],
|
|
14465
|
+
command: "bb"
|
|
14466
|
+
};
|
|
14467
|
+
}
|
|
14468
|
+
return {
|
|
14469
|
+
args: [
|
|
14470
|
+
"pr",
|
|
14471
|
+
"list",
|
|
14472
|
+
"--state",
|
|
14473
|
+
"OPEN",
|
|
14474
|
+
...query.kind === "branch" ? ["--source", sourceBranch] : [],
|
|
14475
|
+
"--format",
|
|
14476
|
+
"json",
|
|
14477
|
+
"--repo",
|
|
14478
|
+
coordinate
|
|
14479
|
+
],
|
|
14480
|
+
command: "bb"
|
|
14481
|
+
};
|
|
14482
|
+
}
|
|
14483
|
+
}
|
|
14484
|
+
}
|
|
14485
|
+
function providerRepositoryCoordinate(remote) {
|
|
14486
|
+
const coordinate = `${remote.namespace}/${remote.repository}`;
|
|
14487
|
+
return remote.host === "github.com" || remote.host === "bitbucket.org" ? coordinate : `${remote.host}/${coordinate}`;
|
|
14488
|
+
}
|
|
14489
|
+
function parseProviderOutput(remote, stdout2, query) {
|
|
14490
|
+
const payload = JSON.parse(stdout2);
|
|
14491
|
+
const values = remote.forge === "bitbucket" && isRecord(payload) ? Array.isArray(payload.values) ? payload.values : [] : Array.isArray(payload) ? payload : [payload];
|
|
14492
|
+
return values.map((value) => normalizePullRequest(value, query)).filter((value) => value !== null).filter(
|
|
14493
|
+
(value) => query.kind === "branch" ? value.sourceBranch === query.sourceBranch : query.kind === "number" ? value.number === query.number : true
|
|
14494
|
+
);
|
|
14495
|
+
}
|
|
14496
|
+
function parseApiOutput(remote, payload, query) {
|
|
14497
|
+
const values = remote.forge === "bitbucket" && isRecord(payload) ? Array.isArray(payload.values) ? payload.values : [] : query.kind === "number" ? [payload] : Array.isArray(payload) ? payload : [];
|
|
14498
|
+
return values.map((value) => normalizePullRequest(value, query)).filter((value) => value !== null).filter(
|
|
14499
|
+
(value) => query.kind === "branch" ? value.sourceBranch === query.sourceBranch : query.kind === "number" ? value.number === query.number : true
|
|
14500
|
+
);
|
|
14501
|
+
}
|
|
14502
|
+
function normalizePullRequest(value, query) {
|
|
14503
|
+
if (!isRecord(value)) return null;
|
|
14504
|
+
const number = numberValue(value.number ?? value.iid ?? value.id);
|
|
14505
|
+
const head = isRecord(value.head) ? value.head : null;
|
|
14506
|
+
const source = isRecord(value.source) ? value.source : null;
|
|
14507
|
+
const sourceBranch = stringValue(
|
|
14508
|
+
value.headRefName ?? (head !== null ? head.ref : void 0) ?? value.source_branch ?? (source !== null && isRecord(source.branch) ? source.branch.name : void 0)
|
|
14509
|
+
);
|
|
14510
|
+
const url = stringValue(
|
|
14511
|
+
value.html_url ?? value.web_url ?? value.url ?? (isRecord(value.links) && isRecord(value.links.html) ? value.links.html.href : void 0)
|
|
14512
|
+
);
|
|
14513
|
+
const state = stringValue(value.state)?.toLowerCase();
|
|
14514
|
+
if (number === null || url === null) return null;
|
|
14515
|
+
if (query.kind !== "number" && sourceBranch === null) return null;
|
|
14516
|
+
if (query.kind === "number" && number !== query.number) return null;
|
|
14517
|
+
if (state !== void 0 && !["open", "opened"].includes(state)) return null;
|
|
14518
|
+
return {
|
|
14519
|
+
number,
|
|
14520
|
+
sourceBranch: sourceBranch ?? "",
|
|
14521
|
+
url
|
|
14522
|
+
};
|
|
14523
|
+
}
|
|
14524
|
+
function publicApiUrl(remote, query) {
|
|
14525
|
+
const branch = encodeURIComponent(query.sourceBranch ?? "");
|
|
14526
|
+
const number = query.number === void 0 ? "" : String(query.number);
|
|
14527
|
+
switch (remote.forge) {
|
|
14528
|
+
case "github": {
|
|
14529
|
+
const coordinate = `${encodeURIComponent(remote.namespace)}/${encodeURIComponent(remote.repository)}`;
|
|
14530
|
+
const apiBase = remote.host === "github.com" ? "https://api.github.com" : `https://${remote.host}/api/v3`;
|
|
14531
|
+
if (query.kind === "number") {
|
|
14532
|
+
return `${apiBase}/repos/${coordinate}/pulls/${number}`;
|
|
14533
|
+
}
|
|
14534
|
+
return `${apiBase}/repos/${coordinate}/pulls?state=open&per_page=100`;
|
|
14535
|
+
}
|
|
14536
|
+
case "gitlab": {
|
|
14537
|
+
const project = encodeURIComponent(
|
|
14538
|
+
`${remote.namespace}/${remote.repository}`
|
|
14539
|
+
);
|
|
14540
|
+
if (query.kind === "number") {
|
|
14541
|
+
return `${remote.webBaseUrl}/api/v4/projects/${project}/merge_requests/${number}`;
|
|
14542
|
+
}
|
|
14543
|
+
return query.kind === "branch" ? `${remote.webBaseUrl}/api/v4/projects/${project}/merge_requests?state=opened&source_branch=${branch}&per_page=100` : `${remote.webBaseUrl}/api/v4/projects/${project}/merge_requests?state=opened&per_page=100`;
|
|
14544
|
+
}
|
|
14545
|
+
case "bitbucket": {
|
|
14546
|
+
const coordinate = `${encodeURIComponent(remote.namespace)}/${encodeURIComponent(remote.repository)}`;
|
|
14547
|
+
if (query.kind === "number") {
|
|
14548
|
+
return `https://api.bitbucket.org/2.0/repositories/${coordinate}/pullrequests/${number}`;
|
|
14549
|
+
}
|
|
14550
|
+
return query.kind === "branch" ? `https://api.bitbucket.org/2.0/repositories/${coordinate}/pullrequests?state=OPEN&source.branch.name=${branch}&pagelen=100` : `https://api.bitbucket.org/2.0/repositories/${coordinate}/pullrequests?state=OPEN&pagelen=100`;
|
|
14551
|
+
}
|
|
14552
|
+
}
|
|
14553
|
+
}
|
|
14554
|
+
function sortPullRequests(pullRequests) {
|
|
14555
|
+
return [...pullRequests].sort((a, b3) => a.number - b3.number);
|
|
14556
|
+
}
|
|
14557
|
+
function commandOptions(cwd) {
|
|
14558
|
+
return {
|
|
14559
|
+
cwd,
|
|
14560
|
+
env: {
|
|
14561
|
+
...process.env,
|
|
14562
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
14563
|
+
GH_PROMPT_DISABLED: "1",
|
|
14564
|
+
GLAB_NON_INTERACTIVE: "1"
|
|
14565
|
+
},
|
|
14566
|
+
timeout: PULL_REQUEST_QUERY_TIMEOUT_MS
|
|
14567
|
+
};
|
|
14568
|
+
}
|
|
14569
|
+
async function defaultRunCommand(command, args, options) {
|
|
14570
|
+
return execFileAsync3(command, args, options);
|
|
14571
|
+
}
|
|
14572
|
+
function isRecord(value) {
|
|
14573
|
+
return typeof value === "object" && value !== null;
|
|
14574
|
+
}
|
|
14575
|
+
function stringValue(value) {
|
|
14576
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
14577
|
+
}
|
|
14578
|
+
function numberValue(value) {
|
|
14579
|
+
if (typeof value === "number" && Number.isInteger(value)) return value;
|
|
14580
|
+
if (typeof value === "string" && /^\d+$/.test(value)) return Number(value);
|
|
14581
|
+
return null;
|
|
14582
|
+
}
|
|
14583
|
+
|
|
14584
|
+
// src/worktree-picker.ts
|
|
14585
|
+
var MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY = 4;
|
|
14586
|
+
async function buildWorktreePromptEntries(sources, dependencies = {}) {
|
|
14587
|
+
const pullRequestQuery = createPullRequestQuery();
|
|
14588
|
+
const queryPullRequests = dependencies.queryPullRequests ?? pullRequestQuery.listOpenPullRequests;
|
|
14589
|
+
const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ?? (dependencies.queryPullRequests === void 0 ? pullRequestQuery.listOpenPullRequestsForRepository : void 0);
|
|
14590
|
+
const repositoryPullRequests = queryRepositoryPullRequests === void 0 ? Promise.resolve(null) : readRepositoryPullRequests(sources, queryRepositoryPullRequests);
|
|
14591
|
+
const [history, infos, pullRequestsByRepository] = await Promise.all([
|
|
14181
14592
|
loadHistory(),
|
|
14182
|
-
readWorktreeInfos(sources.map((source) => source.worktree))
|
|
14593
|
+
readWorktreeInfos(sources.map((source) => source.worktree)),
|
|
14594
|
+
repositoryPullRequests
|
|
14183
14595
|
]);
|
|
14596
|
+
const pullRequests = await Promise.all(
|
|
14597
|
+
sources.map(
|
|
14598
|
+
(source) => readSourcePullRequests(
|
|
14599
|
+
source,
|
|
14600
|
+
queryPullRequests,
|
|
14601
|
+
pullRequestsByRepository
|
|
14602
|
+
)
|
|
14603
|
+
)
|
|
14604
|
+
);
|
|
14184
14605
|
const historyByPath = new Map(history.map((entry) => [entry.path, entry]));
|
|
14185
14606
|
const entries = sources.map(
|
|
14186
14607
|
(source, index) => buildWorktreePromptEntry(
|
|
14187
14608
|
source,
|
|
14188
14609
|
infos[index],
|
|
14610
|
+
pullRequests[index],
|
|
14189
14611
|
historyByPath.get(source.worktree.path)?.timestamp ?? null,
|
|
14190
14612
|
Date.now()
|
|
14191
14613
|
)
|
|
@@ -14283,7 +14705,10 @@ function buildSearchableWorktreeEntry(worktree) {
|
|
|
14283
14705
|
searchText: buildPromptSearchText(worktree),
|
|
14284
14706
|
value: worktree.path,
|
|
14285
14707
|
worktree: {
|
|
14286
|
-
branch
|
|
14708
|
+
branch: formatWorktreeBranchLabel(
|
|
14709
|
+
worktree.branch,
|
|
14710
|
+
worktree.pullRequestNumbers
|
|
14711
|
+
),
|
|
14287
14712
|
metadata,
|
|
14288
14713
|
path: worktree.path,
|
|
14289
14714
|
repoName: worktree.repoName
|
|
@@ -14865,14 +15290,19 @@ function buildPromptSearchText(worktree) {
|
|
|
14865
15290
|
worktree.branch ?? "detached",
|
|
14866
15291
|
worktree.path,
|
|
14867
15292
|
worktree.label,
|
|
14868
|
-
`${worktree.repoName}/${worktree.branch ?? "detached"}
|
|
15293
|
+
`${worktree.repoName}/${worktree.branch ?? "detached"}`,
|
|
15294
|
+
...(worktree.pullRequestNumbers ?? []).map((number) => `#${number}`),
|
|
15295
|
+
...worktree.pullRequestUrls ?? []
|
|
14869
15296
|
].join(" ").toLowerCase();
|
|
14870
15297
|
}
|
|
14871
|
-
function buildWorktreePromptEntry(source, info, lastUsedTimestamp, now) {
|
|
15298
|
+
function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp, now) {
|
|
14872
15299
|
const lastWorkedTimestamp = info.lastCommitTimestamp === null ? null : info.lastCommitTimestamp * 1e3;
|
|
14873
15300
|
const lastActivityTimestamp = lastUsedTimestamp ?? lastWorkedTimestamp;
|
|
14874
15301
|
const lastActivityType = lastUsedTimestamp !== null ? "used" : lastWorkedTimestamp !== null ? "worked" : null;
|
|
14875
|
-
const branch =
|
|
15302
|
+
const branch = formatWorktreeBranchLabel(
|
|
15303
|
+
source.worktree.branch,
|
|
15304
|
+
pullRequests.map((pullRequest) => pullRequest.number)
|
|
15305
|
+
);
|
|
14876
15306
|
const badges = buildStatusBadges(info);
|
|
14877
15307
|
const recency = formatPromptRecency(
|
|
14878
15308
|
lastActivityTimestamp,
|
|
@@ -14897,9 +15327,80 @@ function buildWorktreePromptEntry(source, info, lastUsedTimestamp, now) {
|
|
|
14897
15327
|
label,
|
|
14898
15328
|
lastActivityTimestamp,
|
|
14899
15329
|
metadata,
|
|
15330
|
+
pullRequestNumbers: pullRequests.map((pullRequest) => pullRequest.number),
|
|
15331
|
+
pullRequestUrls: pullRequests.map((pullRequest) => pullRequest.url),
|
|
14900
15332
|
repoName: source.repoName
|
|
14901
15333
|
};
|
|
14902
15334
|
}
|
|
15335
|
+
async function readSourcePullRequests(source, queryPullRequests, pullRequestsByRepository) {
|
|
15336
|
+
if (source.repoRoot === void 0 || source.worktree.branch === null) {
|
|
15337
|
+
return [];
|
|
15338
|
+
}
|
|
15339
|
+
if (pullRequestsByRepository !== null) {
|
|
15340
|
+
return sortSourcePullRequests(
|
|
15341
|
+
pullRequestsByRepository.get(source.repoRoot)?.filter(
|
|
15342
|
+
(pullRequest) => pullRequest.sourceBranch === source.worktree.branch
|
|
15343
|
+
)
|
|
15344
|
+
);
|
|
15345
|
+
}
|
|
15346
|
+
try {
|
|
15347
|
+
const pullRequests = await queryPullRequests(
|
|
15348
|
+
source.repoRoot,
|
|
15349
|
+
source.worktree.branch
|
|
15350
|
+
);
|
|
15351
|
+
return sortSourcePullRequests(pullRequests);
|
|
15352
|
+
} catch {
|
|
15353
|
+
return [];
|
|
15354
|
+
}
|
|
15355
|
+
}
|
|
15356
|
+
async function readRepositoryPullRequests(sources, queryPullRequests) {
|
|
15357
|
+
const repoRoots = [
|
|
15358
|
+
...new Set(
|
|
15359
|
+
sources.flatMap(
|
|
15360
|
+
(source) => source.repoRoot === void 0 || source.worktree.branch === null ? [] : [source.repoRoot]
|
|
15361
|
+
)
|
|
15362
|
+
)
|
|
15363
|
+
];
|
|
15364
|
+
const results = await mapWithConcurrency2(
|
|
15365
|
+
repoRoots,
|
|
15366
|
+
MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY,
|
|
15367
|
+
async (repoRoot) => {
|
|
15368
|
+
try {
|
|
15369
|
+
return [
|
|
15370
|
+
repoRoot,
|
|
15371
|
+
sortSourcePullRequests(await queryPullRequests(repoRoot))
|
|
15372
|
+
];
|
|
15373
|
+
} catch {
|
|
15374
|
+
return [repoRoot, []];
|
|
15375
|
+
}
|
|
15376
|
+
}
|
|
15377
|
+
);
|
|
15378
|
+
return new Map(results);
|
|
15379
|
+
}
|
|
15380
|
+
async function mapWithConcurrency2(items, limit, mapper) {
|
|
15381
|
+
const results = new Array(items.length);
|
|
15382
|
+
let nextIndex = 0;
|
|
15383
|
+
async function readNext() {
|
|
15384
|
+
for (; ; ) {
|
|
15385
|
+
const index = nextIndex;
|
|
15386
|
+
nextIndex += 1;
|
|
15387
|
+
if (index >= items.length) return;
|
|
15388
|
+
results[index] = await mapper(items[index]);
|
|
15389
|
+
}
|
|
15390
|
+
}
|
|
15391
|
+
await Promise.all(
|
|
15392
|
+
Array.from({ length: Math.min(limit, items.length) }, () => readNext())
|
|
15393
|
+
);
|
|
15394
|
+
return results;
|
|
15395
|
+
}
|
|
15396
|
+
function sortSourcePullRequests(pullRequests) {
|
|
15397
|
+
return [...pullRequests ?? []].sort((a, b3) => a.number - b3.number);
|
|
15398
|
+
}
|
|
15399
|
+
function formatWorktreeBranchLabel(branch, pullRequestNumbers = []) {
|
|
15400
|
+
const branchLabel = branch ?? "(detached)";
|
|
15401
|
+
if (pullRequestNumbers.length === 0) return branchLabel;
|
|
15402
|
+
return `${branchLabel} (${pullRequestNumbers.map((number) => `#${number}`).join(", ")})`;
|
|
15403
|
+
}
|
|
14903
15404
|
function buildStatusBadges(info) {
|
|
14904
15405
|
const badges = [];
|
|
14905
15406
|
if (info.isCurrent) {
|
|
@@ -15156,8 +15657,8 @@ function createCleanCommand(dependencies = {}) {
|
|
|
15156
15657
|
}
|
|
15157
15658
|
return 0;
|
|
15158
15659
|
}
|
|
15159
|
-
const removedPaths = [];
|
|
15160
15660
|
const removedWorktrees = [];
|
|
15661
|
+
const failures = [];
|
|
15161
15662
|
for (const worktree of selectedWorktrees) {
|
|
15162
15663
|
if (options.stale && !await isStaleCleanupCandidate(
|
|
15163
15664
|
repository.repoRoot,
|
|
@@ -15173,10 +15674,15 @@ function createCleanCommand(dependencies = {}) {
|
|
|
15173
15674
|
try {
|
|
15174
15675
|
await removeWorktree(repository.repoRoot, worktree.path);
|
|
15175
15676
|
} catch (error) {
|
|
15176
|
-
if (!
|
|
15177
|
-
|
|
15677
|
+
if (!isWorktreeForceRemovalError(error)) {
|
|
15678
|
+
failures.push({
|
|
15679
|
+
branch: worktree.branch,
|
|
15680
|
+
message: toMessage(error),
|
|
15681
|
+
path: worktree.path
|
|
15682
|
+
});
|
|
15683
|
+
continue;
|
|
15178
15684
|
}
|
|
15179
|
-
if (options.stale) {
|
|
15685
|
+
if (options.stale && !isWorktreeDeletionError(error)) {
|
|
15180
15686
|
options.stderr(
|
|
15181
15687
|
`Skipped ${worktree.path}: no longer a safe stale cleanup candidate
|
|
15182
15688
|
`
|
|
@@ -15184,24 +15690,24 @@ function createCleanCommand(dependencies = {}) {
|
|
|
15184
15690
|
continue;
|
|
15185
15691
|
}
|
|
15186
15692
|
if (!options.force && !await confirmForceRemoveWorktree(worktree.path)) {
|
|
15187
|
-
|
|
15188
|
-
|
|
15189
|
-
|
|
15693
|
+
failures.push({
|
|
15694
|
+
branch: worktree.branch,
|
|
15695
|
+
message: "force removal declined",
|
|
15696
|
+
path: worktree.path
|
|
15697
|
+
});
|
|
15698
|
+
continue;
|
|
15190
15699
|
}
|
|
15191
15700
|
try {
|
|
15192
15701
|
await forceRemoveWorktree(repository.repoRoot, worktree.path);
|
|
15193
15702
|
} catch (forceError) {
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15200
|
-
);
|
|
15201
|
-
return 1;
|
|
15703
|
+
failures.push({
|
|
15704
|
+
branch: worktree.branch,
|
|
15705
|
+
message: toMessage(forceError),
|
|
15706
|
+
path: worktree.path
|
|
15707
|
+
});
|
|
15708
|
+
continue;
|
|
15202
15709
|
}
|
|
15203
15710
|
}
|
|
15204
|
-
removedPaths.push(worktree.path);
|
|
15205
15711
|
removedWorktrees.push(worktree);
|
|
15206
15712
|
if (worktree.branch) {
|
|
15207
15713
|
try {
|
|
@@ -15233,13 +15739,16 @@ function createCleanCommand(dependencies = {}) {
|
|
|
15233
15739
|
const info = selectedInfoByPath.get(worktree.path);
|
|
15234
15740
|
return info === void 0 ? { branch: worktree.branch, path: worktree.path } : serializeWorktreeInfo(info);
|
|
15235
15741
|
});
|
|
15236
|
-
|
|
15742
|
+
const payload = failures.length === 0 ? { removed } : { removed, failed: failures };
|
|
15743
|
+
options.stdout(`${JSON.stringify(payload, null, 2)}
|
|
15237
15744
|
`);
|
|
15745
|
+
} else if (failures.length > 0) {
|
|
15746
|
+
reportCleanFailures(failures, options.stderr);
|
|
15238
15747
|
} else {
|
|
15239
15748
|
options.stdout(`${repository.repoRoot}
|
|
15240
15749
|
`);
|
|
15241
15750
|
}
|
|
15242
|
-
return 0;
|
|
15751
|
+
return failures.length === 0 ? 0 : 1;
|
|
15243
15752
|
};
|
|
15244
15753
|
}
|
|
15245
15754
|
var runCleanCommand = createCleanCommand();
|
|
@@ -15316,9 +15825,13 @@ function resolveSelectedWorktrees(worktrees, selections) {
|
|
|
15316
15825
|
}
|
|
15317
15826
|
return selectedWorktrees;
|
|
15318
15827
|
}
|
|
15319
|
-
function
|
|
15320
|
-
|
|
15321
|
-
|
|
15828
|
+
function reportCleanFailures(failures, stderr) {
|
|
15829
|
+
const noun = failures.length === 1 ? "worktree" : "worktrees";
|
|
15830
|
+
stderr(`Failed to clean ${failures.length} ${noun}:
|
|
15831
|
+
`);
|
|
15832
|
+
for (const failure of failures) {
|
|
15833
|
+
const branch = failure.branch === null ? "detached" : failure.branch;
|
|
15834
|
+
stderr(`- ${failure.path} (${branch}): ${failure.message}
|
|
15322
15835
|
`);
|
|
15323
15836
|
}
|
|
15324
15837
|
}
|
|
@@ -15409,7 +15922,14 @@ var TOP_LEVEL_COMMANDS = [
|
|
|
15409
15922
|
name: "new",
|
|
15410
15923
|
description: "create a new branch or detached linked worktree"
|
|
15411
15924
|
},
|
|
15412
|
-
{
|
|
15925
|
+
{
|
|
15926
|
+
name: "init",
|
|
15927
|
+
description: "set up shell integration interactively or print a shell wrapper"
|
|
15928
|
+
},
|
|
15929
|
+
{
|
|
15930
|
+
name: "doctor",
|
|
15931
|
+
description: "check gji installation and configuration health"
|
|
15932
|
+
},
|
|
15413
15933
|
{ name: "completion", description: "print shell completion definitions" },
|
|
15414
15934
|
{ name: "pr", description: "fetch a pull request into a linked worktree" },
|
|
15415
15935
|
{ name: "back", description: "navigate to the previously visited worktree" },
|
|
@@ -15462,6 +15982,70 @@ function renderBashCompletion() {
|
|
|
15462
15982
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
15463
15983
|
}
|
|
15464
15984
|
|
|
15985
|
+
__gji_pr_targets_cache=""
|
|
15986
|
+
__gji_pr_targets_cache_at=0
|
|
15987
|
+
__gji_pr_targets_cache_loaded=0
|
|
15988
|
+
__gji_pr_targets_cache_repo=""
|
|
15989
|
+
|
|
15990
|
+
__gji_run_bounded() {
|
|
15991
|
+
local output_file pid status attempt
|
|
15992
|
+
output_file=$(mktemp "\${TMPDIR:-/tmp}/gji-completion.XXXXXX") || return 124
|
|
15993
|
+
"$@" >"$output_file" 2>/dev/null &
|
|
15994
|
+
pid=$!
|
|
15995
|
+
for ((attempt = 0; attempt < 20; attempt++)); do
|
|
15996
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
15997
|
+
wait "$pid"
|
|
15998
|
+
status=$?
|
|
15999
|
+
cat "$output_file"
|
|
16000
|
+
rm -f "$output_file"
|
|
16001
|
+
return "$status"
|
|
16002
|
+
fi
|
|
16003
|
+
sleep 0.1
|
|
16004
|
+
done
|
|
16005
|
+
kill "$pid" 2>/dev/null
|
|
16006
|
+
wait "$pid" 2>/dev/null
|
|
16007
|
+
rm -f "$output_file"
|
|
16008
|
+
return 124
|
|
16009
|
+
}
|
|
16010
|
+
|
|
16011
|
+
__gji_pr_targets() {
|
|
16012
|
+
local now repo_key branches targets
|
|
16013
|
+
now=$(date +%s)
|
|
16014
|
+
repo_key=$(command git rev-parse --show-toplevel 2>/dev/null)
|
|
16015
|
+
if [ "$__gji_pr_targets_cache_loaded" -eq 1 ] && [ "$repo_key" = "$__gji_pr_targets_cache_repo" ] && [ "$((now - __gji_pr_targets_cache_at))" -lt 30 ]; then
|
|
16016
|
+
printf '%s
|
|
16017
|
+
' "$__gji_pr_targets_cache"
|
|
16018
|
+
return
|
|
16019
|
+
fi
|
|
16020
|
+
|
|
16021
|
+
branches="$(__gji_worktree_branches)"
|
|
16022
|
+
targets=""
|
|
16023
|
+
if [ -n "$repo_key" ] && command -v gh >/dev/null 2>&1; then
|
|
16024
|
+
targets=$(__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
|
|
16025
|
+
tr '{},' '\\n' | sed -n 's/.*"number":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')
|
|
16026
|
+
elif [ -n "$repo_key" ] && command -v glab >/dev/null 2>&1; then
|
|
16027
|
+
targets=$(__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
|
|
16028
|
+
tr '{},' '\\n' | sed -n 's/.*"iid":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')
|
|
16029
|
+
elif [ -n "$repo_key" ] && command -v bb >/dev/null 2>&1; then
|
|
16030
|
+
targets=$(__gji_run_bounded bb pr list --state OPEN --format json |
|
|
16031
|
+
tr '{},' '\\n' | sed -n 's/.*"id":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')
|
|
16032
|
+
fi
|
|
16033
|
+
|
|
16034
|
+
__gji_pr_targets_cache="$branches"
|
|
16035
|
+
if [ -n "$targets" ]; then
|
|
16036
|
+
if [ -n "$__gji_pr_targets_cache" ]; then
|
|
16037
|
+
__gji_pr_targets_cache+=$'
|
|
16038
|
+
'
|
|
16039
|
+
fi
|
|
16040
|
+
__gji_pr_targets_cache+="$targets"
|
|
16041
|
+
fi
|
|
16042
|
+
__gji_pr_targets_cache_at="$now"
|
|
16043
|
+
__gji_pr_targets_cache_loaded=1
|
|
16044
|
+
__gji_pr_targets_cache_repo="$repo_key"
|
|
16045
|
+
printf '%s
|
|
16046
|
+
' "$__gji_pr_targets_cache"
|
|
16047
|
+
}
|
|
16048
|
+
|
|
15465
16049
|
_gji_completion() {
|
|
15466
16050
|
local cur command_name
|
|
15467
16051
|
COMPREPLY=()
|
|
@@ -15479,13 +16063,37 @@ _gji_completion() {
|
|
|
15479
16063
|
COMPREPLY=( $(compgen -W "--detached --force --open --editor --dry-run --json --help" -- "$cur") )
|
|
15480
16064
|
;;
|
|
15481
16065
|
init)
|
|
15482
|
-
COMPREPLY=( $(compgen -W "${shells} --write --help" -- "$cur") )
|
|
16066
|
+
COMPREPLY=( $(compgen -W "${shells} --write --json --help" -- "$cur") )
|
|
16067
|
+
;;
|
|
16068
|
+
doctor)
|
|
16069
|
+
COMPREPLY=( $(compgen -W "--fix --yes --json --help" -- "$cur") )
|
|
15483
16070
|
;;
|
|
15484
16071
|
completion)
|
|
15485
16072
|
COMPREPLY=( $(compgen -W "${shells} --help" -- "$cur") )
|
|
15486
16073
|
;;
|
|
15487
16074
|
pr)
|
|
15488
|
-
|
|
16075
|
+
if [ "$COMP_CWORD" -eq 2 ]; then
|
|
16076
|
+
COMPREPLY=( $(compgen -W "open --dry-run --json --help" -- "$cur") )
|
|
16077
|
+
elif [ "\${COMP_WORDS[2]}" = "open" ]; then
|
|
16078
|
+
local has_select=0 has_target=0 word
|
|
16079
|
+
for word in "\${COMP_WORDS[@]:3:$((COMP_CWORD - 3))}"; do
|
|
16080
|
+
if [ "$word" = "--select" ]; then has_select=1; fi
|
|
16081
|
+
if [[ "$word" != -* && "$word" != "" ]]; then has_target=1; fi
|
|
16082
|
+
done
|
|
16083
|
+
if [[ "$cur" == -* ]]; then
|
|
16084
|
+
if [[ "$has_select" -eq 1 || "$has_target" -eq 1 ]]; then
|
|
16085
|
+
COMPREPLY=( $(compgen -W "--help" -- "$cur") )
|
|
16086
|
+
else
|
|
16087
|
+
COMPREPLY=( $(compgen -W "--select --help" -- "$cur") )
|
|
16088
|
+
fi
|
|
16089
|
+
elif [[ "$has_select" -eq 1 || "$has_target" -eq 1 ]]; then
|
|
16090
|
+
COMPREPLY=()
|
|
16091
|
+
else
|
|
16092
|
+
COMPREPLY=( $(compgen -W "$(__gji_pr_targets) --select --help" -- "$cur") )
|
|
16093
|
+
fi
|
|
16094
|
+
else
|
|
16095
|
+
COMPREPLY=( $(compgen -W "--dry-run --json --help" -- "$cur") )
|
|
16096
|
+
fi
|
|
15489
16097
|
;;
|
|
15490
16098
|
back)
|
|
15491
16099
|
COMPREPLY=( $(compgen -W "--print --help" -- "$cur") )
|
|
@@ -15570,6 +16178,103 @@ function renderFishCompletion() {
|
|
|
15570
16178
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
15571
16179
|
end
|
|
15572
16180
|
|
|
16181
|
+
set -g __gji_pr_targets_cache
|
|
16182
|
+
set -g __gji_pr_targets_cache_at 0
|
|
16183
|
+
set -g __gji_pr_targets_cache_loaded 0
|
|
16184
|
+
set -g __gji_pr_targets_cache_repo
|
|
16185
|
+
|
|
16186
|
+
function __gji_run_bounded
|
|
16187
|
+
set -l output_file (mktemp "$TMPDIR/gji-completion.XXXXXX" 2>/dev/null)
|
|
16188
|
+
if test -z "$output_file"
|
|
16189
|
+
set output_file (mktemp "/tmp/gji-completion.XXXXXX" 2>/dev/null)
|
|
16190
|
+
end
|
|
16191
|
+
if test -z "$output_file"
|
|
16192
|
+
return 124
|
|
16193
|
+
end
|
|
16194
|
+
|
|
16195
|
+
command $argv >$output_file 2>/dev/null &
|
|
16196
|
+
set -l pid $last_pid
|
|
16197
|
+
for attempt in (seq 1 20)
|
|
16198
|
+
if not kill -0 $pid 2>/dev/null
|
|
16199
|
+
wait $pid
|
|
16200
|
+
set -l exit_status $status
|
|
16201
|
+
command cat $output_file
|
|
16202
|
+
command rm -f $output_file
|
|
16203
|
+
return $exit_status
|
|
16204
|
+
end
|
|
16205
|
+
sleep 0.1
|
|
16206
|
+
end
|
|
16207
|
+
kill $pid 2>/dev/null
|
|
16208
|
+
wait $pid 2>/dev/null
|
|
16209
|
+
command rm -f $output_file
|
|
16210
|
+
return 124
|
|
16211
|
+
end
|
|
16212
|
+
|
|
16213
|
+
function __gji_pr_targets
|
|
16214
|
+
set -l now (date +%s)
|
|
16215
|
+
set -l repo_key (command git rev-parse --show-toplevel 2>/dev/null)
|
|
16216
|
+
if test $__gji_pr_targets_cache_loaded -eq 1; and test "$repo_key" = "$__gji_pr_targets_cache_repo"; and test (math $now - $__gji_pr_targets_cache_at) -lt 30
|
|
16217
|
+
printf '%s
|
|
16218
|
+
' $__gji_pr_targets_cache
|
|
16219
|
+
return
|
|
16220
|
+
end
|
|
16221
|
+
|
|
16222
|
+
set -l branches (__gji_worktree_branches)
|
|
16223
|
+
set -l targets
|
|
16224
|
+
if test -n "$repo_key"; and command -q gh
|
|
16225
|
+
set targets (__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
|
|
16226
|
+
string replace -a -r '[{},]' '\\n' |
|
|
16227
|
+
string match -r '"number"[[:space:]]*:[[:space:]]*[0-9]+' | string replace -r '.*:[[:space:]]*' '')
|
|
16228
|
+
else if test -n "$repo_key"; and command -q glab
|
|
16229
|
+
set targets (__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
|
|
16230
|
+
string replace -a -r '[{},]' '\\n' |
|
|
16231
|
+
string match -r '"iid"[[:space:]]*:[[:space:]]*[0-9]+' | string replace -r '.*:[[:space:]]*' '')
|
|
16232
|
+
else if test -n "$repo_key"; and command -q bb
|
|
16233
|
+
set targets (__gji_run_bounded bb pr list --state OPEN --format json |
|
|
16234
|
+
string replace -a -r '[{},]' '\\n' |
|
|
16235
|
+
string match -r '"id"[[:space:]]*:[[:space:]]*[0-9]+' | string replace -r '.*:[[:space:]]*' '')
|
|
16236
|
+
end
|
|
16237
|
+
|
|
16238
|
+
set -g __gji_pr_targets_cache $branches $targets
|
|
16239
|
+
set -g __gji_pr_targets_cache_at $now
|
|
16240
|
+
set -g __gji_pr_targets_cache_loaded 1
|
|
16241
|
+
set -g __gji_pr_targets_cache_repo $repo_key
|
|
16242
|
+
printf '%s
|
|
16243
|
+
' $__gji_pr_targets_cache
|
|
16244
|
+
end
|
|
16245
|
+
|
|
16246
|
+
function __gji_should_complete_pr_select
|
|
16247
|
+
set -l tokens (commandline -opc)
|
|
16248
|
+
if test (count $tokens) -lt 3; or test $tokens[2] != pr; or test $tokens[3] != open
|
|
16249
|
+
return 1
|
|
16250
|
+
end
|
|
16251
|
+
for token in $tokens[4..-1]
|
|
16252
|
+
if test $token = --select; or not string match -q -- '-*' $token
|
|
16253
|
+
return 1
|
|
16254
|
+
end
|
|
16255
|
+
end
|
|
16256
|
+
return 0
|
|
16257
|
+
end
|
|
16258
|
+
|
|
16259
|
+
function __gji_should_complete_pr_target
|
|
16260
|
+
set -l tokens (commandline -opc)
|
|
16261
|
+
if test (count $tokens) -lt 3; or test $tokens[2] != pr; or test $tokens[3] != open
|
|
16262
|
+
return 1
|
|
16263
|
+
end
|
|
16264
|
+
if string match -q -- '-*' (commandline -ct)
|
|
16265
|
+
return 1
|
|
16266
|
+
end
|
|
16267
|
+
for token in $tokens[4..-1]
|
|
16268
|
+
if test $token = --select
|
|
16269
|
+
return 1
|
|
16270
|
+
end
|
|
16271
|
+
if not string match -q -- '-*' $token
|
|
16272
|
+
return 1
|
|
16273
|
+
end
|
|
16274
|
+
end
|
|
16275
|
+
return 0
|
|
16276
|
+
end
|
|
16277
|
+
|
|
15573
16278
|
function __gji_should_complete_config_action
|
|
15574
16279
|
set -l tokens (commandline -opc)
|
|
15575
16280
|
test (count $tokens) -eq 2
|
|
@@ -15599,14 +16304,22 @@ complete -c gji -n '__fish_seen_subcommand_from new' -l dry-run -d 'show what wo
|
|
|
15599
16304
|
complete -c gji -n '__fish_seen_subcommand_from new' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
15600
16305
|
|
|
15601
16306
|
complete -c gji -n '__fish_seen_subcommand_from init' -l write -d 'write the integration to the shell config file'
|
|
16307
|
+
complete -c gji -n '__fish_seen_subcommand_from init' -l json -d 'emit a JSON error in non-interactive mode'
|
|
15602
16308
|
${shellLines}
|
|
15603
16309
|
|
|
16310
|
+
complete -c gji -n '__fish_seen_subcommand_from doctor' -l fix -d 'apply safe automatic fixes after showing the plan'
|
|
16311
|
+
complete -c gji -n '__fish_seen_subcommand_from doctor' -l yes -d 'apply --fix without prompting'
|
|
16312
|
+
complete -c gji -n '__fish_seen_subcommand_from doctor' -l json -d 'emit diagnostic checks as JSON'
|
|
16313
|
+
|
|
15604
16314
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'bash' -d 'shell'
|
|
15605
16315
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'fish' -d 'shell'
|
|
15606
16316
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'zsh' -d 'shell'
|
|
15607
16317
|
|
|
15608
|
-
complete -c gji -n '__fish_seen_subcommand_from pr' -l dry-run -d 'show what would be created without executing any git commands or writing files'
|
|
15609
|
-
complete -c gji -n '__fish_seen_subcommand_from pr' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
16318
|
+
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'
|
|
16319
|
+
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'
|
|
16320
|
+
complete -c gji -n '__fish_seen_subcommand_from pr' -a 'open' -d 'open a pull request in the default browser'
|
|
16321
|
+
complete -c gji -n '__gji_should_complete_pr_select' -l select -d 'choose a pull request from any linked worktree'
|
|
16322
|
+
complete -c gji -n '__gji_should_complete_pr_target' -a '(__gji_pr_targets)' -d 'branch or PR number'
|
|
15610
16323
|
|
|
15611
16324
|
complete -c gji -n '__fish_seen_subcommand_from back' -l print -d 'print the resolved worktree path explicitly'
|
|
15612
16325
|
|
|
@@ -15668,15 +16381,71 @@ __gji_worktree_branches() {
|
|
|
15668
16381
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
15669
16382
|
}
|
|
15670
16383
|
|
|
16384
|
+
typeset -g __gji_pr_targets_cache=()
|
|
16385
|
+
typeset -g __gji_pr_targets_cache_at=0
|
|
16386
|
+
typeset -g __gji_pr_targets_cache_loaded=0
|
|
16387
|
+
typeset -g __gji_pr_targets_cache_repo=""
|
|
16388
|
+
|
|
16389
|
+
__gji_run_bounded() {
|
|
16390
|
+
local output_file pid status attempt
|
|
16391
|
+
output_file=$(mktemp "\${TMPDIR:-/tmp}/gji-completion.XXXXXX") || return 124
|
|
16392
|
+
"$@" >"$output_file" 2>/dev/null &
|
|
16393
|
+
pid=$!
|
|
16394
|
+
for ((attempt = 0; attempt < 20; attempt++)); do
|
|
16395
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
16396
|
+
wait "$pid"
|
|
16397
|
+
status=$?
|
|
16398
|
+
cat "$output_file"
|
|
16399
|
+
rm -f "$output_file"
|
|
16400
|
+
return "$status"
|
|
16401
|
+
fi
|
|
16402
|
+
sleep 0.1
|
|
16403
|
+
done
|
|
16404
|
+
kill "$pid" 2>/dev/null
|
|
16405
|
+
wait "$pid" 2>/dev/null
|
|
16406
|
+
rm -f "$output_file"
|
|
16407
|
+
return 124
|
|
16408
|
+
}
|
|
16409
|
+
|
|
16410
|
+
__gji_pr_targets() {
|
|
16411
|
+
local now repo_key
|
|
16412
|
+
local -a branches targets
|
|
16413
|
+
now=$(date +%s)
|
|
16414
|
+
repo_key=$(command git rev-parse --show-toplevel 2>/dev/null)
|
|
16415
|
+
if (( __gji_pr_targets_cache_loaded == 1 && "\${repo_key}" == "\${__gji_pr_targets_cache_repo}" && now - __gji_pr_targets_cache_at < 30 )); then
|
|
16416
|
+
print -rl -- "\${__gji_pr_targets_cache[@]}"
|
|
16417
|
+
return
|
|
16418
|
+
fi
|
|
16419
|
+
|
|
16420
|
+
branches=("\${(@f)$(__gji_worktree_branches)}")
|
|
16421
|
+
targets=()
|
|
16422
|
+
if [[ -n "\${repo_key}" && $+commands[gh] -eq 1 ]]; then
|
|
16423
|
+
targets=("\${(@f)$(__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
|
|
16424
|
+
tr '{},' '\\n' | sed -n 's/.*"number":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
|
|
16425
|
+
elif [[ -n "\${repo_key}" && $+commands[glab] -eq 1 ]]; then
|
|
16426
|
+
targets=("\${(@f)$(__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
|
|
16427
|
+
tr '{},' '\\n' | sed -n 's/.*"iid":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
|
|
16428
|
+
elif [[ -n "\${repo_key}" && $+commands[bb] -eq 1 ]]; then
|
|
16429
|
+
targets=("\${(@f)$(__gji_run_bounded bb pr list --state OPEN --format json |
|
|
16430
|
+
tr '{},' '\\n' | sed -n 's/.*"id":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
|
|
16431
|
+
fi
|
|
16432
|
+
|
|
16433
|
+
__gji_pr_targets_cache=("\${branches[@]}" "\${targets[@]}")
|
|
16434
|
+
__gji_pr_targets_cache_at="$now"
|
|
16435
|
+
__gji_pr_targets_cache_loaded=1
|
|
16436
|
+
__gji_pr_targets_cache_repo="$repo_key"
|
|
16437
|
+
print -rl -- "\${__gji_pr_targets_cache[@]}"
|
|
16438
|
+
}
|
|
16439
|
+
|
|
15671
16440
|
local context state line
|
|
15672
|
-
local -a
|
|
16441
|
+
local -a command_entries worktree_branches
|
|
15673
16442
|
|
|
15674
|
-
|
|
16443
|
+
command_entries=(
|
|
15675
16444
|
${commandLines}
|
|
15676
16445
|
)
|
|
15677
16446
|
|
|
15678
16447
|
if (( CURRENT == 2 )); then
|
|
15679
|
-
_describe 'command'
|
|
16448
|
+
_describe 'command' command_entries
|
|
15680
16449
|
return
|
|
15681
16450
|
fi
|
|
15682
16451
|
|
|
@@ -15685,13 +16454,26 @@ case "\${words[2]}" in
|
|
|
15685
16454
|
_arguments '--detached[create a detached worktree without a branch]' '--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: '
|
|
15686
16455
|
;;
|
|
15687
16456
|
init)
|
|
15688
|
-
_arguments '--write[write the integration to the shell config file]' '2:shell:(${shells})'
|
|
16457
|
+
_arguments '--write[write the integration to the shell config file]' '--json[emit a JSON error in non-interactive mode]' '2:shell:(${shells})'
|
|
16458
|
+
;;
|
|
16459
|
+
doctor)
|
|
16460
|
+
_arguments '--fix[apply safe automatic fixes after showing the plan]' '--yes[apply --fix without prompting]' '--json[emit diagnostic checks as JSON]'
|
|
15689
16461
|
;;
|
|
15690
16462
|
completion)
|
|
15691
16463
|
_arguments '2:shell:(${shells})'
|
|
15692
16464
|
;;
|
|
15693
16465
|
pr)
|
|
15694
|
-
|
|
16466
|
+
if [[ "\${words[3]}" == "open" ]]; then
|
|
16467
|
+
if (( \${words[(I)--select]} )); then
|
|
16468
|
+
_arguments
|
|
16469
|
+
elif [[ -n "\${words[4]}" && "\${words[4]}" != -* ]]; then
|
|
16470
|
+
_arguments
|
|
16471
|
+
else
|
|
16472
|
+
_arguments '--select[choose a pull request from any linked worktree]' '4:branch or PR number:->pr_targets'
|
|
16473
|
+
fi
|
|
16474
|
+
else
|
|
16475
|
+
_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)'
|
|
16476
|
+
fi
|
|
15695
16477
|
;;
|
|
15696
16478
|
back)
|
|
15697
16479
|
_arguments '--print[print the resolved worktree path explicitly]' '2:steps: '
|
|
@@ -15754,6 +16536,10 @@ case "$state" in
|
|
|
15754
16536
|
worktree_branches=(\${(@f)$(__gji_worktree_branches)})
|
|
15755
16537
|
_describe 'worktree branch' worktree_branches
|
|
15756
16538
|
;;
|
|
16539
|
+
pr_targets)
|
|
16540
|
+
worktree_branches=(\${(@f)$(__gji_pr_targets)})
|
|
16541
|
+
_describe 'branch or PR number' worktree_branches
|
|
16542
|
+
;;
|
|
15757
16543
|
config_keys)
|
|
15758
16544
|
_values 'config key' ${configKeys}
|
|
15759
16545
|
;;
|
|
@@ -15817,51 +16603,13 @@ function writeJson(stdout2, value) {
|
|
|
15817
16603
|
`);
|
|
15818
16604
|
}
|
|
15819
16605
|
|
|
15820
|
-
// src/
|
|
15821
|
-
import {
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
import {
|
|
15825
|
-
import { basename as
|
|
15826
|
-
|
|
15827
|
-
// src/new.ts
|
|
15828
|
-
import { execFile as execFile3 } from "node:child_process";
|
|
15829
|
-
import { mkdir as mkdir4 } from "node:fs/promises";
|
|
15830
|
-
import { basename as basename3, dirname as dirname5 } from "node:path";
|
|
15831
|
-
import { promisify as promisify4 } from "node:util";
|
|
15832
|
-
|
|
15833
|
-
// src/conflict.ts
|
|
15834
|
-
import { constants } from "node:fs";
|
|
15835
|
-
import { access as access2 } from "node:fs/promises";
|
|
15836
|
-
async function pathExists(path9) {
|
|
15837
|
-
try {
|
|
15838
|
-
await access2(path9, constants.F_OK);
|
|
15839
|
-
return true;
|
|
15840
|
-
} catch {
|
|
15841
|
-
return false;
|
|
15842
|
-
}
|
|
15843
|
-
}
|
|
15844
|
-
async function promptForPathConflict(path9) {
|
|
15845
|
-
const choice = await ve({
|
|
15846
|
-
message: `Target path already exists: ${path9}`,
|
|
15847
|
-
options: [
|
|
15848
|
-
{
|
|
15849
|
-
value: "abort",
|
|
15850
|
-
label: "Abort",
|
|
15851
|
-
hint: "Keep the existing directory untouched"
|
|
15852
|
-
},
|
|
15853
|
-
{
|
|
15854
|
-
value: "reuse",
|
|
15855
|
-
label: "Reuse path",
|
|
15856
|
-
hint: "Print the existing path and stop"
|
|
15857
|
-
}
|
|
15858
|
-
]
|
|
15859
|
-
});
|
|
15860
|
-
if (pD(choice)) {
|
|
15861
|
-
return "abort";
|
|
15862
|
-
}
|
|
15863
|
-
return choice;
|
|
15864
|
-
}
|
|
16606
|
+
// src/doctor.ts
|
|
16607
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
16608
|
+
import { constants as constants3 } from "node:fs";
|
|
16609
|
+
import { access as access4, readFile as readFile4 } from "node:fs/promises";
|
|
16610
|
+
import { homedir as homedir5 } from "node:os";
|
|
16611
|
+
import { basename as basename4, dirname as dirname5, join as join6 } from "node:path";
|
|
16612
|
+
import { promisify as promisify5 } from "node:util";
|
|
15865
16613
|
|
|
15866
16614
|
// src/editor.ts
|
|
15867
16615
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -15901,14 +16649,774 @@ async function defaultSpawnEditor(cli, args) {
|
|
|
15901
16649
|
child.unref();
|
|
15902
16650
|
}
|
|
15903
16651
|
|
|
16652
|
+
// src/repo-registry.ts
|
|
16653
|
+
import { randomUUID } from "node:crypto";
|
|
16654
|
+
import { constants } from "node:fs";
|
|
16655
|
+
import {
|
|
16656
|
+
access as access2,
|
|
16657
|
+
mkdir as mkdir3,
|
|
16658
|
+
readFile as readFile3,
|
|
16659
|
+
realpath,
|
|
16660
|
+
rename,
|
|
16661
|
+
rm as rm2,
|
|
16662
|
+
writeFile as writeFile4
|
|
16663
|
+
} from "node:fs/promises";
|
|
16664
|
+
import { homedir as homedir4 } from "node:os";
|
|
16665
|
+
import { basename as basename3, dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
|
|
16666
|
+
var REGISTRY_FILE_NAME = "repos.json";
|
|
16667
|
+
var MAX_REGISTRY_ENTRIES = 100;
|
|
16668
|
+
var registryLocks = /* @__PURE__ */ new Map();
|
|
16669
|
+
function REGISTRY_FILE_PATH(home = homedir4()) {
|
|
16670
|
+
const configDir = process.env.GJI_CONFIG_DIR;
|
|
16671
|
+
if (configDir) {
|
|
16672
|
+
return join4(resolve4(configDir), REGISTRY_FILE_NAME);
|
|
16673
|
+
}
|
|
16674
|
+
return join4(home, GLOBAL_CONFIG_DIRECTORY, REGISTRY_FILE_NAME);
|
|
16675
|
+
}
|
|
16676
|
+
async function loadRegistry(home = homedir4()) {
|
|
16677
|
+
const path9 = REGISTRY_FILE_PATH(home);
|
|
16678
|
+
try {
|
|
16679
|
+
const raw = await readFile3(path9, "utf8");
|
|
16680
|
+
const parsed = JSON.parse(raw);
|
|
16681
|
+
if (!Array.isArray(parsed)) return [];
|
|
16682
|
+
return parsed.filter(isRegistryEntry);
|
|
16683
|
+
} catch {
|
|
16684
|
+
return [];
|
|
16685
|
+
}
|
|
16686
|
+
}
|
|
16687
|
+
async function canonicalizeRepoPath(repoPath) {
|
|
16688
|
+
try {
|
|
16689
|
+
return await realpath(repoPath);
|
|
16690
|
+
} catch {
|
|
16691
|
+
return resolve4(repoPath);
|
|
16692
|
+
}
|
|
16693
|
+
}
|
|
16694
|
+
async function registerRepo(repoPath, home = homedir4()) {
|
|
16695
|
+
const registryPath = REGISTRY_FILE_PATH(home);
|
|
16696
|
+
await withRegistryLock(registryPath, async () => {
|
|
16697
|
+
const existing = await normalizeRegistryForWrite(await loadRegistry(home));
|
|
16698
|
+
const canonicalRepoPath = await canonicalizeRepoPath(repoPath);
|
|
16699
|
+
if (existing.length > 0 && existing[0].path === canonicalRepoPath) return;
|
|
16700
|
+
const entry = {
|
|
16701
|
+
lastUsed: Date.now(),
|
|
16702
|
+
name: basename3(canonicalRepoPath),
|
|
16703
|
+
path: canonicalRepoPath
|
|
16704
|
+
};
|
|
16705
|
+
const filtered = existing.filter((e2) => e2.path !== canonicalRepoPath);
|
|
16706
|
+
const next = [entry, ...filtered].slice(0, MAX_REGISTRY_ENTRIES);
|
|
16707
|
+
await writeRegistry(next, registryPath);
|
|
16708
|
+
});
|
|
16709
|
+
}
|
|
16710
|
+
async function removeMissingRegistryEntries(paths, home = homedir4()) {
|
|
16711
|
+
if (paths.size === 0) return { removedPaths: [], skippedPaths: [] };
|
|
16712
|
+
const registryPath = REGISTRY_FILE_PATH(home);
|
|
16713
|
+
return withRegistryLock(registryPath, async () => {
|
|
16714
|
+
const entries = await loadRegistry(home);
|
|
16715
|
+
const candidates = entries.filter((entry) => paths.has(entry.path));
|
|
16716
|
+
const status = await Promise.all(
|
|
16717
|
+
candidates.map(async (entry) => ({
|
|
16718
|
+
entry,
|
|
16719
|
+
missing: await isMissingPath(entry.path)
|
|
16720
|
+
}))
|
|
16721
|
+
);
|
|
16722
|
+
const removedPaths = status.filter(({ missing }) => missing).map(({ entry }) => entry.path);
|
|
16723
|
+
const skippedPaths = status.filter(({ missing }) => !missing).map(({ entry }) => entry.path);
|
|
16724
|
+
if (removedPaths.length > 0) {
|
|
16725
|
+
await writeRegistry(
|
|
16726
|
+
entries.filter((entry) => !removedPaths.includes(entry.path)),
|
|
16727
|
+
registryPath
|
|
16728
|
+
);
|
|
16729
|
+
}
|
|
16730
|
+
return { removedPaths, skippedPaths };
|
|
16731
|
+
});
|
|
16732
|
+
}
|
|
16733
|
+
async function withRegistryLock(registryPath, operation) {
|
|
16734
|
+
const previous = registryLocks.get(registryPath);
|
|
16735
|
+
let release;
|
|
16736
|
+
const current = new Promise((resolve6) => {
|
|
16737
|
+
release = resolve6;
|
|
16738
|
+
});
|
|
16739
|
+
registryLocks.set(registryPath, current);
|
|
16740
|
+
if (previous) await previous;
|
|
16741
|
+
try {
|
|
16742
|
+
return await operation();
|
|
16743
|
+
} finally {
|
|
16744
|
+
release();
|
|
16745
|
+
if (registryLocks.get(registryPath) === current) {
|
|
16746
|
+
registryLocks.delete(registryPath);
|
|
16747
|
+
}
|
|
16748
|
+
}
|
|
16749
|
+
}
|
|
16750
|
+
async function isMissingPath(path9) {
|
|
16751
|
+
try {
|
|
16752
|
+
await access2(path9, constants.F_OK);
|
|
16753
|
+
return false;
|
|
16754
|
+
} catch (error) {
|
|
16755
|
+
return isMissingPathError(error);
|
|
16756
|
+
}
|
|
16757
|
+
}
|
|
16758
|
+
function isMissingPathError(error) {
|
|
16759
|
+
if (!(error instanceof Error) || !("code" in error)) return false;
|
|
16760
|
+
const code = error.code;
|
|
16761
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
16762
|
+
}
|
|
16763
|
+
async function writeRegistry(entries, registryPath) {
|
|
16764
|
+
await mkdir3(dirname4(registryPath), { recursive: true });
|
|
16765
|
+
const temporaryPath = `${registryPath}.tmp-${process.pid}-${randomUUID()}`;
|
|
16766
|
+
try {
|
|
16767
|
+
await writeFile4(
|
|
16768
|
+
temporaryPath,
|
|
16769
|
+
`${JSON.stringify(entries, null, 2)}
|
|
16770
|
+
`,
|
|
16771
|
+
"utf8"
|
|
16772
|
+
);
|
|
16773
|
+
await rename(temporaryPath, registryPath);
|
|
16774
|
+
} catch (error) {
|
|
16775
|
+
await rm2(temporaryPath, { force: true }).catch(() => void 0);
|
|
16776
|
+
throw error;
|
|
16777
|
+
}
|
|
16778
|
+
}
|
|
16779
|
+
async function normalizeRegistryForWrite(entries) {
|
|
16780
|
+
const normalized = [];
|
|
16781
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
16782
|
+
for (const entry of entries) {
|
|
16783
|
+
const canonicalPath = await canonicalizeRepoPath(entry.path);
|
|
16784
|
+
if (seenPaths.has(canonicalPath)) {
|
|
16785
|
+
continue;
|
|
16786
|
+
}
|
|
16787
|
+
seenPaths.add(canonicalPath);
|
|
16788
|
+
normalized.push({
|
|
16789
|
+
...entry,
|
|
16790
|
+
name: basename3(canonicalPath),
|
|
16791
|
+
path: canonicalPath
|
|
16792
|
+
});
|
|
16793
|
+
}
|
|
16794
|
+
return normalized;
|
|
16795
|
+
}
|
|
16796
|
+
function isRegistryEntry(value) {
|
|
16797
|
+
return typeof value === "object" && value !== null && "path" in value && typeof value.path === "string" && "name" in value && typeof value.name === "string" && "lastUsed" in value && typeof value.lastUsed === "number";
|
|
16798
|
+
}
|
|
16799
|
+
|
|
16800
|
+
// src/shell-setup.ts
|
|
16801
|
+
import { constants as constants2 } from "node:fs";
|
|
16802
|
+
import { access as access3 } from "node:fs/promises";
|
|
16803
|
+
import { delimiter, join as join5 } from "node:path";
|
|
16804
|
+
var SHELL_INTEGRATION_START_MARKER = "# >>> gji init >>>";
|
|
16805
|
+
var SHELL_INTEGRATION_END_MARKER = "# <<< gji init <<<";
|
|
16806
|
+
var ONBOARDING_SHELL_INTEGRATION_START_MARKER = "# >>> gji shell integration >>>";
|
|
16807
|
+
var ONBOARDING_SHELL_INTEGRATION_END_MARKER = "# <<< gji shell integration <<<";
|
|
16808
|
+
async function executableExists(command) {
|
|
16809
|
+
const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join5(directory, command));
|
|
16810
|
+
for (const path9 of candidates) {
|
|
16811
|
+
try {
|
|
16812
|
+
await access3(path9, constants2.X_OK);
|
|
16813
|
+
return true;
|
|
16814
|
+
} catch {
|
|
16815
|
+
}
|
|
16816
|
+
}
|
|
16817
|
+
return false;
|
|
16818
|
+
}
|
|
16819
|
+
function resolveShellConfigPath(shell, home) {
|
|
16820
|
+
switch (shell) {
|
|
16821
|
+
case "bash":
|
|
16822
|
+
return join5(home, ".bashrc");
|
|
16823
|
+
case "fish":
|
|
16824
|
+
return join5(home, ".config", "fish", "config.fish");
|
|
16825
|
+
case "zsh":
|
|
16826
|
+
return join5(home, ".zshrc");
|
|
16827
|
+
}
|
|
16828
|
+
}
|
|
16829
|
+
function resolveCompletionPath(shell, home) {
|
|
16830
|
+
switch (shell) {
|
|
16831
|
+
case "bash":
|
|
16832
|
+
return join5(
|
|
16833
|
+
home,
|
|
16834
|
+
".local",
|
|
16835
|
+
"share",
|
|
16836
|
+
"bash-completion",
|
|
16837
|
+
"completions",
|
|
16838
|
+
"gji"
|
|
16839
|
+
);
|
|
16840
|
+
case "fish":
|
|
16841
|
+
return join5(home, ".config", "fish", "completions", "gji.fish");
|
|
16842
|
+
case "zsh":
|
|
16843
|
+
return join5(home, ".zsh", "completions", "_gji");
|
|
16844
|
+
}
|
|
16845
|
+
}
|
|
16846
|
+
function hasShellIntegration(config, shell) {
|
|
16847
|
+
if (config.includes(SHELL_INTEGRATION_START_MARKER) && config.includes(SHELL_INTEGRATION_END_MARKER) || config.includes(ONBOARDING_SHELL_INTEGRATION_START_MARKER) && config.includes(ONBOARDING_SHELL_INTEGRATION_END_MARKER)) {
|
|
16848
|
+
return true;
|
|
16849
|
+
}
|
|
16850
|
+
if (shell === "fish") return false;
|
|
16851
|
+
return new RegExp(
|
|
16852
|
+
`^[\\t ]*eval\\s+["']?\\$\\(\\s*gji\\s+init\\s+${shell}\\s*\\)["']?\\s*(?:#.*)?$`,
|
|
16853
|
+
"m"
|
|
16854
|
+
).test(config);
|
|
16855
|
+
}
|
|
16856
|
+
|
|
16857
|
+
// src/doctor.ts
|
|
16858
|
+
var execFileAsync4 = promisify5(execFile4);
|
|
16859
|
+
var MINIMUM_GIT_VERSION = { major: 2, minor: 17 };
|
|
16860
|
+
async function runDoctorCommand(options) {
|
|
16861
|
+
if (options.yes && !options.fix) {
|
|
16862
|
+
const message = "--yes requires --fix";
|
|
16863
|
+
if (options.json) {
|
|
16864
|
+
(options.stderr ?? options.stdout)(
|
|
16865
|
+
`${JSON.stringify({ error: message })}
|
|
16866
|
+
`
|
|
16867
|
+
);
|
|
16868
|
+
} else {
|
|
16869
|
+
(options.stderr ?? options.stdout)(`gji doctor: ${message}
|
|
16870
|
+
`);
|
|
16871
|
+
}
|
|
16872
|
+
return 1;
|
|
16873
|
+
}
|
|
16874
|
+
const home = options.home ?? homedir5();
|
|
16875
|
+
const shell = resolveSupportedShell(
|
|
16876
|
+
void 0,
|
|
16877
|
+
options.shell ?? process.env.SHELL
|
|
16878
|
+
);
|
|
16879
|
+
let inspection = await collectDoctorInspection(options.cwd, home, shell);
|
|
16880
|
+
let fixes = [];
|
|
16881
|
+
if (options.fix) {
|
|
16882
|
+
fixes = buildDoctorFixes(inspection.missingRegistryPaths);
|
|
16883
|
+
if (fixes.length > 0) {
|
|
16884
|
+
const approval = await requestFixApproval(fixes, options);
|
|
16885
|
+
if (approval === "apply") {
|
|
16886
|
+
fixes = await applyDoctorFixes(fixes, home);
|
|
16887
|
+
} else {
|
|
16888
|
+
fixes = fixes.map((fix) => ({
|
|
16889
|
+
...fix,
|
|
16890
|
+
status: approval,
|
|
16891
|
+
hint: approval === "pending" ? "re-run with --yes to apply this fix without a prompt" : "run gji doctor --fix again to review this fix"
|
|
16892
|
+
}));
|
|
16893
|
+
}
|
|
16894
|
+
}
|
|
16895
|
+
inspection = await collectDoctorInspection(options.cwd, home, shell);
|
|
16896
|
+
}
|
|
16897
|
+
const problems = inspection.checks.filter(
|
|
16898
|
+
(check) => check.status === "fail"
|
|
16899
|
+
).length;
|
|
16900
|
+
if (options.json) {
|
|
16901
|
+
const output = options.fix ? { checks: inspection.checks, problems, fixes } : { checks: inspection.checks, problems };
|
|
16902
|
+
options.stdout(`${JSON.stringify(output)}
|
|
16903
|
+
`);
|
|
16904
|
+
} else {
|
|
16905
|
+
options.stdout(renderDoctorChecks(inspection.checks, problems));
|
|
16906
|
+
if (options.fix) {
|
|
16907
|
+
options.stdout(renderDoctorFixes(fixes));
|
|
16908
|
+
}
|
|
16909
|
+
}
|
|
16910
|
+
return problems > 0 ? 1 : 0;
|
|
16911
|
+
}
|
|
16912
|
+
async function collectDoctorInspection(cwd, home, shell) {
|
|
16913
|
+
const repository = await detectRepositoryOrSkip(cwd);
|
|
16914
|
+
const globalConfig = await inspectConfig(
|
|
16915
|
+
GLOBAL_CONFIG_FILE_PATH(home),
|
|
16916
|
+
"global",
|
|
16917
|
+
KNOWN_GLOBAL_CONFIG_KEYS
|
|
16918
|
+
);
|
|
16919
|
+
const localConfig = repository ? await inspectConfig(
|
|
16920
|
+
join6(repository.repoRoot, CONFIG_FILE_NAME),
|
|
16921
|
+
"local",
|
|
16922
|
+
KNOWN_CONFIG_KEYS
|
|
16923
|
+
) : null;
|
|
16924
|
+
const effectiveConfig = resolveEffectiveConfig(
|
|
16925
|
+
globalConfig.config,
|
|
16926
|
+
localConfig?.config ?? {},
|
|
16927
|
+
repository?.repoRoot,
|
|
16928
|
+
home
|
|
16929
|
+
);
|
|
16930
|
+
const registry = await inspectRegistry(home);
|
|
16931
|
+
return {
|
|
16932
|
+
checks: [
|
|
16933
|
+
await checkGitVersion(),
|
|
16934
|
+
await checkShellIntegration(shell, home),
|
|
16935
|
+
await checkCompletion(shell, home),
|
|
16936
|
+
globalConfig.check,
|
|
16937
|
+
localConfig?.check ?? skippedCheck(
|
|
16938
|
+
"local-config",
|
|
16939
|
+
"local config not checked outside a Git repository"
|
|
16940
|
+
),
|
|
16941
|
+
await checkWorktreeBase(repository, effectiveConfig, home),
|
|
16942
|
+
registry.check,
|
|
16943
|
+
await checkEditor(effectiveConfig)
|
|
16944
|
+
],
|
|
16945
|
+
missingRegistryPaths: registry.missingPaths
|
|
16946
|
+
};
|
|
16947
|
+
}
|
|
16948
|
+
function buildDoctorFixes(missingRegistryPaths) {
|
|
16949
|
+
if (missingRegistryPaths.length === 0) return [];
|
|
16950
|
+
const count = missingRegistryPaths.length;
|
|
16951
|
+
return [
|
|
16952
|
+
{
|
|
16953
|
+
id: "repo-registry",
|
|
16954
|
+
message: `remove ${count} stale ${count === 1 ? "repository entry" : "repository entries"} from the registry`,
|
|
16955
|
+
paths: missingRegistryPaths,
|
|
16956
|
+
status: "pending"
|
|
16957
|
+
}
|
|
16958
|
+
];
|
|
16959
|
+
}
|
|
16960
|
+
async function requestFixApproval(fixes, options) {
|
|
16961
|
+
if (options.yes) return "apply";
|
|
16962
|
+
if (!isDoctorInteractive(options)) return "pending";
|
|
16963
|
+
const confirmed = options.confirmFixes ? await options.confirmFixes(fixes) : await ye({
|
|
16964
|
+
initialValue: true,
|
|
16965
|
+
message: `Apply ${fixes.length} automatic ${fixes.length === 1 ? "fix" : "fixes"} (${fixes.flatMap((fix) => fix.paths ?? []).join(", ")})?`
|
|
16966
|
+
});
|
|
16967
|
+
if (pD(confirmed) || !confirmed) return "declined";
|
|
16968
|
+
return "apply";
|
|
16969
|
+
}
|
|
16970
|
+
function isDoctorInteractive(options) {
|
|
16971
|
+
if (options.json || isHeadless()) return false;
|
|
16972
|
+
if (options.interactive !== void 0) return options.interactive;
|
|
16973
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
16974
|
+
}
|
|
16975
|
+
async function applyDoctorFixes(fixes, home) {
|
|
16976
|
+
return Promise.all(
|
|
16977
|
+
fixes.map(async (fix) => {
|
|
16978
|
+
try {
|
|
16979
|
+
const result = await removeMissingRegistryEntries(
|
|
16980
|
+
new Set(fix.paths ?? []),
|
|
16981
|
+
home
|
|
16982
|
+
);
|
|
16983
|
+
const removedCount = result.removedPaths.length;
|
|
16984
|
+
const skippedCount = result.skippedPaths.length;
|
|
16985
|
+
return {
|
|
16986
|
+
...fix,
|
|
16987
|
+
message: removedCount === 0 ? "no stale repository entries were removed" : `removed ${removedCount} stale ${removedCount === 1 ? "repository entry" : "repository entries"} from the registry`,
|
|
16988
|
+
status: removedCount === 0 ? "skipped" : "applied",
|
|
16989
|
+
hint: skippedCount > 0 ? `${skippedCount} path(s) were no longer confirmed missing` : void 0
|
|
16990
|
+
};
|
|
16991
|
+
} catch (error) {
|
|
16992
|
+
return {
|
|
16993
|
+
...fix,
|
|
16994
|
+
status: "failed",
|
|
16995
|
+
hint: error instanceof Error ? error.message : String(error)
|
|
16996
|
+
};
|
|
16997
|
+
}
|
|
16998
|
+
})
|
|
16999
|
+
);
|
|
17000
|
+
}
|
|
17001
|
+
async function detectRepositoryOrSkip(cwd) {
|
|
17002
|
+
try {
|
|
17003
|
+
return await detectRepository(cwd);
|
|
17004
|
+
} catch {
|
|
17005
|
+
return null;
|
|
17006
|
+
}
|
|
17007
|
+
}
|
|
17008
|
+
async function inspectConfig(path9, label, knownKeys) {
|
|
17009
|
+
try {
|
|
17010
|
+
const contents = await readFile4(path9, "utf8");
|
|
17011
|
+
const value = JSON.parse(contents);
|
|
17012
|
+
if (!isConfigObject(value)) {
|
|
17013
|
+
return {
|
|
17014
|
+
check: failedCheck(
|
|
17015
|
+
`${label}-config`,
|
|
17016
|
+
`${label} config must contain a JSON object (${path9})`,
|
|
17017
|
+
"replace the file contents with a JSON object"
|
|
17018
|
+
),
|
|
17019
|
+
config: {}
|
|
17020
|
+
};
|
|
17021
|
+
}
|
|
17022
|
+
const unknownKeys = Object.keys(value).filter((key) => !knownKeys.has(key));
|
|
17023
|
+
const warning = unknownKeys.length > 0 ? `; warning: unknown ${unknownKeys.length === 1 ? "key" : "keys"} ${unknownKeys.map((key) => `"${key}"`).join(", ")}` : "";
|
|
17024
|
+
return {
|
|
17025
|
+
check: okCheck(
|
|
17026
|
+
`${label}-config`,
|
|
17027
|
+
`${label} config valid (${path9}${warning})`
|
|
17028
|
+
),
|
|
17029
|
+
config: value
|
|
17030
|
+
};
|
|
17031
|
+
} catch (error) {
|
|
17032
|
+
if (isMissingFileError2(error)) {
|
|
17033
|
+
return {
|
|
17034
|
+
check: okCheck(
|
|
17035
|
+
`${label}-config`,
|
|
17036
|
+
`${label} config not found (optional)`
|
|
17037
|
+
),
|
|
17038
|
+
config: {}
|
|
17039
|
+
};
|
|
17040
|
+
}
|
|
17041
|
+
return {
|
|
17042
|
+
check: failedCheck(
|
|
17043
|
+
`${label}-config`,
|
|
17044
|
+
`${label} config is invalid (${path9})`,
|
|
17045
|
+
"fix the JSON syntax and run gji doctor again"
|
|
17046
|
+
),
|
|
17047
|
+
config: {}
|
|
17048
|
+
};
|
|
17049
|
+
}
|
|
17050
|
+
}
|
|
17051
|
+
function isConfigObject(value) {
|
|
17052
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17053
|
+
}
|
|
17054
|
+
function isMissingFileError2(error) {
|
|
17055
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
17056
|
+
}
|
|
17057
|
+
function resolveEffectiveConfig(globalConfig, localConfig, repoRoot, home) {
|
|
17058
|
+
const globalBase = { ...globalConfig };
|
|
17059
|
+
const repos = globalBase.repos;
|
|
17060
|
+
delete globalBase.repos;
|
|
17061
|
+
return {
|
|
17062
|
+
...globalBase,
|
|
17063
|
+
...resolvePerRepoConfig(repos, repoRoot, home),
|
|
17064
|
+
...localConfig
|
|
17065
|
+
};
|
|
17066
|
+
}
|
|
17067
|
+
function resolvePerRepoConfig(repos, repoRoot, home) {
|
|
17068
|
+
if (!repoRoot || !isConfigObject(repos)) {
|
|
17069
|
+
return {};
|
|
17070
|
+
}
|
|
17071
|
+
for (const [path9, config] of Object.entries(repos)) {
|
|
17072
|
+
if (expandTilde2(path9, home) === repoRoot && isConfigObject(config)) {
|
|
17073
|
+
return config;
|
|
17074
|
+
}
|
|
17075
|
+
}
|
|
17076
|
+
return {};
|
|
17077
|
+
}
|
|
17078
|
+
function expandTilde2(path9, home) {
|
|
17079
|
+
if (path9 === "~") return home;
|
|
17080
|
+
if (path9.startsWith("~/")) return join6(home, path9.slice(2));
|
|
17081
|
+
return path9;
|
|
17082
|
+
}
|
|
17083
|
+
async function checkGitVersion() {
|
|
17084
|
+
try {
|
|
17085
|
+
const { stdout: stdout2 } = await execFileAsync4("git", ["--version"]);
|
|
17086
|
+
const version = parseGitVersion(stdout2);
|
|
17087
|
+
if (!version) {
|
|
17088
|
+
return failedCheck(
|
|
17089
|
+
"git-version",
|
|
17090
|
+
"could not parse the installed Git version",
|
|
17091
|
+
"install Git 2.17 or newer"
|
|
17092
|
+
);
|
|
17093
|
+
}
|
|
17094
|
+
if (isGitVersionSupported(version)) {
|
|
17095
|
+
return okCheck("git-version", `git ${version.raw}`);
|
|
17096
|
+
}
|
|
17097
|
+
return failedCheck(
|
|
17098
|
+
"git-version",
|
|
17099
|
+
`git ${version.raw} is too old (requires Git 2.17 or newer)`,
|
|
17100
|
+
"upgrade Git and run gji doctor again"
|
|
17101
|
+
);
|
|
17102
|
+
} catch {
|
|
17103
|
+
return failedCheck(
|
|
17104
|
+
"git-version",
|
|
17105
|
+
"git is not available on PATH",
|
|
17106
|
+
"install Git 2.17 or newer"
|
|
17107
|
+
);
|
|
17108
|
+
}
|
|
17109
|
+
}
|
|
17110
|
+
function parseGitVersion(output) {
|
|
17111
|
+
const match = /(?:^|\s)(\d+)\.(\d+)(?:\.(\d+))?/.exec(output);
|
|
17112
|
+
if (!match) return null;
|
|
17113
|
+
return {
|
|
17114
|
+
major: Number(match[1]),
|
|
17115
|
+
minor: Number(match[2]),
|
|
17116
|
+
raw: match.slice(1).filter(Boolean).join(".")
|
|
17117
|
+
};
|
|
17118
|
+
}
|
|
17119
|
+
function isGitVersionSupported(version) {
|
|
17120
|
+
return version.major > MINIMUM_GIT_VERSION.major || version.major === MINIMUM_GIT_VERSION.major && version.minor >= MINIMUM_GIT_VERSION.minor;
|
|
17121
|
+
}
|
|
17122
|
+
async function checkShellIntegration(shell, home) {
|
|
17123
|
+
if (!shell) {
|
|
17124
|
+
return skippedCheck(
|
|
17125
|
+
"shell-integration",
|
|
17126
|
+
"shell integration not checked (unable to detect a supported shell)"
|
|
17127
|
+
);
|
|
17128
|
+
}
|
|
17129
|
+
const rcPath = resolveShellConfigPath(shell, home);
|
|
17130
|
+
try {
|
|
17131
|
+
const contents = await readFile4(rcPath, "utf8");
|
|
17132
|
+
if (hasShellIntegration(contents, shell)) {
|
|
17133
|
+
return okCheck(
|
|
17134
|
+
"shell-integration",
|
|
17135
|
+
`${shell} integration found in ${rcPath}`
|
|
17136
|
+
);
|
|
17137
|
+
}
|
|
17138
|
+
} catch (error) {
|
|
17139
|
+
if (isMissingFileError2(error)) {
|
|
17140
|
+
return failedCheck(
|
|
17141
|
+
"shell-integration",
|
|
17142
|
+
`shell integration not found because ${rcPath} does not exist`,
|
|
17143
|
+
`create ${rcPath}, then add: eval "$(gji init ${shell})"`
|
|
17144
|
+
);
|
|
17145
|
+
}
|
|
17146
|
+
return failedCheck(
|
|
17147
|
+
"shell-integration",
|
|
17148
|
+
`could not read ${rcPath}`,
|
|
17149
|
+
`add: eval "$(gji init ${shell})"`
|
|
17150
|
+
);
|
|
17151
|
+
}
|
|
17152
|
+
return failedCheck(
|
|
17153
|
+
"shell-integration",
|
|
17154
|
+
`shell integration not found in ${rcPath}`,
|
|
17155
|
+
`add: eval "$(gji init ${shell})"`
|
|
17156
|
+
);
|
|
17157
|
+
}
|
|
17158
|
+
async function checkCompletion(shell, home) {
|
|
17159
|
+
if (!shell) {
|
|
17160
|
+
return skippedCheck(
|
|
17161
|
+
"completion",
|
|
17162
|
+
"shell completion not checked (unable to detect a supported shell)"
|
|
17163
|
+
);
|
|
17164
|
+
}
|
|
17165
|
+
const path9 = resolveCompletionPath(shell, home);
|
|
17166
|
+
if (await pathExists(path9)) {
|
|
17167
|
+
return okCheck("completion", `${shell} completion installed (${path9})`);
|
|
17168
|
+
}
|
|
17169
|
+
return skippedCheck(
|
|
17170
|
+
"completion",
|
|
17171
|
+
`${shell} completion not installed (optional)`,
|
|
17172
|
+
`run: gji completion ${shell} > ${path9}`
|
|
17173
|
+
);
|
|
17174
|
+
}
|
|
17175
|
+
async function checkWorktreeBase(repository, config, home) {
|
|
17176
|
+
if (!repository) {
|
|
17177
|
+
return skippedCheck(
|
|
17178
|
+
"worktree-base",
|
|
17179
|
+
"worktree base not checked outside a Git repository"
|
|
17180
|
+
);
|
|
17181
|
+
}
|
|
17182
|
+
const basePath = resolveWorktreeBase(repository.repoRoot, config, home);
|
|
17183
|
+
const writablePath = await findNearestExistingPath(basePath);
|
|
17184
|
+
if (!writablePath) {
|
|
17185
|
+
return failedCheck(
|
|
17186
|
+
"worktree-base",
|
|
17187
|
+
`worktree base cannot be created (${basePath})`,
|
|
17188
|
+
"create a writable parent directory or update worktreePath"
|
|
17189
|
+
);
|
|
17190
|
+
}
|
|
17191
|
+
try {
|
|
17192
|
+
await access4(writablePath, constants3.W_OK | constants3.X_OK);
|
|
17193
|
+
return okCheck("worktree-base", `worktree base writable (${basePath})`);
|
|
17194
|
+
} catch {
|
|
17195
|
+
return failedCheck(
|
|
17196
|
+
"worktree-base",
|
|
17197
|
+
`worktree base is not writable (${writablePath})`,
|
|
17198
|
+
"update worktreePath to use a writable directory"
|
|
17199
|
+
);
|
|
17200
|
+
}
|
|
17201
|
+
}
|
|
17202
|
+
function resolveWorktreeBase(repoRoot, config, home) {
|
|
17203
|
+
const configuredPath = config.worktreePath;
|
|
17204
|
+
if (typeof configuredPath === "string" && (configuredPath.startsWith("/") || configuredPath.startsWith("~"))) {
|
|
17205
|
+
return expandTilde2(configuredPath, home);
|
|
17206
|
+
}
|
|
17207
|
+
return join6(dirname5(repoRoot), "worktrees", basename4(repoRoot));
|
|
17208
|
+
}
|
|
17209
|
+
async function findNearestExistingPath(path9) {
|
|
17210
|
+
let candidate = path9;
|
|
17211
|
+
while (true) {
|
|
17212
|
+
try {
|
|
17213
|
+
await access4(candidate, constants3.F_OK);
|
|
17214
|
+
return candidate;
|
|
17215
|
+
} catch {
|
|
17216
|
+
const parent = dirname5(candidate);
|
|
17217
|
+
if (parent === candidate) return null;
|
|
17218
|
+
candidate = parent;
|
|
17219
|
+
}
|
|
17220
|
+
}
|
|
17221
|
+
}
|
|
17222
|
+
async function inspectRegistry(home) {
|
|
17223
|
+
const entries = await loadRegistry(home);
|
|
17224
|
+
const missingEntries = await Promise.all(
|
|
17225
|
+
entries.map(async (entry) => ({
|
|
17226
|
+
entry,
|
|
17227
|
+
status: await inspectRegistryPath(entry.path)
|
|
17228
|
+
}))
|
|
17229
|
+
);
|
|
17230
|
+
const missingCount = missingEntries.filter(
|
|
17231
|
+
({ status }) => status === "missing"
|
|
17232
|
+
).length;
|
|
17233
|
+
const unreadableCount = missingEntries.filter(
|
|
17234
|
+
({ status }) => status === "unreadable"
|
|
17235
|
+
).length;
|
|
17236
|
+
const missingPaths = missingEntries.filter(({ status }) => status === "missing").map(({ entry }) => entry.path);
|
|
17237
|
+
if (missingCount === 0 && unreadableCount === 0) {
|
|
17238
|
+
return {
|
|
17239
|
+
check: okCheck(
|
|
17240
|
+
"repo-registry",
|
|
17241
|
+
`${entries.length} repos registered, all reachable`
|
|
17242
|
+
),
|
|
17243
|
+
missingPaths
|
|
17244
|
+
};
|
|
17245
|
+
}
|
|
17246
|
+
const messageParts = [];
|
|
17247
|
+
if (missingCount > 0) {
|
|
17248
|
+
messageParts.push(
|
|
17249
|
+
`${missingCount} ${missingCount === 1 ? "path is" : "paths are"} missing`
|
|
17250
|
+
);
|
|
17251
|
+
}
|
|
17252
|
+
if (unreadableCount > 0) {
|
|
17253
|
+
messageParts.push(
|
|
17254
|
+
`${unreadableCount} ${unreadableCount === 1 ? "path is" : "paths are"} not accessible`
|
|
17255
|
+
);
|
|
17256
|
+
}
|
|
17257
|
+
return {
|
|
17258
|
+
check: failedCheck(
|
|
17259
|
+
"repo-registry",
|
|
17260
|
+
`${entries.length} repos registered, ${messageParts.join(", ")}`,
|
|
17261
|
+
missingCount > 0 ? `remove confirmed stale entries from ${REGISTRY_FILE_PATH(home)}; check permissions for inaccessible paths` : "check permissions for inaccessible paths before removing registry entries"
|
|
17262
|
+
),
|
|
17263
|
+
missingPaths
|
|
17264
|
+
};
|
|
17265
|
+
}
|
|
17266
|
+
async function inspectRegistryPath(path9) {
|
|
17267
|
+
try {
|
|
17268
|
+
await access4(path9, constants3.F_OK);
|
|
17269
|
+
return "exists";
|
|
17270
|
+
} catch (error) {
|
|
17271
|
+
if (isMissingPathError2(error)) return "missing";
|
|
17272
|
+
return "unreadable";
|
|
17273
|
+
}
|
|
17274
|
+
}
|
|
17275
|
+
function isMissingPathError2(error) {
|
|
17276
|
+
if (!(error instanceof Error) || !("code" in error)) return false;
|
|
17277
|
+
const code = error.code;
|
|
17278
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
17279
|
+
}
|
|
17280
|
+
async function pathExists(path9) {
|
|
17281
|
+
try {
|
|
17282
|
+
await access4(path9, constants3.F_OK);
|
|
17283
|
+
return true;
|
|
17284
|
+
} catch {
|
|
17285
|
+
return false;
|
|
17286
|
+
}
|
|
17287
|
+
}
|
|
17288
|
+
async function checkEditor(config) {
|
|
17289
|
+
const editor = config.editor;
|
|
17290
|
+
if (typeof editor !== "string" || editor.length === 0) {
|
|
17291
|
+
return skippedCheck("editor", "editor not configured (optional)");
|
|
17292
|
+
}
|
|
17293
|
+
if (await executableExists(editor)) {
|
|
17294
|
+
return okCheck("editor", `editor "${editor}" found on PATH`);
|
|
17295
|
+
}
|
|
17296
|
+
const knownEditor = EDITORS.some(({ cli }) => cli === editor);
|
|
17297
|
+
return failedCheck(
|
|
17298
|
+
"editor",
|
|
17299
|
+
`editor "${editor}" was not found on PATH`,
|
|
17300
|
+
knownEditor ? `install ${editor} or choose another editor with: gji open --save` : "choose another editor with: gji open --save"
|
|
17301
|
+
);
|
|
17302
|
+
}
|
|
17303
|
+
function okCheck(id, message) {
|
|
17304
|
+
return { id, message, status: "ok" };
|
|
17305
|
+
}
|
|
17306
|
+
function failedCheck(id, message, hint) {
|
|
17307
|
+
return { hint, id, message, status: "fail" };
|
|
17308
|
+
}
|
|
17309
|
+
function skippedCheck(id, message, hint) {
|
|
17310
|
+
return { hint, id, message, status: "skip" };
|
|
17311
|
+
}
|
|
17312
|
+
function renderDoctorChecks(checks, problems) {
|
|
17313
|
+
const lines = ["gji doctor", ""];
|
|
17314
|
+
for (const check of checks) {
|
|
17315
|
+
lines.push(` ${statusSymbol(check.status)} ${check.message}`);
|
|
17316
|
+
if (check.hint) lines.push(` ${check.hint}`);
|
|
17317
|
+
}
|
|
17318
|
+
lines.push(
|
|
17319
|
+
"",
|
|
17320
|
+
`${problems} ${problems === 1 ? "problem" : "problems"} found.`
|
|
17321
|
+
);
|
|
17322
|
+
return `${lines.join("\n")}
|
|
17323
|
+
`;
|
|
17324
|
+
}
|
|
17325
|
+
function renderDoctorFixes(fixes) {
|
|
17326
|
+
const lines = ["", "Automatic fixes:"];
|
|
17327
|
+
if (fixes.length === 0) {
|
|
17328
|
+
lines.push(" No automatic fixes available.");
|
|
17329
|
+
} else {
|
|
17330
|
+
for (const fix of fixes) {
|
|
17331
|
+
lines.push(` ${fixStatusSymbol(fix.status)} ${fix.message}`);
|
|
17332
|
+
if (fix.paths && fix.paths.length > 0) {
|
|
17333
|
+
for (const path9 of fix.paths) lines.push(` ${path9}`);
|
|
17334
|
+
}
|
|
17335
|
+
if (fix.hint) lines.push(` ${fix.hint}`);
|
|
17336
|
+
}
|
|
17337
|
+
}
|
|
17338
|
+
return `${lines.join("\n")}
|
|
17339
|
+
`;
|
|
17340
|
+
}
|
|
17341
|
+
function fixStatusSymbol(status) {
|
|
17342
|
+
switch (status) {
|
|
17343
|
+
case "applied":
|
|
17344
|
+
return "\u2713";
|
|
17345
|
+
case "declined":
|
|
17346
|
+
return "-";
|
|
17347
|
+
case "failed":
|
|
17348
|
+
return "\u2717";
|
|
17349
|
+
case "pending":
|
|
17350
|
+
return "!";
|
|
17351
|
+
case "skipped":
|
|
17352
|
+
return "-";
|
|
17353
|
+
}
|
|
17354
|
+
}
|
|
17355
|
+
function statusSymbol(status) {
|
|
17356
|
+
switch (status) {
|
|
17357
|
+
case "fail":
|
|
17358
|
+
return "\u2717";
|
|
17359
|
+
case "ok":
|
|
17360
|
+
return "\u2713";
|
|
17361
|
+
case "skip":
|
|
17362
|
+
return "-";
|
|
17363
|
+
}
|
|
17364
|
+
}
|
|
17365
|
+
|
|
17366
|
+
// src/go.ts
|
|
17367
|
+
import { basename as basename7 } from "node:path";
|
|
17368
|
+
|
|
17369
|
+
// src/warp.ts
|
|
17370
|
+
import { realpath as realpath2 } from "node:fs/promises";
|
|
17371
|
+
import { basename as basename6, resolve as resolve5 } from "node:path";
|
|
17372
|
+
|
|
17373
|
+
// src/new.ts
|
|
17374
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
17375
|
+
import { mkdir as mkdir5 } from "node:fs/promises";
|
|
17376
|
+
import { basename as basename5, dirname as dirname7 } from "node:path";
|
|
17377
|
+
import { promisify as promisify6 } from "node:util";
|
|
17378
|
+
|
|
17379
|
+
// src/conflict.ts
|
|
17380
|
+
import { constants as constants4 } from "node:fs";
|
|
17381
|
+
import { access as access5 } from "node:fs/promises";
|
|
17382
|
+
async function pathExists2(path9) {
|
|
17383
|
+
try {
|
|
17384
|
+
await access5(path9, constants4.F_OK);
|
|
17385
|
+
return true;
|
|
17386
|
+
} catch {
|
|
17387
|
+
return false;
|
|
17388
|
+
}
|
|
17389
|
+
}
|
|
17390
|
+
async function promptForPathConflict(path9) {
|
|
17391
|
+
const choice = await ve({
|
|
17392
|
+
message: `Target path already exists: ${path9}`,
|
|
17393
|
+
options: [
|
|
17394
|
+
{
|
|
17395
|
+
value: "abort",
|
|
17396
|
+
label: "Abort",
|
|
17397
|
+
hint: "Keep the existing directory untouched"
|
|
17398
|
+
},
|
|
17399
|
+
{
|
|
17400
|
+
value: "reuse",
|
|
17401
|
+
label: "Reuse path",
|
|
17402
|
+
hint: "Print the existing path and stop"
|
|
17403
|
+
}
|
|
17404
|
+
]
|
|
17405
|
+
});
|
|
17406
|
+
if (pD(choice)) {
|
|
17407
|
+
return "abort";
|
|
17408
|
+
}
|
|
17409
|
+
return choice;
|
|
17410
|
+
}
|
|
17411
|
+
|
|
15904
17412
|
// src/file-sync.ts
|
|
15905
|
-
import { copyFile, mkdir as
|
|
15906
|
-
import { dirname as
|
|
17413
|
+
import { copyFile, mkdir as mkdir4, stat } from "node:fs/promises";
|
|
17414
|
+
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join7, normalize } from "node:path";
|
|
15907
17415
|
async function syncFiles(mainRoot, targetPath, patterns) {
|
|
15908
17416
|
for (const pattern of patterns) {
|
|
15909
17417
|
const normalized = validateSyncFilePattern(pattern);
|
|
15910
|
-
const sourcePath =
|
|
15911
|
-
const destPath =
|
|
17418
|
+
const sourcePath = join7(mainRoot, normalized);
|
|
17419
|
+
const destPath = join7(targetPath, normalized);
|
|
15912
17420
|
const sourceExists = await fileExists(sourcePath);
|
|
15913
17421
|
if (!sourceExists) {
|
|
15914
17422
|
continue;
|
|
@@ -15917,7 +17425,7 @@ async function syncFiles(mainRoot, targetPath, patterns) {
|
|
|
15917
17425
|
if (destExists) {
|
|
15918
17426
|
continue;
|
|
15919
17427
|
}
|
|
15920
|
-
await
|
|
17428
|
+
await mkdir4(dirname6(destPath), { recursive: true });
|
|
15921
17429
|
await copyFile(sourcePath, destPath);
|
|
15922
17430
|
}
|
|
15923
17431
|
}
|
|
@@ -15954,8 +17462,8 @@ function isNotFoundError(error) {
|
|
|
15954
17462
|
import { spawn as spawn4 } from "node:child_process";
|
|
15955
17463
|
|
|
15956
17464
|
// src/package-manager.ts
|
|
15957
|
-
import { access as
|
|
15958
|
-
import { join as
|
|
17465
|
+
import { access as access6, readdir } from "node:fs/promises";
|
|
17466
|
+
import { join as join8 } from "node:path";
|
|
15959
17467
|
var ENTRIES = [
|
|
15960
17468
|
// JavaScript / TypeScript
|
|
15961
17469
|
{ name: "pnpm", signals: ["pnpm-lock.yaml"], command: "pnpm install" },
|
|
@@ -16085,7 +17593,7 @@ async function detectPackageManager(repoRoot) {
|
|
|
16085
17593
|
async function matchesExact(repoRoot, signals) {
|
|
16086
17594
|
for (const signal of signals) {
|
|
16087
17595
|
try {
|
|
16088
|
-
await
|
|
17596
|
+
await access6(join8(repoRoot, signal));
|
|
16089
17597
|
return true;
|
|
16090
17598
|
} catch {
|
|
16091
17599
|
}
|
|
@@ -16249,7 +17757,7 @@ function isPlainObject2(value) {
|
|
|
16249
17757
|
}
|
|
16250
17758
|
|
|
16251
17759
|
// src/new.ts
|
|
16252
|
-
var
|
|
17760
|
+
var execFileAsync5 = promisify6(execFile5);
|
|
16253
17761
|
var NEW_OUTPUT_FILE_ENV = "GJI_NEW_OUTPUT_FILE";
|
|
16254
17762
|
function createNewCommand(dependencies = {}) {
|
|
16255
17763
|
const createBranchPlaceholder = dependencies.createBranchPlaceholder ?? generateBranchPlaceholder;
|
|
@@ -16317,11 +17825,11 @@ function createNewCommand(dependencies = {}) {
|
|
|
16317
17825
|
worktreeName,
|
|
16318
17826
|
configuredBasePath
|
|
16319
17827
|
);
|
|
16320
|
-
if (!usesGeneratedDetachedName && await
|
|
17828
|
+
if (!usesGeneratedDetachedName && await pathExists2(worktreePath)) {
|
|
16321
17829
|
if (options.force) {
|
|
16322
17830
|
if (!options.dryRun) {
|
|
16323
17831
|
try {
|
|
16324
|
-
await
|
|
17832
|
+
await execFileAsync5(
|
|
16325
17833
|
"git",
|
|
16326
17834
|
["worktree", "remove", "--force", worktreePath],
|
|
16327
17835
|
{ cwd: repository.repoRoot }
|
|
@@ -16342,7 +17850,7 @@ function createNewCommand(dependencies = {}) {
|
|
|
16342
17850
|
}
|
|
16343
17851
|
if (!options.detached) {
|
|
16344
17852
|
try {
|
|
16345
|
-
await
|
|
17853
|
+
await execFileAsync5("git", ["branch", "-D", worktreeName], {
|
|
16346
17854
|
cwd: repository.repoRoot
|
|
16347
17855
|
});
|
|
16348
17856
|
} catch {
|
|
@@ -16399,9 +17907,9 @@ function createNewCommand(dependencies = {}) {
|
|
|
16399
17907
|
}
|
|
16400
17908
|
return 0;
|
|
16401
17909
|
}
|
|
16402
|
-
await
|
|
17910
|
+
await mkdir5(dirname7(worktreePath), { recursive: true });
|
|
16403
17911
|
const gitArgs = options.detached ? ["worktree", "add", "--detach", worktreePath] : await localBranchExists(repository.repoRoot, worktreeName) ? ["worktree", "add", worktreePath, worktreeName] : ["worktree", "add", "-b", worktreeName, worktreePath];
|
|
16404
|
-
await
|
|
17912
|
+
await execFileAsync5("git", gitArgs, { cwd: repository.repoRoot });
|
|
16405
17913
|
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
|
|
16406
17914
|
(p2) => typeof p2 === "string"
|
|
16407
17915
|
) : [];
|
|
@@ -16430,7 +17938,7 @@ function createNewCommand(dependencies = {}) {
|
|
|
16430
17938
|
{
|
|
16431
17939
|
branch: worktreeName,
|
|
16432
17940
|
path: worktreePath,
|
|
16433
|
-
repo:
|
|
17941
|
+
repo: basename5(repository.repoRoot)
|
|
16434
17942
|
},
|
|
16435
17943
|
options.stderr
|
|
16436
17944
|
);
|
|
@@ -16582,7 +18090,7 @@ async function resolveUniqueDetachedWorktreePath(repoRoot, baseName, basePath) {
|
|
|
16582
18090
|
candidateName,
|
|
16583
18091
|
basePath
|
|
16584
18092
|
);
|
|
16585
|
-
if (!await
|
|
18093
|
+
if (!await pathExists2(candidatePath)) {
|
|
16586
18094
|
return candidatePath;
|
|
16587
18095
|
}
|
|
16588
18096
|
attempt += 1;
|
|
@@ -16605,7 +18113,7 @@ async function defaultPromptForBranch(placeholder) {
|
|
|
16605
18113
|
}
|
|
16606
18114
|
async function localBranchExists(repoRoot, branchName) {
|
|
16607
18115
|
try {
|
|
16608
|
-
await
|
|
18116
|
+
await execFileAsync5(
|
|
16609
18117
|
"git",
|
|
16610
18118
|
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
16611
18119
|
{ cwd: repoRoot }
|
|
@@ -16650,74 +18158,6 @@ async function openWorktree(worktreePath, editorCli, spawnFn, stderr) {
|
|
|
16650
18158
|
}
|
|
16651
18159
|
}
|
|
16652
18160
|
|
|
16653
|
-
// src/repo-registry.ts
|
|
16654
|
-
import { mkdir as mkdir5, readFile as readFile3, realpath, writeFile as writeFile4 } from "node:fs/promises";
|
|
16655
|
-
import { homedir as homedir4 } from "node:os";
|
|
16656
|
-
import { basename as basename4, dirname as dirname6, join as join6, resolve as resolve4 } from "node:path";
|
|
16657
|
-
var REGISTRY_FILE_NAME = "repos.json";
|
|
16658
|
-
var MAX_REGISTRY_ENTRIES = 100;
|
|
16659
|
-
function REGISTRY_FILE_PATH(home = homedir4()) {
|
|
16660
|
-
const configDir = process.env.GJI_CONFIG_DIR;
|
|
16661
|
-
if (configDir) {
|
|
16662
|
-
return join6(resolve4(configDir), REGISTRY_FILE_NAME);
|
|
16663
|
-
}
|
|
16664
|
-
return join6(home, GLOBAL_CONFIG_DIRECTORY, REGISTRY_FILE_NAME);
|
|
16665
|
-
}
|
|
16666
|
-
async function loadRegistry(home = homedir4()) {
|
|
16667
|
-
const path9 = REGISTRY_FILE_PATH(home);
|
|
16668
|
-
try {
|
|
16669
|
-
const raw = await readFile3(path9, "utf8");
|
|
16670
|
-
const parsed = JSON.parse(raw);
|
|
16671
|
-
if (!Array.isArray(parsed)) return [];
|
|
16672
|
-
return parsed.filter(isRegistryEntry);
|
|
16673
|
-
} catch {
|
|
16674
|
-
return [];
|
|
16675
|
-
}
|
|
16676
|
-
}
|
|
16677
|
-
async function canonicalizeRepoPath(repoPath) {
|
|
16678
|
-
try {
|
|
16679
|
-
return await realpath(repoPath);
|
|
16680
|
-
} catch {
|
|
16681
|
-
return resolve4(repoPath);
|
|
16682
|
-
}
|
|
16683
|
-
}
|
|
16684
|
-
async function registerRepo(repoPath, home = homedir4()) {
|
|
16685
|
-
const registryPath = REGISTRY_FILE_PATH(home);
|
|
16686
|
-
const existing = await normalizeRegistryForWrite(await loadRegistry(home));
|
|
16687
|
-
const canonicalRepoPath = await canonicalizeRepoPath(repoPath);
|
|
16688
|
-
if (existing.length > 0 && existing[0].path === canonicalRepoPath) return;
|
|
16689
|
-
const entry = {
|
|
16690
|
-
lastUsed: Date.now(),
|
|
16691
|
-
name: basename4(canonicalRepoPath),
|
|
16692
|
-
path: canonicalRepoPath
|
|
16693
|
-
};
|
|
16694
|
-
const filtered = existing.filter((e2) => e2.path !== canonicalRepoPath);
|
|
16695
|
-
const next = [entry, ...filtered].slice(0, MAX_REGISTRY_ENTRIES);
|
|
16696
|
-
await mkdir5(dirname6(registryPath), { recursive: true });
|
|
16697
|
-
await writeFile4(registryPath, `${JSON.stringify(next, null, 2)}
|
|
16698
|
-
`, "utf8");
|
|
16699
|
-
}
|
|
16700
|
-
async function normalizeRegistryForWrite(entries) {
|
|
16701
|
-
const normalized = [];
|
|
16702
|
-
const seenPaths = /* @__PURE__ */ new Set();
|
|
16703
|
-
for (const entry of entries) {
|
|
16704
|
-
const canonicalPath = await canonicalizeRepoPath(entry.path);
|
|
16705
|
-
if (seenPaths.has(canonicalPath)) {
|
|
16706
|
-
continue;
|
|
16707
|
-
}
|
|
16708
|
-
seenPaths.add(canonicalPath);
|
|
16709
|
-
normalized.push({
|
|
16710
|
-
...entry,
|
|
16711
|
-
name: basename4(canonicalPath),
|
|
16712
|
-
path: canonicalPath
|
|
16713
|
-
});
|
|
16714
|
-
}
|
|
16715
|
-
return normalized;
|
|
16716
|
-
}
|
|
16717
|
-
function isRegistryEntry(value) {
|
|
16718
|
-
return typeof value === "object" && value !== null && "path" in value && typeof value.path === "string" && "name" in value && typeof value.name === "string" && "lastUsed" in value && typeof value.lastUsed === "number";
|
|
16719
|
-
}
|
|
16720
|
-
|
|
16721
18161
|
// src/warp.ts
|
|
16722
18162
|
var WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
|
|
16723
18163
|
async function runWarpCommand(options) {
|
|
@@ -16749,7 +18189,8 @@ async function runWarpNavigate(options) {
|
|
|
16749
18189
|
const target = await resolveWarpTarget({
|
|
16750
18190
|
...options,
|
|
16751
18191
|
commandName: "gji warp",
|
|
16752
|
-
json: options.json
|
|
18192
|
+
json: options.json,
|
|
18193
|
+
queryPullRequests: options.queryPullRequests
|
|
16753
18194
|
});
|
|
16754
18195
|
if (!target) return 1;
|
|
16755
18196
|
if (options.json) {
|
|
@@ -16829,7 +18270,7 @@ async function deduplicateRegistryForNew(registry) {
|
|
|
16829
18270
|
seenPaths.add(canonicalPath);
|
|
16830
18271
|
deduplicated.push({
|
|
16831
18272
|
...entry,
|
|
16832
|
-
name:
|
|
18273
|
+
name: basename6(canonicalPath),
|
|
16833
18274
|
path: canonicalPath
|
|
16834
18275
|
});
|
|
16835
18276
|
}
|
|
@@ -16866,15 +18307,16 @@ async function resolveWarpTarget(options) {
|
|
|
16866
18307
|
const results = await Promise.allSettled(
|
|
16867
18308
|
registry.map(async (entry) => {
|
|
16868
18309
|
const worktrees = await listWorktrees(entry.path);
|
|
16869
|
-
return { repoName: entry.name, worktrees };
|
|
18310
|
+
return { repoName: entry.name, repoRoot: entry.path, worktrees };
|
|
16870
18311
|
})
|
|
16871
18312
|
);
|
|
16872
18313
|
const allItems = [];
|
|
16873
18314
|
for (const result of results) {
|
|
16874
18315
|
if (result.status === "rejected") continue;
|
|
16875
|
-
const { repoName, worktrees } = result.value;
|
|
18316
|
+
const { repoName, repoRoot, worktrees } = result.value;
|
|
16876
18317
|
for (const worktree of worktrees) {
|
|
16877
18318
|
allItems.push({
|
|
18319
|
+
repoRoot,
|
|
16878
18320
|
repoName,
|
|
16879
18321
|
worktree: {
|
|
16880
18322
|
...worktree,
|
|
@@ -16888,6 +18330,7 @@ async function resolveWarpTarget(options) {
|
|
|
16888
18330
|
return null;
|
|
16889
18331
|
}
|
|
16890
18332
|
const promptSources = allItems.map((item) => ({
|
|
18333
|
+
repoRoot: item.repoRoot,
|
|
16891
18334
|
repoName: item.repoName,
|
|
16892
18335
|
worktree: item.worktree
|
|
16893
18336
|
}));
|
|
@@ -16899,7 +18342,9 @@ async function resolveWarpTarget(options) {
|
|
|
16899
18342
|
}
|
|
16900
18343
|
return { branch: match.worktree.branch, path: match.worktree.path };
|
|
16901
18344
|
}
|
|
16902
|
-
const promptEntries = await buildWorktreePromptEntries(promptSources
|
|
18345
|
+
const promptEntries = await buildWorktreePromptEntries(promptSources, {
|
|
18346
|
+
queryPullRequests: options.queryPullRequests
|
|
18347
|
+
});
|
|
16903
18348
|
const path9 = await promptForWarpTarget(promptEntries);
|
|
16904
18349
|
if (!path9) {
|
|
16905
18350
|
options.stderr("Aborted\n");
|
|
@@ -16947,10 +18392,13 @@ function createGoCommand(dependencies = {}) {
|
|
|
16947
18392
|
return 1;
|
|
16948
18393
|
}
|
|
16949
18394
|
const promptSources = worktrees.map((worktree) => ({
|
|
18395
|
+
repoRoot: repository.repoRoot,
|
|
16950
18396
|
repoName: repository.repoName,
|
|
16951
18397
|
worktree
|
|
16952
18398
|
}));
|
|
16953
|
-
const promptEntries = options.branch ? [] : await buildWorktreePromptEntries(promptSources
|
|
18399
|
+
const promptEntries = options.branch ? [] : await buildWorktreePromptEntries(promptSources, {
|
|
18400
|
+
queryPullRequests: dependencies.queryPullRequests
|
|
18401
|
+
});
|
|
16954
18402
|
const queried = options.branch ? resolveWorktreeQuery(promptSources, options.branch) : null;
|
|
16955
18403
|
const prompted = options.branch ? null : await prompt(promptEntries);
|
|
16956
18404
|
const resolvedPath = options.branch ? queried?.worktree.path : prompted ?? void 0;
|
|
@@ -16978,7 +18426,7 @@ function createGoCommand(dependencies = {}) {
|
|
|
16978
18426
|
{
|
|
16979
18427
|
branch: chosenWorktree?.branch ?? void 0,
|
|
16980
18428
|
path: resolvedPath,
|
|
16981
|
-
repo:
|
|
18429
|
+
repo: basename7(repository.repoRoot)
|
|
16982
18430
|
},
|
|
16983
18431
|
options.stderr
|
|
16984
18432
|
);
|
|
@@ -17009,11 +18457,11 @@ async function runHistoryCommand(options) {
|
|
|
17009
18457
|
}
|
|
17010
18458
|
|
|
17011
18459
|
// src/init.ts
|
|
17012
|
-
import {
|
|
17013
|
-
import {
|
|
17014
|
-
import {
|
|
17015
|
-
|
|
17016
|
-
var
|
|
18460
|
+
import { constants as constants5 } from "node:fs";
|
|
18461
|
+
import { access as access7, mkdir as mkdir6, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
|
|
18462
|
+
import { homedir as homedir6 } from "node:os";
|
|
18463
|
+
import { dirname as dirname8 } from "node:path";
|
|
18464
|
+
var ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
|
|
17017
18465
|
var SHELL_WRAPPED_COMMANDS = [
|
|
17018
18466
|
{
|
|
17019
18467
|
bypassOptions: ["--help"],
|
|
@@ -17064,10 +18512,256 @@ var SHELL_WRAPPED_COMMANDS = [
|
|
|
17064
18512
|
names: ["warp"],
|
|
17065
18513
|
tempPrefix: "gji-warp"
|
|
17066
18514
|
}
|
|
17067
|
-
];
|
|
17068
|
-
async function runInitCommand(options) {
|
|
18515
|
+
];
|
|
18516
|
+
async function runInitCommand(options) {
|
|
18517
|
+
if (options.shell === void 0) {
|
|
18518
|
+
return runOnboardingInitCommand(options);
|
|
18519
|
+
}
|
|
18520
|
+
return runLegacyInitCommand(options);
|
|
18521
|
+
}
|
|
18522
|
+
async function runOnboardingInitCommand(options) {
|
|
18523
|
+
if (options.json || isHeadless() || !canRunOnboarding(options)) {
|
|
18524
|
+
return writeNonInteractiveInitError(options);
|
|
18525
|
+
}
|
|
18526
|
+
const home = options.home ?? homedir6();
|
|
18527
|
+
const context = {
|
|
18528
|
+
detectedShell: resolveSupportedShell(void 0, process.env.SHELL),
|
|
18529
|
+
home
|
|
18530
|
+
};
|
|
18531
|
+
if (options.promptForOnboarding === void 0) {
|
|
18532
|
+
return runDefaultOnboarding(context);
|
|
18533
|
+
}
|
|
18534
|
+
const result = await options.promptForOnboarding(context);
|
|
18535
|
+
if (!result) {
|
|
18536
|
+
options.stderr?.("Aborted.\n");
|
|
18537
|
+
return 1;
|
|
18538
|
+
}
|
|
18539
|
+
await applyOnboardingResult(result, home);
|
|
18540
|
+
return 0;
|
|
18541
|
+
}
|
|
18542
|
+
function canRunOnboarding(options) {
|
|
18543
|
+
return options.interactive ?? (options.promptForOnboarding !== void 0 || process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
18544
|
+
}
|
|
18545
|
+
function writeNonInteractiveInitError(options) {
|
|
18546
|
+
const error = "run `gji init <shell> --write` in non-interactive mode";
|
|
18547
|
+
options.stderr?.(
|
|
18548
|
+
options.json ? `${JSON.stringify({ error })}
|
|
18549
|
+
` : `${error}
|
|
18550
|
+
`
|
|
18551
|
+
);
|
|
18552
|
+
return 1;
|
|
18553
|
+
}
|
|
18554
|
+
async function runDefaultOnboarding(context) {
|
|
18555
|
+
Ie("gji init");
|
|
18556
|
+
const shell = await ve({
|
|
18557
|
+
message: "Which shell should gji configure?",
|
|
18558
|
+
initialValue: context.detectedShell ?? "zsh",
|
|
18559
|
+
options: [
|
|
18560
|
+
{ value: "zsh", label: "zsh" },
|
|
18561
|
+
{ value: "bash", label: "bash" },
|
|
18562
|
+
{ value: "fish", label: "fish" }
|
|
18563
|
+
]
|
|
18564
|
+
});
|
|
18565
|
+
if (pD(shell)) return abortDefaultOnboarding();
|
|
18566
|
+
const rcPath = resolveShellConfigPath(shell, context.home);
|
|
18567
|
+
const currentConfig = await readExistingConfig(rcPath);
|
|
18568
|
+
const shellIntegration = await promptForShellIntegration(
|
|
18569
|
+
currentConfig,
|
|
18570
|
+
rcPath,
|
|
18571
|
+
shell
|
|
18572
|
+
);
|
|
18573
|
+
if (shellIntegration === null) return abortDefaultOnboarding();
|
|
18574
|
+
const completionPath = resolveCompletionPath(shell, context.home);
|
|
18575
|
+
const completionAlreadyInstalled = await fileExists2(completionPath);
|
|
18576
|
+
await applyShellIntegration(shell, shellIntegration, context.home);
|
|
18577
|
+
const installCompletion = await promptForCompletion(shell, completionPath);
|
|
18578
|
+
if (installCompletion === null) return abortDefaultOnboarding();
|
|
18579
|
+
if (installCompletion) {
|
|
18580
|
+
await mkdir6(dirname8(completionPath), { recursive: true });
|
|
18581
|
+
await writeFile5(completionPath, renderShellCompletion(shell), "utf8");
|
|
18582
|
+
}
|
|
18583
|
+
if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
|
|
18584
|
+
await ensureZshCompletionPath(rcPath);
|
|
18585
|
+
}
|
|
18586
|
+
const editor = await promptForEditor();
|
|
18587
|
+
if (editor === null) return abortDefaultOnboarding();
|
|
18588
|
+
if (editor) {
|
|
18589
|
+
await updateGlobalConfigKey("editor", editor, context.home);
|
|
18590
|
+
}
|
|
18591
|
+
Se(
|
|
18592
|
+
`Setup complete. Restart your shell or run: source ${rcPath}
|
|
18593
|
+
Verify with: gji doctor`
|
|
18594
|
+
);
|
|
18595
|
+
return 0;
|
|
18596
|
+
}
|
|
18597
|
+
async function applyShellIntegration(shell, choice, home) {
|
|
18598
|
+
if (choice === "install") {
|
|
18599
|
+
await installOnboardingShellIntegration(shell, home);
|
|
18600
|
+
}
|
|
18601
|
+
if (choice !== "skip") {
|
|
18602
|
+
await updateGlobalConfigKey("shellIntegration", true, home);
|
|
18603
|
+
}
|
|
18604
|
+
}
|
|
18605
|
+
function abortDefaultOnboarding() {
|
|
18606
|
+
Se("Aborted.");
|
|
18607
|
+
return 1;
|
|
18608
|
+
}
|
|
18609
|
+
async function promptForShellIntegration(currentConfig, rcPath, shell) {
|
|
18610
|
+
if (currentConfig.includes(ONBOARDING_SHELL_INTEGRATION_START_MARKER) && currentConfig.includes(ONBOARDING_SHELL_INTEGRATION_END_MARKER)) {
|
|
18611
|
+
M2.success(`Shell integration marker found in ${rcPath}; refreshing it.`);
|
|
18612
|
+
return "install";
|
|
18613
|
+
}
|
|
18614
|
+
if (hasShellIntegration(currentConfig, shell)) {
|
|
18615
|
+
M2.success(`Shell integration already installed in ${rcPath} \u2713`);
|
|
18616
|
+
return "existing";
|
|
18617
|
+
}
|
|
18618
|
+
const confirmed = await ye({
|
|
18619
|
+
message: `Install shell integration in ${rcPath}?`,
|
|
18620
|
+
initialValue: true
|
|
18621
|
+
});
|
|
18622
|
+
if (pD(confirmed)) return null;
|
|
18623
|
+
return confirmed ? "install" : "skip";
|
|
18624
|
+
}
|
|
18625
|
+
async function promptForCompletion(shell, completionPath) {
|
|
18626
|
+
if (await fileExists2(completionPath)) {
|
|
18627
|
+
M2.success(`${shell} completion already installed at ${completionPath} \u2713`);
|
|
18628
|
+
return false;
|
|
18629
|
+
}
|
|
18630
|
+
const confirmed = await ye({
|
|
18631
|
+
message: `Install ${shell} completion at ${completionPath}?`,
|
|
18632
|
+
initialValue: true
|
|
18633
|
+
});
|
|
18634
|
+
if (pD(confirmed)) return null;
|
|
18635
|
+
return confirmed;
|
|
18636
|
+
}
|
|
18637
|
+
async function promptForEditor() {
|
|
18638
|
+
const availableEditors = await findAvailableEditors();
|
|
18639
|
+
if (availableEditors.length === 0) {
|
|
18640
|
+
M2.info("No supported editor CLIs found on PATH; skipping editor setup.");
|
|
18641
|
+
return void 0;
|
|
18642
|
+
}
|
|
18643
|
+
const skipValue = "__gji_skip_editor__";
|
|
18644
|
+
const selected = await ve({
|
|
18645
|
+
message: "Which editor should gji use?",
|
|
18646
|
+
options: [
|
|
18647
|
+
...availableEditors.map(({ cli, name }) => ({
|
|
18648
|
+
value: cli,
|
|
18649
|
+
label: name,
|
|
18650
|
+
hint: cli
|
|
18651
|
+
})),
|
|
18652
|
+
{ value: skipValue, label: "Skip" }
|
|
18653
|
+
]
|
|
18654
|
+
});
|
|
18655
|
+
if (pD(selected)) return null;
|
|
18656
|
+
return selected === skipValue ? void 0 : selected;
|
|
18657
|
+
}
|
|
18658
|
+
async function findAvailableEditors() {
|
|
18659
|
+
const availableEditors = [];
|
|
18660
|
+
for (const editor of EDITORS) {
|
|
18661
|
+
if (await executableExists(editor.cli)) {
|
|
18662
|
+
availableEditors.push(editor);
|
|
18663
|
+
}
|
|
18664
|
+
}
|
|
18665
|
+
return availableEditors;
|
|
18666
|
+
}
|
|
18667
|
+
async function applyOnboardingResult(result, home) {
|
|
18668
|
+
const rcPath = resolveShellConfigPath(result.shell, home);
|
|
18669
|
+
const completionPath = resolveCompletionPath(result.shell, home);
|
|
18670
|
+
const completionAlreadyInstalled = await fileExists2(completionPath);
|
|
18671
|
+
await applyShellIntegration(result.shell, result.shellIntegration, home);
|
|
18672
|
+
if (result.installCompletion) {
|
|
18673
|
+
await mkdir6(dirname8(completionPath), { recursive: true });
|
|
18674
|
+
await writeFile5(
|
|
18675
|
+
completionPath,
|
|
18676
|
+
renderShellCompletion(result.shell),
|
|
18677
|
+
"utf8"
|
|
18678
|
+
);
|
|
18679
|
+
}
|
|
18680
|
+
if (result.shell === "zsh" && (completionAlreadyInstalled || result.installCompletion)) {
|
|
18681
|
+
await ensureZshCompletionPath(rcPath);
|
|
18682
|
+
}
|
|
18683
|
+
if (result.editor) {
|
|
18684
|
+
await updateGlobalConfigKey("editor", result.editor, home);
|
|
18685
|
+
}
|
|
18686
|
+
}
|
|
18687
|
+
async function installOnboardingShellIntegration(shell, home) {
|
|
18688
|
+
const rcPath = resolveShellConfigPath(shell, home);
|
|
18689
|
+
await mkdir6(dirname8(rcPath), { recursive: true });
|
|
18690
|
+
const current = await readExistingConfig(rcPath);
|
|
18691
|
+
if (shell === "fish") {
|
|
18692
|
+
await writeFile5(
|
|
18693
|
+
rcPath,
|
|
18694
|
+
upsertShellIntegration(current, renderShellIntegration(shell)),
|
|
18695
|
+
"utf8"
|
|
18696
|
+
);
|
|
18697
|
+
return;
|
|
18698
|
+
}
|
|
18699
|
+
await writeFile5(
|
|
18700
|
+
rcPath,
|
|
18701
|
+
upsertOnboardingShellIntegration(current, shell),
|
|
18702
|
+
"utf8"
|
|
18703
|
+
);
|
|
18704
|
+
}
|
|
18705
|
+
function upsertOnboardingShellIntegration(existingConfig, shell) {
|
|
18706
|
+
const script = renderOnboardingShellIntegration(shell).trimEnd();
|
|
18707
|
+
const blockPattern = new RegExp(
|
|
18708
|
+
`${escapeForRegExp(ONBOARDING_SHELL_INTEGRATION_START_MARKER)}[\\s\\S]*?${escapeForRegExp(ONBOARDING_SHELL_INTEGRATION_END_MARKER)}\\n?`,
|
|
18709
|
+
"m"
|
|
18710
|
+
);
|
|
18711
|
+
if (blockPattern.test(existingConfig)) {
|
|
18712
|
+
return ensureTrailingNewline(
|
|
18713
|
+
existingConfig.replace(blockPattern, `${script}
|
|
18714
|
+
`)
|
|
18715
|
+
);
|
|
18716
|
+
}
|
|
18717
|
+
const prefix = existingConfig.trimEnd();
|
|
18718
|
+
if (prefix.length === 0) return ensureTrailingNewline(script);
|
|
18719
|
+
return ensureTrailingNewline(`${prefix}
|
|
18720
|
+
|
|
18721
|
+
${script}`);
|
|
18722
|
+
}
|
|
18723
|
+
function renderOnboardingShellIntegration(shell) {
|
|
18724
|
+
return `${ONBOARDING_SHELL_INTEGRATION_START_MARKER}
|
|
18725
|
+
eval "$(gji init ${shell})"
|
|
18726
|
+
${ONBOARDING_SHELL_INTEGRATION_END_MARKER}
|
|
18727
|
+
`;
|
|
18728
|
+
}
|
|
18729
|
+
async function ensureZshCompletionPath(rcPath) {
|
|
18730
|
+
const current = await readExistingConfig(rcPath);
|
|
18731
|
+
const next = upsertZshCompletionPath(current);
|
|
18732
|
+
if (next !== current) {
|
|
18733
|
+
await writeFile5(rcPath, next, "utf8");
|
|
18734
|
+
}
|
|
18735
|
+
}
|
|
18736
|
+
function upsertZshCompletionPath(existingConfig) {
|
|
18737
|
+
const configWithoutCompletionPath = existingConfig.replace(
|
|
18738
|
+
/# >>> gji zsh completion path >>>[\s\S]*?# <<< gji zsh completion path <<<\n?/m,
|
|
18739
|
+
""
|
|
18740
|
+
).replace(
|
|
18741
|
+
new RegExp(
|
|
18742
|
+
`^[\\t ]*${escapeForRegExp(ZSH_COMPLETION_PATH_LINE)}[\\t ]*\\n?`,
|
|
18743
|
+
"gm"
|
|
18744
|
+
),
|
|
18745
|
+
""
|
|
18746
|
+
);
|
|
18747
|
+
const suffix = configWithoutCompletionPath.replace(/^(?:\r?\n)+/, "").trimEnd();
|
|
18748
|
+
return ensureTrailingNewline(
|
|
18749
|
+
suffix.length === 0 ? ZSH_COMPLETION_PATH_LINE : `${ZSH_COMPLETION_PATH_LINE}
|
|
18750
|
+
|
|
18751
|
+
${suffix}`
|
|
18752
|
+
);
|
|
18753
|
+
}
|
|
18754
|
+
async function fileExists2(path9) {
|
|
18755
|
+
try {
|
|
18756
|
+
await access7(path9, constants5.F_OK);
|
|
18757
|
+
return true;
|
|
18758
|
+
} catch {
|
|
18759
|
+
return false;
|
|
18760
|
+
}
|
|
18761
|
+
}
|
|
18762
|
+
async function runLegacyInitCommand(options) {
|
|
17069
18763
|
const shell = resolveSupportedShell(options.shell, process.env.SHELL);
|
|
17070
|
-
const home = options.home ??
|
|
18764
|
+
const home = options.home ?? homedir6();
|
|
17071
18765
|
if (!shell) {
|
|
17072
18766
|
options.stderr?.(
|
|
17073
18767
|
"Unable to detect a supported shell. Specify one explicitly: bash, fish, or zsh.\n"
|
|
@@ -17080,7 +18774,7 @@ async function runInitCommand(options) {
|
|
|
17080
18774
|
return 0;
|
|
17081
18775
|
}
|
|
17082
18776
|
const rcPath = resolveShellConfigPath(shell, home);
|
|
17083
|
-
await mkdir6(
|
|
18777
|
+
await mkdir6(dirname8(rcPath), { recursive: true });
|
|
17084
18778
|
const current = await readExistingConfig(rcPath);
|
|
17085
18779
|
const next = upsertShellIntegration(current, script);
|
|
17086
18780
|
await writeFile5(rcPath, next, "utf8");
|
|
@@ -17089,7 +18783,7 @@ async function runInitCommand(options) {
|
|
|
17089
18783
|
const { config: globalConfig } = await loadGlobalConfig(home);
|
|
17090
18784
|
const alreadyConfigured = "shellIntegration" in globalConfig || "installSaveTarget" in globalConfig;
|
|
17091
18785
|
const hasCustomPrompt = options.promptForSetup !== void 0;
|
|
17092
|
-
const canPrompt = hasCustomPrompt || process.stdout.isTTY === true;
|
|
18786
|
+
const canPrompt = !isHeadless() && (hasCustomPrompt || process.stdout.isTTY === true);
|
|
17093
18787
|
if (!alreadyConfigured && canPrompt) {
|
|
17094
18788
|
const prompt = options.promptForSetup ?? defaultPromptForSetup;
|
|
17095
18789
|
const result = await prompt();
|
|
@@ -17111,30 +18805,30 @@ function renderShellIntegration(shell) {
|
|
|
17111
18805
|
).join("\n\n");
|
|
17112
18806
|
switch (shell) {
|
|
17113
18807
|
case "fish":
|
|
17114
|
-
return `${
|
|
18808
|
+
return `${SHELL_INTEGRATION_START_MARKER}
|
|
17115
18809
|
function gji --wraps gji --description 'gji shell integration'
|
|
17116
18810
|
${indentBlock(commandBlocks, 4)}
|
|
17117
18811
|
|
|
17118
18812
|
command gji $argv
|
|
17119
18813
|
end
|
|
17120
|
-
${
|
|
18814
|
+
${SHELL_INTEGRATION_END_MARKER}
|
|
17121
18815
|
`;
|
|
17122
18816
|
case "bash":
|
|
17123
18817
|
case "zsh":
|
|
17124
|
-
return `${
|
|
18818
|
+
return `${SHELL_INTEGRATION_START_MARKER}
|
|
17125
18819
|
gji() {
|
|
17126
18820
|
${indentBlock(commandBlocks, 2)}
|
|
17127
18821
|
|
|
17128
18822
|
command gji "$@"
|
|
17129
18823
|
}
|
|
17130
|
-
${
|
|
18824
|
+
${SHELL_INTEGRATION_END_MARKER}
|
|
17131
18825
|
`;
|
|
17132
18826
|
}
|
|
17133
18827
|
}
|
|
17134
18828
|
function upsertShellIntegration(existingConfig, script) {
|
|
17135
18829
|
const trimmedScript = script.trimEnd();
|
|
17136
18830
|
const blockPattern = new RegExp(
|
|
17137
|
-
`${escapeForRegExp(
|
|
18831
|
+
`${escapeForRegExp(SHELL_INTEGRATION_START_MARKER)}[\\s\\S]*?${escapeForRegExp(SHELL_INTEGRATION_END_MARKER)}\\n?`,
|
|
17138
18832
|
"m"
|
|
17139
18833
|
);
|
|
17140
18834
|
if (blockPattern.test(existingConfig)) {
|
|
@@ -17172,21 +18866,11 @@ async function saveWizardConfig(result, cwd, home) {
|
|
|
17172
18866
|
await saveGlobalConfig({ ...existing, ...values }, home);
|
|
17173
18867
|
}
|
|
17174
18868
|
}
|
|
17175
|
-
function resolveShellConfigPath(shell, home) {
|
|
17176
|
-
switch (shell) {
|
|
17177
|
-
case "bash":
|
|
17178
|
-
return join7(home, ".bashrc");
|
|
17179
|
-
case "fish":
|
|
17180
|
-
return join7(home, ".config", "fish", "config.fish");
|
|
17181
|
-
case "zsh":
|
|
17182
|
-
return join7(home, ".zshrc");
|
|
17183
|
-
}
|
|
17184
|
-
}
|
|
17185
18869
|
async function readExistingConfig(path9) {
|
|
17186
18870
|
try {
|
|
17187
|
-
return await
|
|
18871
|
+
return await readFile5(path9, "utf8");
|
|
17188
18872
|
} catch (error) {
|
|
17189
|
-
if (
|
|
18873
|
+
if (isMissingFileError3(error)) {
|
|
17190
18874
|
return "";
|
|
17191
18875
|
}
|
|
17192
18876
|
throw error;
|
|
@@ -17199,7 +18883,7 @@ function ensureTrailingNewline(value) {
|
|
|
17199
18883
|
function escapeForRegExp(value) {
|
|
17200
18884
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17201
18885
|
}
|
|
17202
|
-
function
|
|
18886
|
+
function isMissingFileError3(error) {
|
|
17203
18887
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
17204
18888
|
}
|
|
17205
18889
|
function renderFishWrapper(command) {
|
|
@@ -17424,14 +19108,14 @@ function sortWorktrees(worktrees) {
|
|
|
17424
19108
|
}
|
|
17425
19109
|
|
|
17426
19110
|
// src/open.ts
|
|
17427
|
-
import { execFile as
|
|
17428
|
-
import { access as
|
|
17429
|
-
import { join as
|
|
17430
|
-
import { promisify as
|
|
17431
|
-
var
|
|
19111
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
19112
|
+
import { access as access8, writeFile as writeFile6 } from "node:fs/promises";
|
|
19113
|
+
import { join as join9 } from "node:path";
|
|
19114
|
+
import { promisify as promisify7 } from "node:util";
|
|
19115
|
+
var execFileAsync6 = promisify7(execFile6);
|
|
17432
19116
|
function createOpenCommand(dependencies = {}) {
|
|
17433
19117
|
const detectEditors = dependencies.detectEditors ?? detectInstalledEditors;
|
|
17434
|
-
const
|
|
19118
|
+
const promptForEditor2 = dependencies.promptForEditor ?? defaultPromptForEditor;
|
|
17435
19119
|
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree;
|
|
17436
19120
|
const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
|
|
17437
19121
|
return async function runOpenCommand2(options) {
|
|
@@ -17466,9 +19150,11 @@ function createOpenCommand(dependencies = {}) {
|
|
|
17466
19150
|
} else {
|
|
17467
19151
|
const entries = await buildWorktreePromptEntries(
|
|
17468
19152
|
worktrees.map((worktree) => ({
|
|
19153
|
+
repoRoot: repository.repoRoot,
|
|
17469
19154
|
repoName: repository.repoName,
|
|
17470
19155
|
worktree
|
|
17471
|
-
}))
|
|
19156
|
+
})),
|
|
19157
|
+
{ queryPullRequests: dependencies.queryPullRequests }
|
|
17472
19158
|
);
|
|
17473
19159
|
const chosen = await promptForWorktree2(entries);
|
|
17474
19160
|
if (!chosen) {
|
|
@@ -17500,7 +19186,7 @@ function createOpenCommand(dependencies = {}) {
|
|
|
17500
19186
|
if (installed.length === 1 || isHeadless()) {
|
|
17501
19187
|
editorCli = installed[0].cli;
|
|
17502
19188
|
} else {
|
|
17503
|
-
const chosen = await
|
|
19189
|
+
const chosen = await promptForEditor2(installed);
|
|
17504
19190
|
if (!chosen) {
|
|
17505
19191
|
options.stderr("Aborted\n");
|
|
17506
19192
|
return 1;
|
|
@@ -17559,7 +19245,7 @@ async function detectInstalledEditors() {
|
|
|
17559
19245
|
}
|
|
17560
19246
|
async function isCommandAvailable(command) {
|
|
17561
19247
|
try {
|
|
17562
|
-
await
|
|
19248
|
+
await execFileAsync6("which", [command]);
|
|
17563
19249
|
return true;
|
|
17564
19250
|
} catch {
|
|
17565
19251
|
return false;
|
|
@@ -17577,9 +19263,9 @@ async function defaultPromptForEditor(editors) {
|
|
|
17577
19263
|
return choice;
|
|
17578
19264
|
}
|
|
17579
19265
|
async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
17580
|
-
const workspacePath =
|
|
19266
|
+
const workspacePath = join9(worktreePath, `${repoName}.code-workspace`);
|
|
17581
19267
|
try {
|
|
17582
|
-
await
|
|
19268
|
+
await access8(workspacePath);
|
|
17583
19269
|
return workspacePath;
|
|
17584
19270
|
} catch {
|
|
17585
19271
|
}
|
|
@@ -17594,11 +19280,11 @@ async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
|
17594
19280
|
}
|
|
17595
19281
|
|
|
17596
19282
|
// src/pr.ts
|
|
17597
|
-
import { execFile as
|
|
19283
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
17598
19284
|
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
17599
|
-
import { basename as
|
|
17600
|
-
import { promisify as
|
|
17601
|
-
var
|
|
19285
|
+
import { basename as basename8, dirname as dirname9 } from "node:path";
|
|
19286
|
+
import { promisify as promisify8 } from "node:util";
|
|
19287
|
+
var execFileAsync7 = promisify8(execFile7);
|
|
17602
19288
|
var PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
|
|
17603
19289
|
function parsePrInput(input) {
|
|
17604
19290
|
if (/^\d+$/.test(input)) return input;
|
|
@@ -17640,7 +19326,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
17640
19326
|
branchName,
|
|
17641
19327
|
configuredBasePath
|
|
17642
19328
|
);
|
|
17643
|
-
if (await
|
|
19329
|
+
if (await pathExists2(worktreePath)) {
|
|
17644
19330
|
if (options.json || isHeadless()) {
|
|
17645
19331
|
const message = `target worktree path already exists: ${worktreePath}`;
|
|
17646
19332
|
if (options.json) {
|
|
@@ -17706,13 +19392,13 @@ function createPrCommand(dependencies = {}) {
|
|
|
17706
19392
|
}
|
|
17707
19393
|
return 1;
|
|
17708
19394
|
}
|
|
17709
|
-
await mkdir7(
|
|
19395
|
+
await mkdir7(dirname9(worktreePath), { recursive: true });
|
|
17710
19396
|
const branchAlreadyExists = await localBranchExists2(
|
|
17711
19397
|
repository.repoRoot,
|
|
17712
19398
|
branchName
|
|
17713
19399
|
);
|
|
17714
19400
|
const worktreeArgs = branchAlreadyExists ? ["worktree", "add", worktreePath, branchName] : ["worktree", "add", "-b", branchName, worktreePath, remoteRef];
|
|
17715
|
-
await
|
|
19401
|
+
await execFileAsync7("git", worktreeArgs, { cwd: repository.repoRoot });
|
|
17716
19402
|
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
|
|
17717
19403
|
(p2) => typeof p2 === "string"
|
|
17718
19404
|
) : [];
|
|
@@ -17741,7 +19427,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
17741
19427
|
{
|
|
17742
19428
|
branch: branchName,
|
|
17743
19429
|
path: worktreePath,
|
|
17744
|
-
repo:
|
|
19430
|
+
repo: basename8(repository.repoRoot)
|
|
17745
19431
|
},
|
|
17746
19432
|
options.stderr
|
|
17747
19433
|
);
|
|
@@ -17759,7 +19445,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
17759
19445
|
}
|
|
17760
19446
|
async function localBranchExists2(repoRoot, branchName) {
|
|
17761
19447
|
try {
|
|
17762
|
-
await
|
|
19448
|
+
await execFileAsync7(
|
|
17763
19449
|
"git",
|
|
17764
19450
|
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
17765
19451
|
{ cwd: repoRoot }
|
|
@@ -17773,7 +19459,7 @@ var runPrCommand = createPrCommand();
|
|
|
17773
19459
|
async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef) {
|
|
17774
19460
|
for (const sourceRef of listPullRequestSourceRefs(input, prNumber)) {
|
|
17775
19461
|
try {
|
|
17776
|
-
await
|
|
19462
|
+
await execFileAsync7(
|
|
17777
19463
|
"git",
|
|
17778
19464
|
["fetch", "origin", `${sourceRef}:${remoteRef}`],
|
|
17779
19465
|
{ cwd: repoRoot }
|
|
@@ -17823,11 +19509,415 @@ async function writeOutput2(worktreePath, stdout2) {
|
|
|
17823
19509
|
await writeShellOutput(PR_OUTPUT_FILE_ENV, worktreePath, stdout2);
|
|
17824
19510
|
}
|
|
17825
19511
|
|
|
19512
|
+
// src/browser.ts
|
|
19513
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
19514
|
+
import { platform as platform2 } from "node:process";
|
|
19515
|
+
import { promisify as promisify9 } from "node:util";
|
|
19516
|
+
var execFileAsync8 = promisify9(execFile8);
|
|
19517
|
+
function createBrowserOpener(dependencies = {}) {
|
|
19518
|
+
const currentPlatform = dependencies.platform ?? platform2;
|
|
19519
|
+
const runCommand = dependencies.runCommand ?? defaultRunCommand2;
|
|
19520
|
+
return async function openBrowser2(url) {
|
|
19521
|
+
validateBrowserUrl(url);
|
|
19522
|
+
const command = browserCommand(currentPlatform, url);
|
|
19523
|
+
await runCommand(command.command, command.args);
|
|
19524
|
+
};
|
|
19525
|
+
}
|
|
19526
|
+
var openBrowser = createBrowserOpener();
|
|
19527
|
+
function browserCommand(currentPlatform, url) {
|
|
19528
|
+
if (currentPlatform === "darwin") return { args: [url], command: "open" };
|
|
19529
|
+
if (currentPlatform === "win32") {
|
|
19530
|
+
return { args: [url], command: "explorer.exe" };
|
|
19531
|
+
}
|
|
19532
|
+
return { args: [url], command: "xdg-open" };
|
|
19533
|
+
}
|
|
19534
|
+
function validateBrowserUrl(url) {
|
|
19535
|
+
let parsed;
|
|
19536
|
+
try {
|
|
19537
|
+
parsed = new URL(url);
|
|
19538
|
+
} catch {
|
|
19539
|
+
throw new Error("browser URL is invalid");
|
|
19540
|
+
}
|
|
19541
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
19542
|
+
throw new Error("browser URL must use http or https");
|
|
19543
|
+
}
|
|
19544
|
+
}
|
|
19545
|
+
async function defaultRunCommand2(command, args) {
|
|
19546
|
+
await execFileAsync8(command, args, { timeout: 1e4 });
|
|
19547
|
+
}
|
|
19548
|
+
|
|
19549
|
+
// src/pr-open.ts
|
|
19550
|
+
function createPrOpenCommand(dependencies = {}) {
|
|
19551
|
+
const query = createPullRequestQuery();
|
|
19552
|
+
const findOpenPullRequest2 = dependencies.findOpenPullRequest ?? query.findOpenPullRequest;
|
|
19553
|
+
const openInBrowser = dependencies.openBrowser ?? openBrowser;
|
|
19554
|
+
const promptForPullRequest = dependencies.promptForPullRequest ?? defaultPromptForPullRequest;
|
|
19555
|
+
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree2;
|
|
19556
|
+
const queryPullRequests = dependencies.queryPullRequests ?? query.listOpenPullRequests;
|
|
19557
|
+
const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ?? (dependencies.queryPullRequests === void 0 ? query.listOpenPullRequestsForRepository : void 0);
|
|
19558
|
+
return async function runPrOpenCommand2(options) {
|
|
19559
|
+
if (options.select && options.target !== void 0) {
|
|
19560
|
+
options.stderr("gji pr open: --select cannot be used with a target\n");
|
|
19561
|
+
return 1;
|
|
19562
|
+
}
|
|
19563
|
+
if (options.select && isHeadless()) {
|
|
19564
|
+
options.stderr(
|
|
19565
|
+
"gji pr open --select: selector is unavailable in non-interactive mode (GJI_NO_TUI=1)\n"
|
|
19566
|
+
);
|
|
19567
|
+
return 1;
|
|
19568
|
+
}
|
|
19569
|
+
const repository = await detectRepository(options.cwd).catch((error) => {
|
|
19570
|
+
options.stderr(
|
|
19571
|
+
`gji pr open: unable to detect the current repository: ${formatError(error)}
|
|
19572
|
+
`
|
|
19573
|
+
);
|
|
19574
|
+
return null;
|
|
19575
|
+
});
|
|
19576
|
+
if (repository === null) return 1;
|
|
19577
|
+
if (options.target === void 0) {
|
|
19578
|
+
if (!options.select) {
|
|
19579
|
+
return openCurrentWorktree(
|
|
19580
|
+
repository.repoRoot,
|
|
19581
|
+
options,
|
|
19582
|
+
queryPullRequests,
|
|
19583
|
+
promptForPullRequest,
|
|
19584
|
+
openInBrowser
|
|
19585
|
+
);
|
|
19586
|
+
}
|
|
19587
|
+
return openFromWorktreeSelector(
|
|
19588
|
+
repository.repoRoot,
|
|
19589
|
+
repository.repoName,
|
|
19590
|
+
options,
|
|
19591
|
+
promptForWorktree2,
|
|
19592
|
+
promptForPullRequest,
|
|
19593
|
+
queryPullRequests,
|
|
19594
|
+
queryRepositoryPullRequests,
|
|
19595
|
+
openInBrowser
|
|
19596
|
+
);
|
|
19597
|
+
}
|
|
19598
|
+
const number = parsePrNumberTarget(options.target);
|
|
19599
|
+
if (number !== null) {
|
|
19600
|
+
return openByNumber(
|
|
19601
|
+
repository.repoRoot,
|
|
19602
|
+
number,
|
|
19603
|
+
options,
|
|
19604
|
+
findOpenPullRequest2,
|
|
19605
|
+
openInBrowser
|
|
19606
|
+
);
|
|
19607
|
+
}
|
|
19608
|
+
const worktrees = await listWorktrees(options.cwd).catch((error) => {
|
|
19609
|
+
options.stderr(
|
|
19610
|
+
`gji pr open: unable to list worktrees: ${formatError(error)}
|
|
19611
|
+
`
|
|
19612
|
+
);
|
|
19613
|
+
return null;
|
|
19614
|
+
});
|
|
19615
|
+
if (worktrees === null) return 1;
|
|
19616
|
+
const match = resolveWorktreeQuery(
|
|
19617
|
+
worktrees.map((worktree) => ({
|
|
19618
|
+
repoRoot: repository.repoRoot,
|
|
19619
|
+
repoName: repository.repoName,
|
|
19620
|
+
worktree
|
|
19621
|
+
})),
|
|
19622
|
+
options.target
|
|
19623
|
+
);
|
|
19624
|
+
if (match === null) {
|
|
19625
|
+
options.stderr(
|
|
19626
|
+
`gji pr open: no worktree found matching: ${options.target}
|
|
19627
|
+
`
|
|
19628
|
+
);
|
|
19629
|
+
return 1;
|
|
19630
|
+
}
|
|
19631
|
+
return openForBranch(
|
|
19632
|
+
repository.repoRoot,
|
|
19633
|
+
match.worktree.branch,
|
|
19634
|
+
options,
|
|
19635
|
+
queryPullRequests,
|
|
19636
|
+
promptForPullRequest,
|
|
19637
|
+
openInBrowser
|
|
19638
|
+
);
|
|
19639
|
+
};
|
|
19640
|
+
}
|
|
19641
|
+
var runPrOpenCommand = createPrOpenCommand();
|
|
19642
|
+
async function openCurrentWorktree(repoRoot, options, queryPullRequests, promptForPullRequest, openInBrowser) {
|
|
19643
|
+
const worktrees = await listWorktrees(options.cwd).catch((error) => {
|
|
19644
|
+
options.stderr(
|
|
19645
|
+
`gji pr open: unable to list worktrees: ${formatError(error)}
|
|
19646
|
+
`
|
|
19647
|
+
);
|
|
19648
|
+
return null;
|
|
19649
|
+
});
|
|
19650
|
+
if (worktrees === null) return 1;
|
|
19651
|
+
const currentWorktree = worktrees.find((worktree) => worktree.isCurrent);
|
|
19652
|
+
if (currentWorktree === void 0) {
|
|
19653
|
+
options.stderr("gji pr open: unable to identify the current worktree\n");
|
|
19654
|
+
return 1;
|
|
19655
|
+
}
|
|
19656
|
+
if (currentWorktree.branch === null) {
|
|
19657
|
+
options.stderr(
|
|
19658
|
+
"gji pr open: current worktree is detached and has no PR branch\n"
|
|
19659
|
+
);
|
|
19660
|
+
return 1;
|
|
19661
|
+
}
|
|
19662
|
+
let pullRequests;
|
|
19663
|
+
try {
|
|
19664
|
+
pullRequests = await queryPullRequests(repoRoot, currentWorktree.branch);
|
|
19665
|
+
} catch (error) {
|
|
19666
|
+
options.stderr(
|
|
19667
|
+
`gji pr open: failed to look up PRs for current branch ${currentWorktree.branch}: ${formatError(error)}
|
|
19668
|
+
`
|
|
19669
|
+
);
|
|
19670
|
+
return 1;
|
|
19671
|
+
}
|
|
19672
|
+
if (pullRequests.length === 0) {
|
|
19673
|
+
options.stderr(
|
|
19674
|
+
isHeadless() ? "gji pr open: no open PR found for the current worktree; pass an explicit branch or PR number (for example, gji pr open '#123')\n" : "gji pr open: no open PR found for the current worktree; use gji pr open --select to choose another\n"
|
|
19675
|
+
);
|
|
19676
|
+
return 1;
|
|
19677
|
+
}
|
|
19678
|
+
return openPullRequests(
|
|
19679
|
+
pullRequests,
|
|
19680
|
+
options,
|
|
19681
|
+
promptForPullRequest,
|
|
19682
|
+
openInBrowser
|
|
19683
|
+
);
|
|
19684
|
+
}
|
|
19685
|
+
async function openFromWorktreeSelector(repoRoot, repoName, options, promptForWorktree2, promptForPullRequest, queryPullRequests, queryRepositoryPullRequests, openInBrowser) {
|
|
19686
|
+
const worktrees = await listWorktrees(options.cwd).catch((error) => {
|
|
19687
|
+
options.stderr(
|
|
19688
|
+
`gji pr open: unable to list worktrees: ${formatError(error)}
|
|
19689
|
+
`
|
|
19690
|
+
);
|
|
19691
|
+
return null;
|
|
19692
|
+
});
|
|
19693
|
+
if (worktrees === null) return 1;
|
|
19694
|
+
let lookupError = null;
|
|
19695
|
+
const queryForSelector = async (root, branch) => {
|
|
19696
|
+
try {
|
|
19697
|
+
return await queryPullRequests(root, branch);
|
|
19698
|
+
} catch (error) {
|
|
19699
|
+
lookupError ??= error;
|
|
19700
|
+
throw error;
|
|
19701
|
+
}
|
|
19702
|
+
};
|
|
19703
|
+
const queryRepositoryForSelector = queryRepositoryPullRequests === void 0 ? void 0 : async (root) => {
|
|
19704
|
+
try {
|
|
19705
|
+
return await queryRepositoryPullRequests(root);
|
|
19706
|
+
} catch (error) {
|
|
19707
|
+
lookupError ??= error;
|
|
19708
|
+
throw error;
|
|
19709
|
+
}
|
|
19710
|
+
};
|
|
19711
|
+
let connectedWorktrees;
|
|
19712
|
+
try {
|
|
19713
|
+
connectedWorktrees = await readConnectedWorktreeSources(
|
|
19714
|
+
repoRoot,
|
|
19715
|
+
repoName,
|
|
19716
|
+
worktrees,
|
|
19717
|
+
queryForSelector,
|
|
19718
|
+
queryRepositoryForSelector
|
|
19719
|
+
);
|
|
19720
|
+
} catch {
|
|
19721
|
+
connectedWorktrees = { pullRequestsByBranch: /* @__PURE__ */ new Map(), sources: [] };
|
|
19722
|
+
}
|
|
19723
|
+
const { pullRequestsByBranch, sources } = connectedWorktrees;
|
|
19724
|
+
const entries = await buildWorktreePromptEntries(sources, {
|
|
19725
|
+
queryPullRequests: async (_root, branch) => pullRequestsByBranch.get(branch) ?? []
|
|
19726
|
+
});
|
|
19727
|
+
const connectedEntries = entries.filter(
|
|
19728
|
+
(entry) => (entry.pullRequestNumbers?.length ?? 0) > 0
|
|
19729
|
+
);
|
|
19730
|
+
if (connectedEntries.length === 0) {
|
|
19731
|
+
if (lookupError !== null) {
|
|
19732
|
+
options.stderr(
|
|
19733
|
+
`gji pr open: failed to look up open PRs: ${formatError(lookupError)}
|
|
19734
|
+
`
|
|
19735
|
+
);
|
|
19736
|
+
return 1;
|
|
19737
|
+
}
|
|
19738
|
+
options.stderr("gji pr open: no open PR worktrees found\n");
|
|
19739
|
+
return 1;
|
|
19740
|
+
}
|
|
19741
|
+
const selectedPath = await promptForWorktree2(connectedEntries);
|
|
19742
|
+
if (selectedPath === null) {
|
|
19743
|
+
options.stderr("Aborted\n");
|
|
19744
|
+
return 1;
|
|
19745
|
+
}
|
|
19746
|
+
const selectedEntry = connectedEntries.find(
|
|
19747
|
+
(entry) => entry.path === selectedPath
|
|
19748
|
+
);
|
|
19749
|
+
if (selectedEntry === void 0) {
|
|
19750
|
+
options.stderr("gji pr open: selected worktree is no longer available\n");
|
|
19751
|
+
return 1;
|
|
19752
|
+
}
|
|
19753
|
+
return openSelectedEntry(
|
|
19754
|
+
selectedEntry,
|
|
19755
|
+
options,
|
|
19756
|
+
promptForPullRequest,
|
|
19757
|
+
openInBrowser
|
|
19758
|
+
);
|
|
19759
|
+
}
|
|
19760
|
+
async function readConnectedWorktreeSources(repoRoot, repoName, worktrees, queryPullRequests, queryRepositoryPullRequests) {
|
|
19761
|
+
const pullRequestsByBranch = /* @__PURE__ */ new Map();
|
|
19762
|
+
if (queryRepositoryPullRequests !== void 0) {
|
|
19763
|
+
const pullRequests = await queryRepositoryPullRequests(repoRoot);
|
|
19764
|
+
for (const worktree of worktrees) {
|
|
19765
|
+
if (worktree.branch === null) continue;
|
|
19766
|
+
const branchPullRequests = pullRequests.filter(
|
|
19767
|
+
(pullRequest) => pullRequest.sourceBranch === worktree.branch
|
|
19768
|
+
);
|
|
19769
|
+
if (branchPullRequests.length > 0) {
|
|
19770
|
+
pullRequestsByBranch.set(worktree.branch, branchPullRequests);
|
|
19771
|
+
}
|
|
19772
|
+
}
|
|
19773
|
+
} else {
|
|
19774
|
+
const branchResults = await Promise.all(
|
|
19775
|
+
worktrees.map(async (worktree) => {
|
|
19776
|
+
if (worktree.branch === null) {
|
|
19777
|
+
return { pullRequests: [], worktree };
|
|
19778
|
+
}
|
|
19779
|
+
try {
|
|
19780
|
+
return {
|
|
19781
|
+
pullRequests: await queryPullRequests(repoRoot, worktree.branch),
|
|
19782
|
+
worktree
|
|
19783
|
+
};
|
|
19784
|
+
} catch {
|
|
19785
|
+
return { pullRequests: [], worktree };
|
|
19786
|
+
}
|
|
19787
|
+
})
|
|
19788
|
+
);
|
|
19789
|
+
for (const { pullRequests, worktree } of branchResults) {
|
|
19790
|
+
if (worktree.branch !== null && pullRequests.length > 0) {
|
|
19791
|
+
pullRequestsByBranch.set(worktree.branch, pullRequests);
|
|
19792
|
+
}
|
|
19793
|
+
}
|
|
19794
|
+
}
|
|
19795
|
+
const sources = worktrees.flatMap((worktree) => {
|
|
19796
|
+
if (worktree.branch === null || !pullRequestsByBranch.has(worktree.branch)) {
|
|
19797
|
+
return [];
|
|
19798
|
+
}
|
|
19799
|
+
return [{ repoRoot, repoName, worktree }];
|
|
19800
|
+
});
|
|
19801
|
+
return { pullRequestsByBranch, sources };
|
|
19802
|
+
}
|
|
19803
|
+
async function openByNumber(repoRoot, number, options, findOpenPullRequest2, openInBrowser) {
|
|
19804
|
+
let pullRequest;
|
|
19805
|
+
try {
|
|
19806
|
+
pullRequest = await findOpenPullRequest2(repoRoot, number);
|
|
19807
|
+
} catch (error) {
|
|
19808
|
+
options.stderr(
|
|
19809
|
+
`gji pr open: failed to look up PR #${number}: ${formatError(error)}
|
|
19810
|
+
`
|
|
19811
|
+
);
|
|
19812
|
+
return 1;
|
|
19813
|
+
}
|
|
19814
|
+
if (pullRequest === null) {
|
|
19815
|
+
options.stderr(`gji pr open: no open PR found for #${number}
|
|
19816
|
+
`);
|
|
19817
|
+
return 1;
|
|
19818
|
+
}
|
|
19819
|
+
return launchBrowser(pullRequest, options, openInBrowser);
|
|
19820
|
+
}
|
|
19821
|
+
async function openForBranch(repoRoot, branch, options, queryPullRequests, promptForPullRequest, openInBrowser) {
|
|
19822
|
+
if (branch === null) {
|
|
19823
|
+
options.stderr("gji pr open: detached worktrees have no PR branch\n");
|
|
19824
|
+
return 1;
|
|
19825
|
+
}
|
|
19826
|
+
let pullRequests;
|
|
19827
|
+
try {
|
|
19828
|
+
pullRequests = await queryPullRequests(repoRoot, branch);
|
|
19829
|
+
} catch (error) {
|
|
19830
|
+
options.stderr(
|
|
19831
|
+
`gji pr open: failed to look up PRs for branch ${branch}: ${formatError(error)}
|
|
19832
|
+
`
|
|
19833
|
+
);
|
|
19834
|
+
return 1;
|
|
19835
|
+
}
|
|
19836
|
+
return openPullRequests(
|
|
19837
|
+
pullRequests,
|
|
19838
|
+
options,
|
|
19839
|
+
promptForPullRequest,
|
|
19840
|
+
openInBrowser
|
|
19841
|
+
);
|
|
19842
|
+
}
|
|
19843
|
+
async function openSelectedEntry(entry, options, promptForPullRequest, openInBrowser) {
|
|
19844
|
+
const numbers = entry.pullRequestNumbers ?? [];
|
|
19845
|
+
const urls = entry.pullRequestUrls ?? [];
|
|
19846
|
+
const pullRequests = numbers.map((number, index) => ({
|
|
19847
|
+
number,
|
|
19848
|
+
sourceBranch: entry.branch ?? "",
|
|
19849
|
+
url: urls[index] ?? ""
|
|
19850
|
+
})).filter((pullRequest) => pullRequest.url.length > 0);
|
|
19851
|
+
return openPullRequests(
|
|
19852
|
+
pullRequests,
|
|
19853
|
+
options,
|
|
19854
|
+
promptForPullRequest,
|
|
19855
|
+
openInBrowser
|
|
19856
|
+
);
|
|
19857
|
+
}
|
|
19858
|
+
async function openPullRequests(pullRequests, options, promptForPullRequest, openInBrowser) {
|
|
19859
|
+
if (pullRequests.length === 0) {
|
|
19860
|
+
options.stderr("gji pr open: no open PR found for the selected branch\n");
|
|
19861
|
+
return 1;
|
|
19862
|
+
}
|
|
19863
|
+
if (pullRequests.length > 1 && isHeadless()) {
|
|
19864
|
+
options.stderr(
|
|
19865
|
+
"gji pr open: multiple open PRs found; pass an explicit PR number in non-interactive mode (GJI_NO_TUI=1)\n"
|
|
19866
|
+
);
|
|
19867
|
+
return 1;
|
|
19868
|
+
}
|
|
19869
|
+
const selected = pullRequests.length === 1 ? pullRequests[0] : await promptForPullRequest(pullRequests);
|
|
19870
|
+
if (selected === null || selected === void 0) {
|
|
19871
|
+
options.stderr("Aborted\n");
|
|
19872
|
+
return 1;
|
|
19873
|
+
}
|
|
19874
|
+
return launchBrowser(selected, options, openInBrowser);
|
|
19875
|
+
}
|
|
19876
|
+
async function launchBrowser(pullRequest, options, openInBrowser) {
|
|
19877
|
+
try {
|
|
19878
|
+
await openInBrowser(pullRequest.url);
|
|
19879
|
+
} catch (error) {
|
|
19880
|
+
options.stderr(
|
|
19881
|
+
`gji pr open: failed to open PR #${pullRequest.number} in the browser: ${formatError(error)}
|
|
19882
|
+
`
|
|
19883
|
+
);
|
|
19884
|
+
return 1;
|
|
19885
|
+
}
|
|
19886
|
+
options.stdout(`Opened PR #${pullRequest.number}: ${pullRequest.url}
|
|
19887
|
+
`);
|
|
19888
|
+
return 0;
|
|
19889
|
+
}
|
|
19890
|
+
function parsePrNumberTarget(target) {
|
|
19891
|
+
const match = target.match(/^#?(\d+)$/);
|
|
19892
|
+
return match === null ? null : Number(match[1]);
|
|
19893
|
+
}
|
|
19894
|
+
async function defaultPromptForWorktree2(worktrees) {
|
|
19895
|
+
return promptForSingleWorktree(
|
|
19896
|
+
"Choose a worktree with an open PR",
|
|
19897
|
+
worktrees
|
|
19898
|
+
);
|
|
19899
|
+
}
|
|
19900
|
+
async function defaultPromptForPullRequest(pullRequests) {
|
|
19901
|
+
const choice = await ve({
|
|
19902
|
+
message: "Choose a pull request",
|
|
19903
|
+
options: pullRequests.map((pullRequest) => ({
|
|
19904
|
+
value: pullRequest.number,
|
|
19905
|
+
label: `#${pullRequest.number}`,
|
|
19906
|
+
hint: pullRequest.url
|
|
19907
|
+
}))
|
|
19908
|
+
});
|
|
19909
|
+
if (pD(choice)) return null;
|
|
19910
|
+
return pullRequests.find((pullRequest) => pullRequest.number === choice) ?? null;
|
|
19911
|
+
}
|
|
19912
|
+
function formatError(error) {
|
|
19913
|
+
return error instanceof Error ? error.message : String(error);
|
|
19914
|
+
}
|
|
19915
|
+
|
|
17826
19916
|
// src/remove.ts
|
|
17827
|
-
import { basename as
|
|
19917
|
+
import { basename as basename9 } from "node:path";
|
|
17828
19918
|
var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
|
|
17829
19919
|
function createRemoveCommand(dependencies = {}) {
|
|
17830
|
-
const promptForWorktree2 = dependencies.promptForWorktree ??
|
|
19920
|
+
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree3;
|
|
17831
19921
|
const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval2;
|
|
17832
19922
|
const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ?? defaultConfirmForceRemoveWorktree;
|
|
17833
19923
|
const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
|
|
@@ -17911,14 +20001,14 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
17911
20001
|
{
|
|
17912
20002
|
branch: worktree.branch ?? void 0,
|
|
17913
20003
|
path: worktree.path,
|
|
17914
|
-
repo:
|
|
20004
|
+
repo: basename9(repository.repoRoot)
|
|
17915
20005
|
},
|
|
17916
20006
|
options.stderr
|
|
17917
20007
|
);
|
|
17918
20008
|
try {
|
|
17919
20009
|
await removeWorktree(repository.repoRoot, worktree.path);
|
|
17920
20010
|
} catch (error) {
|
|
17921
|
-
if (!
|
|
20011
|
+
if (!isWorktreeForceRemovalError(error)) {
|
|
17922
20012
|
throw error;
|
|
17923
20013
|
}
|
|
17924
20014
|
if (!options.force && !await confirmForceRemoveWorktree(worktree.path)) {
|
|
@@ -17971,7 +20061,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
17971
20061
|
};
|
|
17972
20062
|
}
|
|
17973
20063
|
var runRemoveCommand = createRemoveCommand();
|
|
17974
|
-
async function
|
|
20064
|
+
async function defaultPromptForWorktree3(worktrees) {
|
|
17975
20065
|
return promptForSingleWorktree("Choose a worktree to finish", worktrees);
|
|
17976
20066
|
}
|
|
17977
20067
|
async function defaultConfirmRemoval2(worktree) {
|
|
@@ -18296,11 +20386,11 @@ function resolveConfiguredString2(value) {
|
|
|
18296
20386
|
}
|
|
18297
20387
|
|
|
18298
20388
|
// src/sync-files-command.ts
|
|
18299
|
-
import { homedir as
|
|
18300
|
-
import { join as
|
|
20389
|
+
import { homedir as homedir7 } from "node:os";
|
|
20390
|
+
import { join as join10 } from "node:path";
|
|
18301
20391
|
async function runSyncFilesCommand(options) {
|
|
18302
20392
|
const repository = await detectRepository(options.cwd);
|
|
18303
|
-
const home = options.home ??
|
|
20393
|
+
const home = options.home ?? homedir7();
|
|
18304
20394
|
const loaded = await loadGlobalConfig(home);
|
|
18305
20395
|
const repoEntry = findRepoConfigEntry(
|
|
18306
20396
|
loaded.config,
|
|
@@ -18346,7 +20436,7 @@ function findRepoConfigEntry(config, repoRoot, home) {
|
|
|
18346
20436
|
const repos = config.repos;
|
|
18347
20437
|
if (!isPlainObject3(repos)) return null;
|
|
18348
20438
|
for (const [key, value] of Object.entries(repos)) {
|
|
18349
|
-
if (
|
|
20439
|
+
if (expandTilde3(key, home) === repoRoot && isPlainObject3(value)) {
|
|
18350
20440
|
return { config: value, key };
|
|
18351
20441
|
}
|
|
18352
20442
|
}
|
|
@@ -18430,9 +20520,9 @@ function writeError(options, message) {
|
|
|
18430
20520
|
options.stderr(`gji sync-files: ${message}
|
|
18431
20521
|
`);
|
|
18432
20522
|
}
|
|
18433
|
-
function
|
|
20523
|
+
function expandTilde3(value, home) {
|
|
18434
20524
|
if (value === "~") return home;
|
|
18435
|
-
if (value.startsWith("~/")) return
|
|
20525
|
+
if (value.startsWith("~/")) return join10(home, value.slice(2));
|
|
18436
20526
|
return value;
|
|
18437
20527
|
}
|
|
18438
20528
|
|
|
@@ -18454,7 +20544,9 @@ function readPackageMetadata() {
|
|
|
18454
20544
|
}
|
|
18455
20545
|
async function runCli(argv, options = {}) {
|
|
18456
20546
|
await maybeNotifyForUpdates(argv);
|
|
18457
|
-
|
|
20547
|
+
if (argv[0] !== "doctor") {
|
|
20548
|
+
maybeRegisterCurrentRepo(options.cwd ?? process.cwd());
|
|
20549
|
+
}
|
|
18458
20550
|
const program2 = createProgram();
|
|
18459
20551
|
const cwd = options.cwd ?? process.cwd();
|
|
18460
20552
|
const stdout2 = options.stdout ?? (() => void 0);
|
|
@@ -18469,7 +20561,12 @@ async function runCli(argv, options = {}) {
|
|
|
18469
20561
|
return { exitCode: 0 };
|
|
18470
20562
|
}
|
|
18471
20563
|
try {
|
|
18472
|
-
attachCommandActions(program2, {
|
|
20564
|
+
attachCommandActions(program2, {
|
|
20565
|
+
cwd,
|
|
20566
|
+
prOpenDependencies: options.prOpenDependencies,
|
|
20567
|
+
stderr,
|
|
20568
|
+
stdout: stdout2
|
|
20569
|
+
});
|
|
18473
20570
|
await program2.parseAsync(["node", "gji", ...argv], { from: "node" });
|
|
18474
20571
|
return { exitCode: 0 };
|
|
18475
20572
|
} catch (error) {
|
|
@@ -18515,9 +20612,12 @@ function registerCommands(program2) {
|
|
|
18515
20612
|
"--json",
|
|
18516
20613
|
"emit JSON on success or error instead of human-readable output"
|
|
18517
20614
|
).action(notImplemented("new"));
|
|
18518
|
-
program2.command("init [shell]").description(
|
|
20615
|
+
program2.command("init [shell]").description(
|
|
20616
|
+
"set up shell integration interactively or print a shell wrapper"
|
|
20617
|
+
).option("--write", "write the integration to the shell config file").option("--json", "emit a JSON error in non-interactive mode").action(notImplemented("init"));
|
|
20618
|
+
program2.command("doctor").description("check gji installation and configuration health").option("--fix", "apply safe automatic fixes after showing the plan").option("--yes", "apply --fix without prompting").option("--json", "emit diagnostic checks as JSON").action(notImplemented("doctor"));
|
|
18519
20619
|
program2.command("completion [shell]").description("print shell completion definitions").action(notImplemented("completion"));
|
|
18520
|
-
program2.command("pr <ref>").description(
|
|
20620
|
+
const prCommand = program2.command("pr <ref>").usage("[options] <ref>").description(
|
|
18521
20621
|
"fetch a pull request by number, #number, or URL into a linked worktree"
|
|
18522
20622
|
).option(
|
|
18523
20623
|
"--dry-run",
|
|
@@ -18526,6 +20626,7 @@ function registerCommands(program2) {
|
|
|
18526
20626
|
"--json",
|
|
18527
20627
|
"emit JSON on success or error instead of human-readable output"
|
|
18528
20628
|
).action(notImplemented("pr"));
|
|
20629
|
+
prCommand.command("open [target]").description("open an existing pull request in the default browser").option("--select", "choose a pull request from any linked worktree").action(notImplemented("pr open"));
|
|
18529
20630
|
program2.command("back [n]").description(
|
|
18530
20631
|
"navigate to the previously visited worktree, optionally N steps back"
|
|
18531
20632
|
).option("--print", "print the resolved worktree path explicitly").action(notImplemented("back"));
|
|
@@ -18583,16 +20684,16 @@ function registerCommands(program2) {
|
|
|
18583
20684
|
}
|
|
18584
20685
|
function attachCommandActions(program2, options) {
|
|
18585
20686
|
program2.commands.find((command) => command.name() === "new")?.action(
|
|
18586
|
-
async (branch,
|
|
20687
|
+
async (branch, commandOptions2) => {
|
|
18587
20688
|
const exitCode = await runNewCommand({
|
|
18588
20689
|
...options,
|
|
18589
20690
|
branch,
|
|
18590
|
-
detached:
|
|
18591
|
-
dryRun:
|
|
18592
|
-
editor:
|
|
18593
|
-
force:
|
|
18594
|
-
json:
|
|
18595
|
-
open:
|
|
20691
|
+
detached: commandOptions2.detached,
|
|
20692
|
+
dryRun: commandOptions2.dryRun,
|
|
20693
|
+
editor: commandOptions2.editor,
|
|
20694
|
+
force: commandOptions2.force,
|
|
20695
|
+
json: commandOptions2.json,
|
|
20696
|
+
open: commandOptions2.open
|
|
18596
20697
|
});
|
|
18597
20698
|
if (exitCode !== 0) {
|
|
18598
20699
|
throw commanderExit(exitCode);
|
|
@@ -18600,13 +20701,29 @@ function attachCommandActions(program2, options) {
|
|
|
18600
20701
|
}
|
|
18601
20702
|
);
|
|
18602
20703
|
program2.commands.find((command) => command.name() === "init")?.action(
|
|
18603
|
-
async (shell,
|
|
20704
|
+
async (shell, commandOptions2) => {
|
|
18604
20705
|
const exitCode = await runInitCommand({
|
|
18605
20706
|
cwd: options.cwd,
|
|
20707
|
+
json: commandOptions2.json,
|
|
18606
20708
|
shell,
|
|
18607
20709
|
stderr: options.stderr,
|
|
18608
20710
|
stdout: options.stdout,
|
|
18609
|
-
write:
|
|
20711
|
+
write: commandOptions2.write
|
|
20712
|
+
});
|
|
20713
|
+
if (exitCode !== 0) {
|
|
20714
|
+
throw commanderExit(exitCode);
|
|
20715
|
+
}
|
|
20716
|
+
}
|
|
20717
|
+
);
|
|
20718
|
+
program2.commands.find((command) => command.name() === "doctor")?.action(
|
|
20719
|
+
async (commandOptions2) => {
|
|
20720
|
+
const exitCode = await runDoctorCommand({
|
|
20721
|
+
cwd: options.cwd,
|
|
20722
|
+
fix: commandOptions2.fix,
|
|
20723
|
+
json: commandOptions2.json,
|
|
20724
|
+
stderr: options.stderr,
|
|
20725
|
+
stdout: options.stdout,
|
|
20726
|
+
yes: commandOptions2.yes
|
|
18610
20727
|
});
|
|
18611
20728
|
if (exitCode !== 0) {
|
|
18612
20729
|
throw commanderExit(exitCode);
|
|
@@ -18624,11 +20741,11 @@ function attachCommandActions(program2, options) {
|
|
|
18624
20741
|
}
|
|
18625
20742
|
});
|
|
18626
20743
|
program2.commands.find((command) => command.name() === "pr")?.action(
|
|
18627
|
-
async (number,
|
|
20744
|
+
async (number, commandOptions2) => {
|
|
18628
20745
|
const exitCode = await runPrCommand({
|
|
18629
20746
|
cwd: options.cwd,
|
|
18630
|
-
dryRun:
|
|
18631
|
-
json:
|
|
20747
|
+
dryRun: commandOptions2.dryRun,
|
|
20748
|
+
json: commandOptions2.json,
|
|
18632
20749
|
number,
|
|
18633
20750
|
stderr: options.stderr,
|
|
18634
20751
|
stdout: options.stdout
|
|
@@ -18638,8 +20755,24 @@ function attachCommandActions(program2, options) {
|
|
|
18638
20755
|
}
|
|
18639
20756
|
}
|
|
18640
20757
|
);
|
|
20758
|
+
const prOpenCommand = program2.commands.find((command) => command.name() === "pr")?.commands.find((command) => command.name() === "open");
|
|
20759
|
+
const runPrOpenCommand2 = createPrOpenCommand(options.prOpenDependencies);
|
|
20760
|
+
prOpenCommand?.action(
|
|
20761
|
+
async (target, commandOptions2) => {
|
|
20762
|
+
const exitCode = await runPrOpenCommand2({
|
|
20763
|
+
cwd: options.cwd,
|
|
20764
|
+
stderr: options.stderr,
|
|
20765
|
+
stdout: options.stdout,
|
|
20766
|
+
select: commandOptions2.select,
|
|
20767
|
+
target
|
|
20768
|
+
});
|
|
20769
|
+
if (exitCode !== 0) {
|
|
20770
|
+
throw commanderExit(exitCode);
|
|
20771
|
+
}
|
|
20772
|
+
}
|
|
20773
|
+
);
|
|
18641
20774
|
program2.commands.find((command) => command.name() === "back")?.action(
|
|
18642
|
-
async (n,
|
|
20775
|
+
async (n, commandOptions2) => {
|
|
18643
20776
|
if (n !== void 0 && !/^\d+$/.test(n)) {
|
|
18644
20777
|
options.stderr(`gji back: invalid step count: ${n}
|
|
18645
20778
|
`);
|
|
@@ -18649,7 +20782,7 @@ function attachCommandActions(program2, options) {
|
|
|
18649
20782
|
const exitCode = await runBackCommand({
|
|
18650
20783
|
cwd: options.cwd,
|
|
18651
20784
|
n: steps,
|
|
18652
|
-
print:
|
|
20785
|
+
print: commandOptions2.print,
|
|
18653
20786
|
stderr: options.stderr,
|
|
18654
20787
|
stdout: options.stdout
|
|
18655
20788
|
});
|
|
@@ -18658,10 +20791,10 @@ function attachCommandActions(program2, options) {
|
|
|
18658
20791
|
}
|
|
18659
20792
|
}
|
|
18660
20793
|
);
|
|
18661
|
-
program2.commands.find((command) => command.name() === "history")?.action(async (
|
|
20794
|
+
program2.commands.find((command) => command.name() === "history")?.action(async (commandOptions2) => {
|
|
18662
20795
|
const exitCode = await runHistoryCommand({
|
|
18663
20796
|
cwd: options.cwd,
|
|
18664
|
-
json:
|
|
20797
|
+
json: commandOptions2.json,
|
|
18665
20798
|
stdout: options.stdout
|
|
18666
20799
|
});
|
|
18667
20800
|
if (exitCode !== 0) {
|
|
@@ -18669,15 +20802,15 @@ function attachCommandActions(program2, options) {
|
|
|
18669
20802
|
}
|
|
18670
20803
|
});
|
|
18671
20804
|
program2.commands.find((command) => command.name() === "open")?.action(
|
|
18672
|
-
async (branch,
|
|
20805
|
+
async (branch, commandOptions2) => {
|
|
18673
20806
|
const exitCode = await runOpenCommand({
|
|
18674
20807
|
branch,
|
|
18675
20808
|
cwd: options.cwd,
|
|
18676
|
-
editor:
|
|
18677
|
-
save:
|
|
20809
|
+
editor: commandOptions2.editor,
|
|
20810
|
+
save: commandOptions2.save,
|
|
18678
20811
|
stderr: options.stderr,
|
|
18679
20812
|
stdout: options.stdout,
|
|
18680
|
-
workspace:
|
|
20813
|
+
workspace: commandOptions2.workspace
|
|
18681
20814
|
});
|
|
18682
20815
|
if (exitCode !== 0) {
|
|
18683
20816
|
throw commanderExit(exitCode);
|
|
@@ -18685,11 +20818,11 @@ function attachCommandActions(program2, options) {
|
|
|
18685
20818
|
}
|
|
18686
20819
|
);
|
|
18687
20820
|
program2.commands.find((command) => command.name() === "go")?.action(
|
|
18688
|
-
async (branch,
|
|
20821
|
+
async (branch, commandOptions2) => {
|
|
18689
20822
|
const exitCode = await runGoCommand({
|
|
18690
20823
|
branch,
|
|
18691
20824
|
cwd: options.cwd,
|
|
18692
|
-
print:
|
|
20825
|
+
print: commandOptions2.print,
|
|
18693
20826
|
stderr: options.stderr,
|
|
18694
20827
|
stdout: options.stdout
|
|
18695
20828
|
});
|
|
@@ -18698,31 +20831,31 @@ function attachCommandActions(program2, options) {
|
|
|
18698
20831
|
}
|
|
18699
20832
|
}
|
|
18700
20833
|
);
|
|
18701
|
-
program2.commands.find((command) => command.name() === "root")?.action(async (
|
|
20834
|
+
program2.commands.find((command) => command.name() === "root")?.action(async (commandOptions2) => {
|
|
18702
20835
|
const exitCode = await runRootCommand({
|
|
18703
20836
|
cwd: options.cwd,
|
|
18704
|
-
print:
|
|
20837
|
+
print: commandOptions2.print,
|
|
18705
20838
|
stdout: options.stdout
|
|
18706
20839
|
});
|
|
18707
20840
|
if (exitCode !== 0) {
|
|
18708
20841
|
throw commanderExit(exitCode);
|
|
18709
20842
|
}
|
|
18710
20843
|
});
|
|
18711
|
-
program2.commands.find((command) => command.name() === "status")?.action(async (
|
|
20844
|
+
program2.commands.find((command) => command.name() === "status")?.action(async (commandOptions2) => {
|
|
18712
20845
|
const exitCode = await runStatusCommand({
|
|
18713
20846
|
cwd: options.cwd,
|
|
18714
|
-
json:
|
|
20847
|
+
json: commandOptions2.json,
|
|
18715
20848
|
stdout: options.stdout
|
|
18716
20849
|
});
|
|
18717
20850
|
if (exitCode !== 0) {
|
|
18718
20851
|
throw commanderExit(exitCode);
|
|
18719
20852
|
}
|
|
18720
20853
|
});
|
|
18721
|
-
program2.commands.find((command) => command.name() === "sync")?.action(async (
|
|
20854
|
+
program2.commands.find((command) => command.name() === "sync")?.action(async (commandOptions2) => {
|
|
18722
20855
|
const exitCode = await runSyncCommand({
|
|
18723
|
-
all:
|
|
20856
|
+
all: commandOptions2.all,
|
|
18724
20857
|
cwd: options.cwd,
|
|
18725
|
-
json:
|
|
20858
|
+
json: commandOptions2.json,
|
|
18726
20859
|
stderr: options.stderr,
|
|
18727
20860
|
stdout: options.stdout
|
|
18728
20861
|
});
|
|
@@ -18733,11 +20866,11 @@ function attachCommandActions(program2, options) {
|
|
|
18733
20866
|
const syncFilesCommand = program2.commands.find(
|
|
18734
20867
|
(command) => command.name() === "sync-files"
|
|
18735
20868
|
);
|
|
18736
|
-
syncFilesCommand?.action(async (
|
|
20869
|
+
syncFilesCommand?.action(async (commandOptions2) => {
|
|
18737
20870
|
const exitCode = await runSyncFilesCommand({
|
|
18738
20871
|
action: "list",
|
|
18739
20872
|
cwd: options.cwd,
|
|
18740
|
-
json:
|
|
20873
|
+
json: commandOptions2.json,
|
|
18741
20874
|
stderr: options.stderr,
|
|
18742
20875
|
stdout: options.stdout
|
|
18743
20876
|
});
|
|
@@ -18745,11 +20878,11 @@ function attachCommandActions(program2, options) {
|
|
|
18745
20878
|
throw commanderExit(exitCode);
|
|
18746
20879
|
}
|
|
18747
20880
|
});
|
|
18748
|
-
syncFilesCommand?.commands.find((command) => command.name() === "list")?.action(async (
|
|
20881
|
+
syncFilesCommand?.commands.find((command) => command.name() === "list")?.action(async (commandOptions2) => {
|
|
18749
20882
|
const exitCode = await runSyncFilesCommand({
|
|
18750
20883
|
action: "list",
|
|
18751
20884
|
cwd: options.cwd,
|
|
18752
|
-
json:
|
|
20885
|
+
json: commandOptions2.json || syncFilesCommand?.opts().json,
|
|
18753
20886
|
stderr: options.stderr,
|
|
18754
20887
|
stdout: options.stdout
|
|
18755
20888
|
});
|
|
@@ -18757,11 +20890,11 @@ function attachCommandActions(program2, options) {
|
|
|
18757
20890
|
throw commanderExit(exitCode);
|
|
18758
20891
|
}
|
|
18759
20892
|
});
|
|
18760
|
-
syncFilesCommand?.commands.find((command) => command.name() === "add")?.action(async (paths,
|
|
20893
|
+
syncFilesCommand?.commands.find((command) => command.name() === "add")?.action(async (paths, commandOptions2) => {
|
|
18761
20894
|
const exitCode = await runSyncFilesCommand({
|
|
18762
20895
|
action: "add",
|
|
18763
20896
|
cwd: options.cwd,
|
|
18764
|
-
json:
|
|
20897
|
+
json: commandOptions2.json || syncFilesCommand?.opts().json,
|
|
18765
20898
|
paths,
|
|
18766
20899
|
stderr: options.stderr,
|
|
18767
20900
|
stdout: options.stdout
|
|
@@ -18770,11 +20903,11 @@ function attachCommandActions(program2, options) {
|
|
|
18770
20903
|
throw commanderExit(exitCode);
|
|
18771
20904
|
}
|
|
18772
20905
|
});
|
|
18773
|
-
const runSyncFilesRemoveCommand = async (paths,
|
|
20906
|
+
const runSyncFilesRemoveCommand = async (paths, commandOptions2) => {
|
|
18774
20907
|
const exitCode = await runSyncFilesCommand({
|
|
18775
20908
|
action: "remove",
|
|
18776
20909
|
cwd: options.cwd,
|
|
18777
|
-
json:
|
|
20910
|
+
json: commandOptions2.json || syncFilesCommand?.opts().json,
|
|
18778
20911
|
paths,
|
|
18779
20912
|
stderr: options.stderr,
|
|
18780
20913
|
stdout: options.stdout
|
|
@@ -18784,11 +20917,11 @@ function attachCommandActions(program2, options) {
|
|
|
18784
20917
|
}
|
|
18785
20918
|
};
|
|
18786
20919
|
syncFilesCommand?.commands.find((command) => command.name() === "remove")?.action(runSyncFilesRemoveCommand);
|
|
18787
|
-
program2.commands.find((command) => command.name() === "ls")?.action(async (
|
|
20920
|
+
program2.commands.find((command) => command.name() === "ls")?.action(async (commandOptions2) => {
|
|
18788
20921
|
const exitCode = await runLsCommand({
|
|
18789
|
-
compact:
|
|
20922
|
+
compact: commandOptions2.compact,
|
|
18790
20923
|
cwd: options.cwd,
|
|
18791
|
-
json:
|
|
20924
|
+
json: commandOptions2.json,
|
|
18792
20925
|
stdout: options.stdout
|
|
18793
20926
|
});
|
|
18794
20927
|
if (exitCode !== 0) {
|
|
@@ -18796,13 +20929,13 @@ function attachCommandActions(program2, options) {
|
|
|
18796
20929
|
}
|
|
18797
20930
|
});
|
|
18798
20931
|
program2.commands.find((command) => command.name() === "clean")?.action(
|
|
18799
|
-
async (
|
|
20932
|
+
async (commandOptions2) => {
|
|
18800
20933
|
const exitCode = await runCleanCommand({
|
|
18801
20934
|
cwd: options.cwd,
|
|
18802
|
-
dryRun:
|
|
18803
|
-
force:
|
|
18804
|
-
json:
|
|
18805
|
-
stale:
|
|
20935
|
+
dryRun: commandOptions2.dryRun,
|
|
20936
|
+
force: commandOptions2.force,
|
|
20937
|
+
json: commandOptions2.json,
|
|
20938
|
+
stale: commandOptions2.stale,
|
|
18806
20939
|
stderr: options.stderr,
|
|
18807
20940
|
stdout: options.stdout
|
|
18808
20941
|
});
|
|
@@ -18811,13 +20944,13 @@ function attachCommandActions(program2, options) {
|
|
|
18811
20944
|
}
|
|
18812
20945
|
}
|
|
18813
20946
|
);
|
|
18814
|
-
const runRemovalCommand = async (branch,
|
|
20947
|
+
const runRemovalCommand = async (branch, commandOptions2 = {}) => {
|
|
18815
20948
|
const exitCode = await runRemoveCommand({
|
|
18816
20949
|
branch,
|
|
18817
20950
|
cwd: options.cwd,
|
|
18818
|
-
dryRun:
|
|
18819
|
-
force:
|
|
18820
|
-
json:
|
|
20951
|
+
dryRun: commandOptions2.dryRun,
|
|
20952
|
+
force: commandOptions2.force,
|
|
20953
|
+
json: commandOptions2.json,
|
|
18821
20954
|
stderr: options.stderr,
|
|
18822
20955
|
stdout: options.stdout
|
|
18823
20956
|
});
|
|
@@ -18837,14 +20970,14 @@ function attachCommandActions(program2, options) {
|
|
|
18837
20970
|
}
|
|
18838
20971
|
});
|
|
18839
20972
|
program2.commands.find((command) => command.name() === "warp")?.action(
|
|
18840
|
-
async (branch,
|
|
18841
|
-
const newFlag =
|
|
20973
|
+
async (branch, commandOptions2) => {
|
|
20974
|
+
const newFlag = commandOptions2.new;
|
|
18842
20975
|
const newWorktree = newFlag !== void 0 && newFlag !== false;
|
|
18843
20976
|
const newBranch = typeof newFlag === "string" ? newFlag : void 0;
|
|
18844
20977
|
const exitCode = await runWarpCommand({
|
|
18845
20978
|
branch: newWorktree ? newBranch ?? branch : branch,
|
|
18846
20979
|
cwd: options.cwd,
|
|
18847
|
-
json:
|
|
20980
|
+
json: commandOptions2.json,
|
|
18848
20981
|
newWorktree,
|
|
18849
20982
|
stderr: options.stderr,
|
|
18850
20983
|
stdout: options.stdout
|
|
@@ -18938,18 +21071,21 @@ async function main() {
|
|
|
18938
21071
|
}
|
|
18939
21072
|
async function warnIfMissingShellIntegration() {
|
|
18940
21073
|
try {
|
|
18941
|
-
const { config } = await loadGlobalConfig(
|
|
21074
|
+
const { config } = await loadGlobalConfig(homedir8());
|
|
18942
21075
|
if (!config.shellIntegration) {
|
|
18943
|
-
const
|
|
18944
|
-
const shellArg = shellBin && ["bash", "zsh", "fish"].includes(shellBin) ? ` ${shellBin}` : "";
|
|
21076
|
+
const setupCommand = isHeadless() ? legacyShellSetupCommand() : "gji init";
|
|
18945
21077
|
process.stderr.write(
|
|
18946
|
-
`gji: shell integration not set up \u2014 run
|
|
21078
|
+
`gji: shell integration not set up \u2014 run \`${setupCommand}\` to enable automatic cd.
|
|
18947
21079
|
`
|
|
18948
21080
|
);
|
|
18949
21081
|
}
|
|
18950
21082
|
} catch {
|
|
18951
21083
|
}
|
|
18952
21084
|
}
|
|
21085
|
+
function legacyShellSetupCommand() {
|
|
21086
|
+
const shell = (process.env.SHELL ?? "").split("/").at(-1);
|
|
21087
|
+
return shell && ["bash", "fish", "zsh"].includes(shell) ? `gji init ${shell} --write` : "gji init <shell> --write";
|
|
21088
|
+
}
|
|
18953
21089
|
void main();
|
|
18954
21090
|
/*! Bundled license information:
|
|
18955
21091
|
|