@solaqua/gji 0.8.1 → 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/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 +2453 -350
- 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/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-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
|
|
@@ -14197,16 +14220,394 @@ function hasStderr(error) {
|
|
|
14197
14220
|
|
|
14198
14221
|
// src/worktree-picker.ts
|
|
14199
14222
|
import { env as env4, platform, stdin, stdout } from "node:process";
|
|
14200
|
-
|
|
14201
|
-
|
|
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([
|
|
14202
14592
|
loadHistory(),
|
|
14203
|
-
readWorktreeInfos(sources.map((source) => source.worktree))
|
|
14593
|
+
readWorktreeInfos(sources.map((source) => source.worktree)),
|
|
14594
|
+
repositoryPullRequests
|
|
14204
14595
|
]);
|
|
14596
|
+
const pullRequests = await Promise.all(
|
|
14597
|
+
sources.map(
|
|
14598
|
+
(source) => readSourcePullRequests(
|
|
14599
|
+
source,
|
|
14600
|
+
queryPullRequests,
|
|
14601
|
+
pullRequestsByRepository
|
|
14602
|
+
)
|
|
14603
|
+
)
|
|
14604
|
+
);
|
|
14205
14605
|
const historyByPath = new Map(history.map((entry) => [entry.path, entry]));
|
|
14206
14606
|
const entries = sources.map(
|
|
14207
14607
|
(source, index) => buildWorktreePromptEntry(
|
|
14208
14608
|
source,
|
|
14209
14609
|
infos[index],
|
|
14610
|
+
pullRequests[index],
|
|
14210
14611
|
historyByPath.get(source.worktree.path)?.timestamp ?? null,
|
|
14211
14612
|
Date.now()
|
|
14212
14613
|
)
|
|
@@ -14304,7 +14705,10 @@ function buildSearchableWorktreeEntry(worktree) {
|
|
|
14304
14705
|
searchText: buildPromptSearchText(worktree),
|
|
14305
14706
|
value: worktree.path,
|
|
14306
14707
|
worktree: {
|
|
14307
|
-
branch
|
|
14708
|
+
branch: formatWorktreeBranchLabel(
|
|
14709
|
+
worktree.branch,
|
|
14710
|
+
worktree.pullRequestNumbers
|
|
14711
|
+
),
|
|
14308
14712
|
metadata,
|
|
14309
14713
|
path: worktree.path,
|
|
14310
14714
|
repoName: worktree.repoName
|
|
@@ -14886,14 +15290,19 @@ function buildPromptSearchText(worktree) {
|
|
|
14886
15290
|
worktree.branch ?? "detached",
|
|
14887
15291
|
worktree.path,
|
|
14888
15292
|
worktree.label,
|
|
14889
|
-
`${worktree.repoName}/${worktree.branch ?? "detached"}
|
|
15293
|
+
`${worktree.repoName}/${worktree.branch ?? "detached"}`,
|
|
15294
|
+
...(worktree.pullRequestNumbers ?? []).map((number) => `#${number}`),
|
|
15295
|
+
...worktree.pullRequestUrls ?? []
|
|
14890
15296
|
].join(" ").toLowerCase();
|
|
14891
15297
|
}
|
|
14892
|
-
function buildWorktreePromptEntry(source, info, lastUsedTimestamp, now) {
|
|
15298
|
+
function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp, now) {
|
|
14893
15299
|
const lastWorkedTimestamp = info.lastCommitTimestamp === null ? null : info.lastCommitTimestamp * 1e3;
|
|
14894
15300
|
const lastActivityTimestamp = lastUsedTimestamp ?? lastWorkedTimestamp;
|
|
14895
15301
|
const lastActivityType = lastUsedTimestamp !== null ? "used" : lastWorkedTimestamp !== null ? "worked" : null;
|
|
14896
|
-
const branch =
|
|
15302
|
+
const branch = formatWorktreeBranchLabel(
|
|
15303
|
+
source.worktree.branch,
|
|
15304
|
+
pullRequests.map((pullRequest) => pullRequest.number)
|
|
15305
|
+
);
|
|
14897
15306
|
const badges = buildStatusBadges(info);
|
|
14898
15307
|
const recency = formatPromptRecency(
|
|
14899
15308
|
lastActivityTimestamp,
|
|
@@ -14918,9 +15327,80 @@ function buildWorktreePromptEntry(source, info, lastUsedTimestamp, now) {
|
|
|
14918
15327
|
label,
|
|
14919
15328
|
lastActivityTimestamp,
|
|
14920
15329
|
metadata,
|
|
15330
|
+
pullRequestNumbers: pullRequests.map((pullRequest) => pullRequest.number),
|
|
15331
|
+
pullRequestUrls: pullRequests.map((pullRequest) => pullRequest.url),
|
|
14921
15332
|
repoName: source.repoName
|
|
14922
15333
|
};
|
|
14923
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
|
+
}
|
|
14924
15404
|
function buildStatusBadges(info) {
|
|
14925
15405
|
const badges = [];
|
|
14926
15406
|
if (info.isCurrent) {
|
|
@@ -15442,7 +15922,14 @@ var TOP_LEVEL_COMMANDS = [
|
|
|
15442
15922
|
name: "new",
|
|
15443
15923
|
description: "create a new branch or detached linked worktree"
|
|
15444
15924
|
},
|
|
15445
|
-
{
|
|
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
|
+
},
|
|
15446
15933
|
{ name: "completion", description: "print shell completion definitions" },
|
|
15447
15934
|
{ name: "pr", description: "fetch a pull request into a linked worktree" },
|
|
15448
15935
|
{ name: "back", description: "navigate to the previously visited worktree" },
|
|
@@ -15495,6 +15982,70 @@ function renderBashCompletion() {
|
|
|
15495
15982
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
15496
15983
|
}
|
|
15497
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
|
+
|
|
15498
16049
|
_gji_completion() {
|
|
15499
16050
|
local cur command_name
|
|
15500
16051
|
COMPREPLY=()
|
|
@@ -15512,13 +16063,37 @@ _gji_completion() {
|
|
|
15512
16063
|
COMPREPLY=( $(compgen -W "--detached --force --open --editor --dry-run --json --help" -- "$cur") )
|
|
15513
16064
|
;;
|
|
15514
16065
|
init)
|
|
15515
|
-
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") )
|
|
15516
16070
|
;;
|
|
15517
16071
|
completion)
|
|
15518
16072
|
COMPREPLY=( $(compgen -W "${shells} --help" -- "$cur") )
|
|
15519
16073
|
;;
|
|
15520
16074
|
pr)
|
|
15521
|
-
|
|
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
|
|
15522
16097
|
;;
|
|
15523
16098
|
back)
|
|
15524
16099
|
COMPREPLY=( $(compgen -W "--print --help" -- "$cur") )
|
|
@@ -15603,6 +16178,103 @@ function renderFishCompletion() {
|
|
|
15603
16178
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
15604
16179
|
end
|
|
15605
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
|
+
|
|
15606
16278
|
function __gji_should_complete_config_action
|
|
15607
16279
|
set -l tokens (commandline -opc)
|
|
15608
16280
|
test (count $tokens) -eq 2
|
|
@@ -15632,14 +16304,22 @@ complete -c gji -n '__fish_seen_subcommand_from new' -l dry-run -d 'show what wo
|
|
|
15632
16304
|
complete -c gji -n '__fish_seen_subcommand_from new' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
15633
16305
|
|
|
15634
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'
|
|
15635
16308
|
${shellLines}
|
|
15636
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
|
+
|
|
15637
16314
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'bash' -d 'shell'
|
|
15638
16315
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'fish' -d 'shell'
|
|
15639
16316
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'zsh' -d 'shell'
|
|
15640
16317
|
|
|
15641
|
-
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'
|
|
15642
|
-
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'
|
|
15643
16323
|
|
|
15644
16324
|
complete -c gji -n '__fish_seen_subcommand_from back' -l print -d 'print the resolved worktree path explicitly'
|
|
15645
16325
|
|
|
@@ -15701,15 +16381,71 @@ __gji_worktree_branches() {
|
|
|
15701
16381
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
15702
16382
|
}
|
|
15703
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
|
+
|
|
15704
16440
|
local context state line
|
|
15705
|
-
local -a
|
|
16441
|
+
local -a command_entries worktree_branches
|
|
15706
16442
|
|
|
15707
|
-
|
|
16443
|
+
command_entries=(
|
|
15708
16444
|
${commandLines}
|
|
15709
16445
|
)
|
|
15710
16446
|
|
|
15711
16447
|
if (( CURRENT == 2 )); then
|
|
15712
|
-
_describe 'command'
|
|
16448
|
+
_describe 'command' command_entries
|
|
15713
16449
|
return
|
|
15714
16450
|
fi
|
|
15715
16451
|
|
|
@@ -15718,13 +16454,26 @@ case "\${words[2]}" in
|
|
|
15718
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: '
|
|
15719
16455
|
;;
|
|
15720
16456
|
init)
|
|
15721
|
-
_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]'
|
|
15722
16461
|
;;
|
|
15723
16462
|
completion)
|
|
15724
16463
|
_arguments '2:shell:(${shells})'
|
|
15725
16464
|
;;
|
|
15726
16465
|
pr)
|
|
15727
|
-
|
|
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
|
|
15728
16477
|
;;
|
|
15729
16478
|
back)
|
|
15730
16479
|
_arguments '--print[print the resolved worktree path explicitly]' '2:steps: '
|
|
@@ -15787,6 +16536,10 @@ case "$state" in
|
|
|
15787
16536
|
worktree_branches=(\${(@f)$(__gji_worktree_branches)})
|
|
15788
16537
|
_describe 'worktree branch' worktree_branches
|
|
15789
16538
|
;;
|
|
16539
|
+
pr_targets)
|
|
16540
|
+
worktree_branches=(\${(@f)$(__gji_pr_targets)})
|
|
16541
|
+
_describe 'branch or PR number' worktree_branches
|
|
16542
|
+
;;
|
|
15790
16543
|
config_keys)
|
|
15791
16544
|
_values 'config key' ${configKeys}
|
|
15792
16545
|
;;
|
|
@@ -15850,51 +16603,13 @@ function writeJson(stdout2, value) {
|
|
|
15850
16603
|
`);
|
|
15851
16604
|
}
|
|
15852
16605
|
|
|
15853
|
-
// src/
|
|
15854
|
-
import {
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
import {
|
|
15858
|
-
import { basename as
|
|
15859
|
-
|
|
15860
|
-
// src/new.ts
|
|
15861
|
-
import { execFile as execFile3 } from "node:child_process";
|
|
15862
|
-
import { mkdir as mkdir4 } from "node:fs/promises";
|
|
15863
|
-
import { basename as basename3, dirname as dirname5 } from "node:path";
|
|
15864
|
-
import { promisify as promisify4 } from "node:util";
|
|
15865
|
-
|
|
15866
|
-
// src/conflict.ts
|
|
15867
|
-
import { constants } from "node:fs";
|
|
15868
|
-
import { access as access2 } from "node:fs/promises";
|
|
15869
|
-
async function pathExists(path9) {
|
|
15870
|
-
try {
|
|
15871
|
-
await access2(path9, constants.F_OK);
|
|
15872
|
-
return true;
|
|
15873
|
-
} catch {
|
|
15874
|
-
return false;
|
|
15875
|
-
}
|
|
15876
|
-
}
|
|
15877
|
-
async function promptForPathConflict(path9) {
|
|
15878
|
-
const choice = await ve({
|
|
15879
|
-
message: `Target path already exists: ${path9}`,
|
|
15880
|
-
options: [
|
|
15881
|
-
{
|
|
15882
|
-
value: "abort",
|
|
15883
|
-
label: "Abort",
|
|
15884
|
-
hint: "Keep the existing directory untouched"
|
|
15885
|
-
},
|
|
15886
|
-
{
|
|
15887
|
-
value: "reuse",
|
|
15888
|
-
label: "Reuse path",
|
|
15889
|
-
hint: "Print the existing path and stop"
|
|
15890
|
-
}
|
|
15891
|
-
]
|
|
15892
|
-
});
|
|
15893
|
-
if (pD(choice)) {
|
|
15894
|
-
return "abort";
|
|
15895
|
-
}
|
|
15896
|
-
return choice;
|
|
15897
|
-
}
|
|
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";
|
|
15898
16613
|
|
|
15899
16614
|
// src/editor.ts
|
|
15900
16615
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -15934,14 +16649,774 @@ async function defaultSpawnEditor(cli, args) {
|
|
|
15934
16649
|
child.unref();
|
|
15935
16650
|
}
|
|
15936
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
|
+
|
|
15937
17412
|
// src/file-sync.ts
|
|
15938
|
-
import { copyFile, mkdir as
|
|
15939
|
-
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";
|
|
15940
17415
|
async function syncFiles(mainRoot, targetPath, patterns) {
|
|
15941
17416
|
for (const pattern of patterns) {
|
|
15942
17417
|
const normalized = validateSyncFilePattern(pattern);
|
|
15943
|
-
const sourcePath =
|
|
15944
|
-
const destPath =
|
|
17418
|
+
const sourcePath = join7(mainRoot, normalized);
|
|
17419
|
+
const destPath = join7(targetPath, normalized);
|
|
15945
17420
|
const sourceExists = await fileExists(sourcePath);
|
|
15946
17421
|
if (!sourceExists) {
|
|
15947
17422
|
continue;
|
|
@@ -15950,7 +17425,7 @@ async function syncFiles(mainRoot, targetPath, patterns) {
|
|
|
15950
17425
|
if (destExists) {
|
|
15951
17426
|
continue;
|
|
15952
17427
|
}
|
|
15953
|
-
await
|
|
17428
|
+
await mkdir4(dirname6(destPath), { recursive: true });
|
|
15954
17429
|
await copyFile(sourcePath, destPath);
|
|
15955
17430
|
}
|
|
15956
17431
|
}
|
|
@@ -15987,8 +17462,8 @@ function isNotFoundError(error) {
|
|
|
15987
17462
|
import { spawn as spawn4 } from "node:child_process";
|
|
15988
17463
|
|
|
15989
17464
|
// src/package-manager.ts
|
|
15990
|
-
import { access as
|
|
15991
|
-
import { join as
|
|
17465
|
+
import { access as access6, readdir } from "node:fs/promises";
|
|
17466
|
+
import { join as join8 } from "node:path";
|
|
15992
17467
|
var ENTRIES = [
|
|
15993
17468
|
// JavaScript / TypeScript
|
|
15994
17469
|
{ name: "pnpm", signals: ["pnpm-lock.yaml"], command: "pnpm install" },
|
|
@@ -16118,7 +17593,7 @@ async function detectPackageManager(repoRoot) {
|
|
|
16118
17593
|
async function matchesExact(repoRoot, signals) {
|
|
16119
17594
|
for (const signal of signals) {
|
|
16120
17595
|
try {
|
|
16121
|
-
await
|
|
17596
|
+
await access6(join8(repoRoot, signal));
|
|
16122
17597
|
return true;
|
|
16123
17598
|
} catch {
|
|
16124
17599
|
}
|
|
@@ -16282,7 +17757,7 @@ function isPlainObject2(value) {
|
|
|
16282
17757
|
}
|
|
16283
17758
|
|
|
16284
17759
|
// src/new.ts
|
|
16285
|
-
var
|
|
17760
|
+
var execFileAsync5 = promisify6(execFile5);
|
|
16286
17761
|
var NEW_OUTPUT_FILE_ENV = "GJI_NEW_OUTPUT_FILE";
|
|
16287
17762
|
function createNewCommand(dependencies = {}) {
|
|
16288
17763
|
const createBranchPlaceholder = dependencies.createBranchPlaceholder ?? generateBranchPlaceholder;
|
|
@@ -16350,11 +17825,11 @@ function createNewCommand(dependencies = {}) {
|
|
|
16350
17825
|
worktreeName,
|
|
16351
17826
|
configuredBasePath
|
|
16352
17827
|
);
|
|
16353
|
-
if (!usesGeneratedDetachedName && await
|
|
17828
|
+
if (!usesGeneratedDetachedName && await pathExists2(worktreePath)) {
|
|
16354
17829
|
if (options.force) {
|
|
16355
17830
|
if (!options.dryRun) {
|
|
16356
17831
|
try {
|
|
16357
|
-
await
|
|
17832
|
+
await execFileAsync5(
|
|
16358
17833
|
"git",
|
|
16359
17834
|
["worktree", "remove", "--force", worktreePath],
|
|
16360
17835
|
{ cwd: repository.repoRoot }
|
|
@@ -16375,7 +17850,7 @@ function createNewCommand(dependencies = {}) {
|
|
|
16375
17850
|
}
|
|
16376
17851
|
if (!options.detached) {
|
|
16377
17852
|
try {
|
|
16378
|
-
await
|
|
17853
|
+
await execFileAsync5("git", ["branch", "-D", worktreeName], {
|
|
16379
17854
|
cwd: repository.repoRoot
|
|
16380
17855
|
});
|
|
16381
17856
|
} catch {
|
|
@@ -16432,9 +17907,9 @@ function createNewCommand(dependencies = {}) {
|
|
|
16432
17907
|
}
|
|
16433
17908
|
return 0;
|
|
16434
17909
|
}
|
|
16435
|
-
await
|
|
17910
|
+
await mkdir5(dirname7(worktreePath), { recursive: true });
|
|
16436
17911
|
const gitArgs = options.detached ? ["worktree", "add", "--detach", worktreePath] : await localBranchExists(repository.repoRoot, worktreeName) ? ["worktree", "add", worktreePath, worktreeName] : ["worktree", "add", "-b", worktreeName, worktreePath];
|
|
16437
|
-
await
|
|
17912
|
+
await execFileAsync5("git", gitArgs, { cwd: repository.repoRoot });
|
|
16438
17913
|
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
|
|
16439
17914
|
(p2) => typeof p2 === "string"
|
|
16440
17915
|
) : [];
|
|
@@ -16463,7 +17938,7 @@ function createNewCommand(dependencies = {}) {
|
|
|
16463
17938
|
{
|
|
16464
17939
|
branch: worktreeName,
|
|
16465
17940
|
path: worktreePath,
|
|
16466
|
-
repo:
|
|
17941
|
+
repo: basename5(repository.repoRoot)
|
|
16467
17942
|
},
|
|
16468
17943
|
options.stderr
|
|
16469
17944
|
);
|
|
@@ -16615,7 +18090,7 @@ async function resolveUniqueDetachedWorktreePath(repoRoot, baseName, basePath) {
|
|
|
16615
18090
|
candidateName,
|
|
16616
18091
|
basePath
|
|
16617
18092
|
);
|
|
16618
|
-
if (!await
|
|
18093
|
+
if (!await pathExists2(candidatePath)) {
|
|
16619
18094
|
return candidatePath;
|
|
16620
18095
|
}
|
|
16621
18096
|
attempt += 1;
|
|
@@ -16638,7 +18113,7 @@ async function defaultPromptForBranch(placeholder) {
|
|
|
16638
18113
|
}
|
|
16639
18114
|
async function localBranchExists(repoRoot, branchName) {
|
|
16640
18115
|
try {
|
|
16641
|
-
await
|
|
18116
|
+
await execFileAsync5(
|
|
16642
18117
|
"git",
|
|
16643
18118
|
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
16644
18119
|
{ cwd: repoRoot }
|
|
@@ -16683,74 +18158,6 @@ async function openWorktree(worktreePath, editorCli, spawnFn, stderr) {
|
|
|
16683
18158
|
}
|
|
16684
18159
|
}
|
|
16685
18160
|
|
|
16686
|
-
// src/repo-registry.ts
|
|
16687
|
-
import { mkdir as mkdir5, readFile as readFile3, realpath, writeFile as writeFile4 } from "node:fs/promises";
|
|
16688
|
-
import { homedir as homedir4 } from "node:os";
|
|
16689
|
-
import { basename as basename4, dirname as dirname6, join as join6, resolve as resolve4 } from "node:path";
|
|
16690
|
-
var REGISTRY_FILE_NAME = "repos.json";
|
|
16691
|
-
var MAX_REGISTRY_ENTRIES = 100;
|
|
16692
|
-
function REGISTRY_FILE_PATH(home = homedir4()) {
|
|
16693
|
-
const configDir = process.env.GJI_CONFIG_DIR;
|
|
16694
|
-
if (configDir) {
|
|
16695
|
-
return join6(resolve4(configDir), REGISTRY_FILE_NAME);
|
|
16696
|
-
}
|
|
16697
|
-
return join6(home, GLOBAL_CONFIG_DIRECTORY, REGISTRY_FILE_NAME);
|
|
16698
|
-
}
|
|
16699
|
-
async function loadRegistry(home = homedir4()) {
|
|
16700
|
-
const path9 = REGISTRY_FILE_PATH(home);
|
|
16701
|
-
try {
|
|
16702
|
-
const raw = await readFile3(path9, "utf8");
|
|
16703
|
-
const parsed = JSON.parse(raw);
|
|
16704
|
-
if (!Array.isArray(parsed)) return [];
|
|
16705
|
-
return parsed.filter(isRegistryEntry);
|
|
16706
|
-
} catch {
|
|
16707
|
-
return [];
|
|
16708
|
-
}
|
|
16709
|
-
}
|
|
16710
|
-
async function canonicalizeRepoPath(repoPath) {
|
|
16711
|
-
try {
|
|
16712
|
-
return await realpath(repoPath);
|
|
16713
|
-
} catch {
|
|
16714
|
-
return resolve4(repoPath);
|
|
16715
|
-
}
|
|
16716
|
-
}
|
|
16717
|
-
async function registerRepo(repoPath, home = homedir4()) {
|
|
16718
|
-
const registryPath = REGISTRY_FILE_PATH(home);
|
|
16719
|
-
const existing = await normalizeRegistryForWrite(await loadRegistry(home));
|
|
16720
|
-
const canonicalRepoPath = await canonicalizeRepoPath(repoPath);
|
|
16721
|
-
if (existing.length > 0 && existing[0].path === canonicalRepoPath) return;
|
|
16722
|
-
const entry = {
|
|
16723
|
-
lastUsed: Date.now(),
|
|
16724
|
-
name: basename4(canonicalRepoPath),
|
|
16725
|
-
path: canonicalRepoPath
|
|
16726
|
-
};
|
|
16727
|
-
const filtered = existing.filter((e2) => e2.path !== canonicalRepoPath);
|
|
16728
|
-
const next = [entry, ...filtered].slice(0, MAX_REGISTRY_ENTRIES);
|
|
16729
|
-
await mkdir5(dirname6(registryPath), { recursive: true });
|
|
16730
|
-
await writeFile4(registryPath, `${JSON.stringify(next, null, 2)}
|
|
16731
|
-
`, "utf8");
|
|
16732
|
-
}
|
|
16733
|
-
async function normalizeRegistryForWrite(entries) {
|
|
16734
|
-
const normalized = [];
|
|
16735
|
-
const seenPaths = /* @__PURE__ */ new Set();
|
|
16736
|
-
for (const entry of entries) {
|
|
16737
|
-
const canonicalPath = await canonicalizeRepoPath(entry.path);
|
|
16738
|
-
if (seenPaths.has(canonicalPath)) {
|
|
16739
|
-
continue;
|
|
16740
|
-
}
|
|
16741
|
-
seenPaths.add(canonicalPath);
|
|
16742
|
-
normalized.push({
|
|
16743
|
-
...entry,
|
|
16744
|
-
name: basename4(canonicalPath),
|
|
16745
|
-
path: canonicalPath
|
|
16746
|
-
});
|
|
16747
|
-
}
|
|
16748
|
-
return normalized;
|
|
16749
|
-
}
|
|
16750
|
-
function isRegistryEntry(value) {
|
|
16751
|
-
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";
|
|
16752
|
-
}
|
|
16753
|
-
|
|
16754
18161
|
// src/warp.ts
|
|
16755
18162
|
var WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
|
|
16756
18163
|
async function runWarpCommand(options) {
|
|
@@ -16782,7 +18189,8 @@ async function runWarpNavigate(options) {
|
|
|
16782
18189
|
const target = await resolveWarpTarget({
|
|
16783
18190
|
...options,
|
|
16784
18191
|
commandName: "gji warp",
|
|
16785
|
-
json: options.json
|
|
18192
|
+
json: options.json,
|
|
18193
|
+
queryPullRequests: options.queryPullRequests
|
|
16786
18194
|
});
|
|
16787
18195
|
if (!target) return 1;
|
|
16788
18196
|
if (options.json) {
|
|
@@ -16862,7 +18270,7 @@ async function deduplicateRegistryForNew(registry) {
|
|
|
16862
18270
|
seenPaths.add(canonicalPath);
|
|
16863
18271
|
deduplicated.push({
|
|
16864
18272
|
...entry,
|
|
16865
|
-
name:
|
|
18273
|
+
name: basename6(canonicalPath),
|
|
16866
18274
|
path: canonicalPath
|
|
16867
18275
|
});
|
|
16868
18276
|
}
|
|
@@ -16899,15 +18307,16 @@ async function resolveWarpTarget(options) {
|
|
|
16899
18307
|
const results = await Promise.allSettled(
|
|
16900
18308
|
registry.map(async (entry) => {
|
|
16901
18309
|
const worktrees = await listWorktrees(entry.path);
|
|
16902
|
-
return { repoName: entry.name, worktrees };
|
|
18310
|
+
return { repoName: entry.name, repoRoot: entry.path, worktrees };
|
|
16903
18311
|
})
|
|
16904
18312
|
);
|
|
16905
18313
|
const allItems = [];
|
|
16906
18314
|
for (const result of results) {
|
|
16907
18315
|
if (result.status === "rejected") continue;
|
|
16908
|
-
const { repoName, worktrees } = result.value;
|
|
18316
|
+
const { repoName, repoRoot, worktrees } = result.value;
|
|
16909
18317
|
for (const worktree of worktrees) {
|
|
16910
18318
|
allItems.push({
|
|
18319
|
+
repoRoot,
|
|
16911
18320
|
repoName,
|
|
16912
18321
|
worktree: {
|
|
16913
18322
|
...worktree,
|
|
@@ -16921,6 +18330,7 @@ async function resolveWarpTarget(options) {
|
|
|
16921
18330
|
return null;
|
|
16922
18331
|
}
|
|
16923
18332
|
const promptSources = allItems.map((item) => ({
|
|
18333
|
+
repoRoot: item.repoRoot,
|
|
16924
18334
|
repoName: item.repoName,
|
|
16925
18335
|
worktree: item.worktree
|
|
16926
18336
|
}));
|
|
@@ -16932,7 +18342,9 @@ async function resolveWarpTarget(options) {
|
|
|
16932
18342
|
}
|
|
16933
18343
|
return { branch: match.worktree.branch, path: match.worktree.path };
|
|
16934
18344
|
}
|
|
16935
|
-
const promptEntries = await buildWorktreePromptEntries(promptSources
|
|
18345
|
+
const promptEntries = await buildWorktreePromptEntries(promptSources, {
|
|
18346
|
+
queryPullRequests: options.queryPullRequests
|
|
18347
|
+
});
|
|
16936
18348
|
const path9 = await promptForWarpTarget(promptEntries);
|
|
16937
18349
|
if (!path9) {
|
|
16938
18350
|
options.stderr("Aborted\n");
|
|
@@ -16980,10 +18392,13 @@ function createGoCommand(dependencies = {}) {
|
|
|
16980
18392
|
return 1;
|
|
16981
18393
|
}
|
|
16982
18394
|
const promptSources = worktrees.map((worktree) => ({
|
|
18395
|
+
repoRoot: repository.repoRoot,
|
|
16983
18396
|
repoName: repository.repoName,
|
|
16984
18397
|
worktree
|
|
16985
18398
|
}));
|
|
16986
|
-
const promptEntries = options.branch ? [] : await buildWorktreePromptEntries(promptSources
|
|
18399
|
+
const promptEntries = options.branch ? [] : await buildWorktreePromptEntries(promptSources, {
|
|
18400
|
+
queryPullRequests: dependencies.queryPullRequests
|
|
18401
|
+
});
|
|
16987
18402
|
const queried = options.branch ? resolveWorktreeQuery(promptSources, options.branch) : null;
|
|
16988
18403
|
const prompted = options.branch ? null : await prompt(promptEntries);
|
|
16989
18404
|
const resolvedPath = options.branch ? queried?.worktree.path : prompted ?? void 0;
|
|
@@ -17011,7 +18426,7 @@ function createGoCommand(dependencies = {}) {
|
|
|
17011
18426
|
{
|
|
17012
18427
|
branch: chosenWorktree?.branch ?? void 0,
|
|
17013
18428
|
path: resolvedPath,
|
|
17014
|
-
repo:
|
|
18429
|
+
repo: basename7(repository.repoRoot)
|
|
17015
18430
|
},
|
|
17016
18431
|
options.stderr
|
|
17017
18432
|
);
|
|
@@ -17042,11 +18457,11 @@ async function runHistoryCommand(options) {
|
|
|
17042
18457
|
}
|
|
17043
18458
|
|
|
17044
18459
|
// src/init.ts
|
|
17045
|
-
import {
|
|
17046
|
-
import {
|
|
17047
|
-
import {
|
|
17048
|
-
|
|
17049
|
-
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)";
|
|
17050
18465
|
var SHELL_WRAPPED_COMMANDS = [
|
|
17051
18466
|
{
|
|
17052
18467
|
bypassOptions: ["--help"],
|
|
@@ -17097,10 +18512,256 @@ var SHELL_WRAPPED_COMMANDS = [
|
|
|
17097
18512
|
names: ["warp"],
|
|
17098
18513
|
tempPrefix: "gji-warp"
|
|
17099
18514
|
}
|
|
17100
|
-
];
|
|
17101
|
-
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) {
|
|
17102
18763
|
const shell = resolveSupportedShell(options.shell, process.env.SHELL);
|
|
17103
|
-
const home = options.home ??
|
|
18764
|
+
const home = options.home ?? homedir6();
|
|
17104
18765
|
if (!shell) {
|
|
17105
18766
|
options.stderr?.(
|
|
17106
18767
|
"Unable to detect a supported shell. Specify one explicitly: bash, fish, or zsh.\n"
|
|
@@ -17113,7 +18774,7 @@ async function runInitCommand(options) {
|
|
|
17113
18774
|
return 0;
|
|
17114
18775
|
}
|
|
17115
18776
|
const rcPath = resolveShellConfigPath(shell, home);
|
|
17116
|
-
await mkdir6(
|
|
18777
|
+
await mkdir6(dirname8(rcPath), { recursive: true });
|
|
17117
18778
|
const current = await readExistingConfig(rcPath);
|
|
17118
18779
|
const next = upsertShellIntegration(current, script);
|
|
17119
18780
|
await writeFile5(rcPath, next, "utf8");
|
|
@@ -17122,7 +18783,7 @@ async function runInitCommand(options) {
|
|
|
17122
18783
|
const { config: globalConfig } = await loadGlobalConfig(home);
|
|
17123
18784
|
const alreadyConfigured = "shellIntegration" in globalConfig || "installSaveTarget" in globalConfig;
|
|
17124
18785
|
const hasCustomPrompt = options.promptForSetup !== void 0;
|
|
17125
|
-
const canPrompt = hasCustomPrompt || process.stdout.isTTY === true;
|
|
18786
|
+
const canPrompt = !isHeadless() && (hasCustomPrompt || process.stdout.isTTY === true);
|
|
17126
18787
|
if (!alreadyConfigured && canPrompt) {
|
|
17127
18788
|
const prompt = options.promptForSetup ?? defaultPromptForSetup;
|
|
17128
18789
|
const result = await prompt();
|
|
@@ -17144,30 +18805,30 @@ function renderShellIntegration(shell) {
|
|
|
17144
18805
|
).join("\n\n");
|
|
17145
18806
|
switch (shell) {
|
|
17146
18807
|
case "fish":
|
|
17147
|
-
return `${
|
|
18808
|
+
return `${SHELL_INTEGRATION_START_MARKER}
|
|
17148
18809
|
function gji --wraps gji --description 'gji shell integration'
|
|
17149
18810
|
${indentBlock(commandBlocks, 4)}
|
|
17150
18811
|
|
|
17151
18812
|
command gji $argv
|
|
17152
18813
|
end
|
|
17153
|
-
${
|
|
18814
|
+
${SHELL_INTEGRATION_END_MARKER}
|
|
17154
18815
|
`;
|
|
17155
18816
|
case "bash":
|
|
17156
18817
|
case "zsh":
|
|
17157
|
-
return `${
|
|
18818
|
+
return `${SHELL_INTEGRATION_START_MARKER}
|
|
17158
18819
|
gji() {
|
|
17159
18820
|
${indentBlock(commandBlocks, 2)}
|
|
17160
18821
|
|
|
17161
18822
|
command gji "$@"
|
|
17162
18823
|
}
|
|
17163
|
-
${
|
|
18824
|
+
${SHELL_INTEGRATION_END_MARKER}
|
|
17164
18825
|
`;
|
|
17165
18826
|
}
|
|
17166
18827
|
}
|
|
17167
18828
|
function upsertShellIntegration(existingConfig, script) {
|
|
17168
18829
|
const trimmedScript = script.trimEnd();
|
|
17169
18830
|
const blockPattern = new RegExp(
|
|
17170
|
-
`${escapeForRegExp(
|
|
18831
|
+
`${escapeForRegExp(SHELL_INTEGRATION_START_MARKER)}[\\s\\S]*?${escapeForRegExp(SHELL_INTEGRATION_END_MARKER)}\\n?`,
|
|
17171
18832
|
"m"
|
|
17172
18833
|
);
|
|
17173
18834
|
if (blockPattern.test(existingConfig)) {
|
|
@@ -17205,21 +18866,11 @@ async function saveWizardConfig(result, cwd, home) {
|
|
|
17205
18866
|
await saveGlobalConfig({ ...existing, ...values }, home);
|
|
17206
18867
|
}
|
|
17207
18868
|
}
|
|
17208
|
-
function resolveShellConfigPath(shell, home) {
|
|
17209
|
-
switch (shell) {
|
|
17210
|
-
case "bash":
|
|
17211
|
-
return join7(home, ".bashrc");
|
|
17212
|
-
case "fish":
|
|
17213
|
-
return join7(home, ".config", "fish", "config.fish");
|
|
17214
|
-
case "zsh":
|
|
17215
|
-
return join7(home, ".zshrc");
|
|
17216
|
-
}
|
|
17217
|
-
}
|
|
17218
18869
|
async function readExistingConfig(path9) {
|
|
17219
18870
|
try {
|
|
17220
|
-
return await
|
|
18871
|
+
return await readFile5(path9, "utf8");
|
|
17221
18872
|
} catch (error) {
|
|
17222
|
-
if (
|
|
18873
|
+
if (isMissingFileError3(error)) {
|
|
17223
18874
|
return "";
|
|
17224
18875
|
}
|
|
17225
18876
|
throw error;
|
|
@@ -17232,7 +18883,7 @@ function ensureTrailingNewline(value) {
|
|
|
17232
18883
|
function escapeForRegExp(value) {
|
|
17233
18884
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17234
18885
|
}
|
|
17235
|
-
function
|
|
18886
|
+
function isMissingFileError3(error) {
|
|
17236
18887
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
17237
18888
|
}
|
|
17238
18889
|
function renderFishWrapper(command) {
|
|
@@ -17457,14 +19108,14 @@ function sortWorktrees(worktrees) {
|
|
|
17457
19108
|
}
|
|
17458
19109
|
|
|
17459
19110
|
// src/open.ts
|
|
17460
|
-
import { execFile as
|
|
17461
|
-
import { access as
|
|
17462
|
-
import { join as
|
|
17463
|
-
import { promisify as
|
|
17464
|
-
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);
|
|
17465
19116
|
function createOpenCommand(dependencies = {}) {
|
|
17466
19117
|
const detectEditors = dependencies.detectEditors ?? detectInstalledEditors;
|
|
17467
|
-
const
|
|
19118
|
+
const promptForEditor2 = dependencies.promptForEditor ?? defaultPromptForEditor;
|
|
17468
19119
|
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree;
|
|
17469
19120
|
const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
|
|
17470
19121
|
return async function runOpenCommand2(options) {
|
|
@@ -17499,9 +19150,11 @@ function createOpenCommand(dependencies = {}) {
|
|
|
17499
19150
|
} else {
|
|
17500
19151
|
const entries = await buildWorktreePromptEntries(
|
|
17501
19152
|
worktrees.map((worktree) => ({
|
|
19153
|
+
repoRoot: repository.repoRoot,
|
|
17502
19154
|
repoName: repository.repoName,
|
|
17503
19155
|
worktree
|
|
17504
|
-
}))
|
|
19156
|
+
})),
|
|
19157
|
+
{ queryPullRequests: dependencies.queryPullRequests }
|
|
17505
19158
|
);
|
|
17506
19159
|
const chosen = await promptForWorktree2(entries);
|
|
17507
19160
|
if (!chosen) {
|
|
@@ -17533,7 +19186,7 @@ function createOpenCommand(dependencies = {}) {
|
|
|
17533
19186
|
if (installed.length === 1 || isHeadless()) {
|
|
17534
19187
|
editorCli = installed[0].cli;
|
|
17535
19188
|
} else {
|
|
17536
|
-
const chosen = await
|
|
19189
|
+
const chosen = await promptForEditor2(installed);
|
|
17537
19190
|
if (!chosen) {
|
|
17538
19191
|
options.stderr("Aborted\n");
|
|
17539
19192
|
return 1;
|
|
@@ -17592,7 +19245,7 @@ async function detectInstalledEditors() {
|
|
|
17592
19245
|
}
|
|
17593
19246
|
async function isCommandAvailable(command) {
|
|
17594
19247
|
try {
|
|
17595
|
-
await
|
|
19248
|
+
await execFileAsync6("which", [command]);
|
|
17596
19249
|
return true;
|
|
17597
19250
|
} catch {
|
|
17598
19251
|
return false;
|
|
@@ -17610,9 +19263,9 @@ async function defaultPromptForEditor(editors) {
|
|
|
17610
19263
|
return choice;
|
|
17611
19264
|
}
|
|
17612
19265
|
async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
17613
|
-
const workspacePath =
|
|
19266
|
+
const workspacePath = join9(worktreePath, `${repoName}.code-workspace`);
|
|
17614
19267
|
try {
|
|
17615
|
-
await
|
|
19268
|
+
await access8(workspacePath);
|
|
17616
19269
|
return workspacePath;
|
|
17617
19270
|
} catch {
|
|
17618
19271
|
}
|
|
@@ -17627,11 +19280,11 @@ async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
|
17627
19280
|
}
|
|
17628
19281
|
|
|
17629
19282
|
// src/pr.ts
|
|
17630
|
-
import { execFile as
|
|
19283
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
17631
19284
|
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
17632
|
-
import { basename as
|
|
17633
|
-
import { promisify as
|
|
17634
|
-
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);
|
|
17635
19288
|
var PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
|
|
17636
19289
|
function parsePrInput(input) {
|
|
17637
19290
|
if (/^\d+$/.test(input)) return input;
|
|
@@ -17673,7 +19326,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
17673
19326
|
branchName,
|
|
17674
19327
|
configuredBasePath
|
|
17675
19328
|
);
|
|
17676
|
-
if (await
|
|
19329
|
+
if (await pathExists2(worktreePath)) {
|
|
17677
19330
|
if (options.json || isHeadless()) {
|
|
17678
19331
|
const message = `target worktree path already exists: ${worktreePath}`;
|
|
17679
19332
|
if (options.json) {
|
|
@@ -17739,13 +19392,13 @@ function createPrCommand(dependencies = {}) {
|
|
|
17739
19392
|
}
|
|
17740
19393
|
return 1;
|
|
17741
19394
|
}
|
|
17742
|
-
await mkdir7(
|
|
19395
|
+
await mkdir7(dirname9(worktreePath), { recursive: true });
|
|
17743
19396
|
const branchAlreadyExists = await localBranchExists2(
|
|
17744
19397
|
repository.repoRoot,
|
|
17745
19398
|
branchName
|
|
17746
19399
|
);
|
|
17747
19400
|
const worktreeArgs = branchAlreadyExists ? ["worktree", "add", worktreePath, branchName] : ["worktree", "add", "-b", branchName, worktreePath, remoteRef];
|
|
17748
|
-
await
|
|
19401
|
+
await execFileAsync7("git", worktreeArgs, { cwd: repository.repoRoot });
|
|
17749
19402
|
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
|
|
17750
19403
|
(p2) => typeof p2 === "string"
|
|
17751
19404
|
) : [];
|
|
@@ -17774,7 +19427,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
17774
19427
|
{
|
|
17775
19428
|
branch: branchName,
|
|
17776
19429
|
path: worktreePath,
|
|
17777
|
-
repo:
|
|
19430
|
+
repo: basename8(repository.repoRoot)
|
|
17778
19431
|
},
|
|
17779
19432
|
options.stderr
|
|
17780
19433
|
);
|
|
@@ -17792,7 +19445,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
17792
19445
|
}
|
|
17793
19446
|
async function localBranchExists2(repoRoot, branchName) {
|
|
17794
19447
|
try {
|
|
17795
|
-
await
|
|
19448
|
+
await execFileAsync7(
|
|
17796
19449
|
"git",
|
|
17797
19450
|
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
17798
19451
|
{ cwd: repoRoot }
|
|
@@ -17806,7 +19459,7 @@ var runPrCommand = createPrCommand();
|
|
|
17806
19459
|
async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef) {
|
|
17807
19460
|
for (const sourceRef of listPullRequestSourceRefs(input, prNumber)) {
|
|
17808
19461
|
try {
|
|
17809
|
-
await
|
|
19462
|
+
await execFileAsync7(
|
|
17810
19463
|
"git",
|
|
17811
19464
|
["fetch", "origin", `${sourceRef}:${remoteRef}`],
|
|
17812
19465
|
{ cwd: repoRoot }
|
|
@@ -17856,11 +19509,415 @@ async function writeOutput2(worktreePath, stdout2) {
|
|
|
17856
19509
|
await writeShellOutput(PR_OUTPUT_FILE_ENV, worktreePath, stdout2);
|
|
17857
19510
|
}
|
|
17858
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
|
+
|
|
17859
19916
|
// src/remove.ts
|
|
17860
|
-
import { basename as
|
|
19917
|
+
import { basename as basename9 } from "node:path";
|
|
17861
19918
|
var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
|
|
17862
19919
|
function createRemoveCommand(dependencies = {}) {
|
|
17863
|
-
const promptForWorktree2 = dependencies.promptForWorktree ??
|
|
19920
|
+
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree3;
|
|
17864
19921
|
const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval2;
|
|
17865
19922
|
const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ?? defaultConfirmForceRemoveWorktree;
|
|
17866
19923
|
const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
|
|
@@ -17944,7 +20001,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
17944
20001
|
{
|
|
17945
20002
|
branch: worktree.branch ?? void 0,
|
|
17946
20003
|
path: worktree.path,
|
|
17947
|
-
repo:
|
|
20004
|
+
repo: basename9(repository.repoRoot)
|
|
17948
20005
|
},
|
|
17949
20006
|
options.stderr
|
|
17950
20007
|
);
|
|
@@ -18004,7 +20061,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
18004
20061
|
};
|
|
18005
20062
|
}
|
|
18006
20063
|
var runRemoveCommand = createRemoveCommand();
|
|
18007
|
-
async function
|
|
20064
|
+
async function defaultPromptForWorktree3(worktrees) {
|
|
18008
20065
|
return promptForSingleWorktree("Choose a worktree to finish", worktrees);
|
|
18009
20066
|
}
|
|
18010
20067
|
async function defaultConfirmRemoval2(worktree) {
|
|
@@ -18329,11 +20386,11 @@ function resolveConfiguredString2(value) {
|
|
|
18329
20386
|
}
|
|
18330
20387
|
|
|
18331
20388
|
// src/sync-files-command.ts
|
|
18332
|
-
import { homedir as
|
|
18333
|
-
import { join as
|
|
20389
|
+
import { homedir as homedir7 } from "node:os";
|
|
20390
|
+
import { join as join10 } from "node:path";
|
|
18334
20391
|
async function runSyncFilesCommand(options) {
|
|
18335
20392
|
const repository = await detectRepository(options.cwd);
|
|
18336
|
-
const home = options.home ??
|
|
20393
|
+
const home = options.home ?? homedir7();
|
|
18337
20394
|
const loaded = await loadGlobalConfig(home);
|
|
18338
20395
|
const repoEntry = findRepoConfigEntry(
|
|
18339
20396
|
loaded.config,
|
|
@@ -18379,7 +20436,7 @@ function findRepoConfigEntry(config, repoRoot, home) {
|
|
|
18379
20436
|
const repos = config.repos;
|
|
18380
20437
|
if (!isPlainObject3(repos)) return null;
|
|
18381
20438
|
for (const [key, value] of Object.entries(repos)) {
|
|
18382
|
-
if (
|
|
20439
|
+
if (expandTilde3(key, home) === repoRoot && isPlainObject3(value)) {
|
|
18383
20440
|
return { config: value, key };
|
|
18384
20441
|
}
|
|
18385
20442
|
}
|
|
@@ -18463,9 +20520,9 @@ function writeError(options, message) {
|
|
|
18463
20520
|
options.stderr(`gji sync-files: ${message}
|
|
18464
20521
|
`);
|
|
18465
20522
|
}
|
|
18466
|
-
function
|
|
20523
|
+
function expandTilde3(value, home) {
|
|
18467
20524
|
if (value === "~") return home;
|
|
18468
|
-
if (value.startsWith("~/")) return
|
|
20525
|
+
if (value.startsWith("~/")) return join10(home, value.slice(2));
|
|
18469
20526
|
return value;
|
|
18470
20527
|
}
|
|
18471
20528
|
|
|
@@ -18487,7 +20544,9 @@ function readPackageMetadata() {
|
|
|
18487
20544
|
}
|
|
18488
20545
|
async function runCli(argv, options = {}) {
|
|
18489
20546
|
await maybeNotifyForUpdates(argv);
|
|
18490
|
-
|
|
20547
|
+
if (argv[0] !== "doctor") {
|
|
20548
|
+
maybeRegisterCurrentRepo(options.cwd ?? process.cwd());
|
|
20549
|
+
}
|
|
18491
20550
|
const program2 = createProgram();
|
|
18492
20551
|
const cwd = options.cwd ?? process.cwd();
|
|
18493
20552
|
const stdout2 = options.stdout ?? (() => void 0);
|
|
@@ -18502,7 +20561,12 @@ async function runCli(argv, options = {}) {
|
|
|
18502
20561
|
return { exitCode: 0 };
|
|
18503
20562
|
}
|
|
18504
20563
|
try {
|
|
18505
|
-
attachCommandActions(program2, {
|
|
20564
|
+
attachCommandActions(program2, {
|
|
20565
|
+
cwd,
|
|
20566
|
+
prOpenDependencies: options.prOpenDependencies,
|
|
20567
|
+
stderr,
|
|
20568
|
+
stdout: stdout2
|
|
20569
|
+
});
|
|
18506
20570
|
await program2.parseAsync(["node", "gji", ...argv], { from: "node" });
|
|
18507
20571
|
return { exitCode: 0 };
|
|
18508
20572
|
} catch (error) {
|
|
@@ -18548,9 +20612,12 @@ function registerCommands(program2) {
|
|
|
18548
20612
|
"--json",
|
|
18549
20613
|
"emit JSON on success or error instead of human-readable output"
|
|
18550
20614
|
).action(notImplemented("new"));
|
|
18551
|
-
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"));
|
|
18552
20619
|
program2.command("completion [shell]").description("print shell completion definitions").action(notImplemented("completion"));
|
|
18553
|
-
program2.command("pr <ref>").description(
|
|
20620
|
+
const prCommand = program2.command("pr <ref>").usage("[options] <ref>").description(
|
|
18554
20621
|
"fetch a pull request by number, #number, or URL into a linked worktree"
|
|
18555
20622
|
).option(
|
|
18556
20623
|
"--dry-run",
|
|
@@ -18559,6 +20626,7 @@ function registerCommands(program2) {
|
|
|
18559
20626
|
"--json",
|
|
18560
20627
|
"emit JSON on success or error instead of human-readable output"
|
|
18561
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"));
|
|
18562
20630
|
program2.command("back [n]").description(
|
|
18563
20631
|
"navigate to the previously visited worktree, optionally N steps back"
|
|
18564
20632
|
).option("--print", "print the resolved worktree path explicitly").action(notImplemented("back"));
|
|
@@ -18616,16 +20684,16 @@ function registerCommands(program2) {
|
|
|
18616
20684
|
}
|
|
18617
20685
|
function attachCommandActions(program2, options) {
|
|
18618
20686
|
program2.commands.find((command) => command.name() === "new")?.action(
|
|
18619
|
-
async (branch,
|
|
20687
|
+
async (branch, commandOptions2) => {
|
|
18620
20688
|
const exitCode = await runNewCommand({
|
|
18621
20689
|
...options,
|
|
18622
20690
|
branch,
|
|
18623
|
-
detached:
|
|
18624
|
-
dryRun:
|
|
18625
|
-
editor:
|
|
18626
|
-
force:
|
|
18627
|
-
json:
|
|
18628
|
-
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
|
|
18629
20697
|
});
|
|
18630
20698
|
if (exitCode !== 0) {
|
|
18631
20699
|
throw commanderExit(exitCode);
|
|
@@ -18633,13 +20701,29 @@ function attachCommandActions(program2, options) {
|
|
|
18633
20701
|
}
|
|
18634
20702
|
);
|
|
18635
20703
|
program2.commands.find((command) => command.name() === "init")?.action(
|
|
18636
|
-
async (shell,
|
|
20704
|
+
async (shell, commandOptions2) => {
|
|
18637
20705
|
const exitCode = await runInitCommand({
|
|
18638
20706
|
cwd: options.cwd,
|
|
20707
|
+
json: commandOptions2.json,
|
|
18639
20708
|
shell,
|
|
18640
20709
|
stderr: options.stderr,
|
|
18641
20710
|
stdout: options.stdout,
|
|
18642
|
-
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
|
|
18643
20727
|
});
|
|
18644
20728
|
if (exitCode !== 0) {
|
|
18645
20729
|
throw commanderExit(exitCode);
|
|
@@ -18657,11 +20741,11 @@ function attachCommandActions(program2, options) {
|
|
|
18657
20741
|
}
|
|
18658
20742
|
});
|
|
18659
20743
|
program2.commands.find((command) => command.name() === "pr")?.action(
|
|
18660
|
-
async (number,
|
|
20744
|
+
async (number, commandOptions2) => {
|
|
18661
20745
|
const exitCode = await runPrCommand({
|
|
18662
20746
|
cwd: options.cwd,
|
|
18663
|
-
dryRun:
|
|
18664
|
-
json:
|
|
20747
|
+
dryRun: commandOptions2.dryRun,
|
|
20748
|
+
json: commandOptions2.json,
|
|
18665
20749
|
number,
|
|
18666
20750
|
stderr: options.stderr,
|
|
18667
20751
|
stdout: options.stdout
|
|
@@ -18671,8 +20755,24 @@ function attachCommandActions(program2, options) {
|
|
|
18671
20755
|
}
|
|
18672
20756
|
}
|
|
18673
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
|
+
);
|
|
18674
20774
|
program2.commands.find((command) => command.name() === "back")?.action(
|
|
18675
|
-
async (n,
|
|
20775
|
+
async (n, commandOptions2) => {
|
|
18676
20776
|
if (n !== void 0 && !/^\d+$/.test(n)) {
|
|
18677
20777
|
options.stderr(`gji back: invalid step count: ${n}
|
|
18678
20778
|
`);
|
|
@@ -18682,7 +20782,7 @@ function attachCommandActions(program2, options) {
|
|
|
18682
20782
|
const exitCode = await runBackCommand({
|
|
18683
20783
|
cwd: options.cwd,
|
|
18684
20784
|
n: steps,
|
|
18685
|
-
print:
|
|
20785
|
+
print: commandOptions2.print,
|
|
18686
20786
|
stderr: options.stderr,
|
|
18687
20787
|
stdout: options.stdout
|
|
18688
20788
|
});
|
|
@@ -18691,10 +20791,10 @@ function attachCommandActions(program2, options) {
|
|
|
18691
20791
|
}
|
|
18692
20792
|
}
|
|
18693
20793
|
);
|
|
18694
|
-
program2.commands.find((command) => command.name() === "history")?.action(async (
|
|
20794
|
+
program2.commands.find((command) => command.name() === "history")?.action(async (commandOptions2) => {
|
|
18695
20795
|
const exitCode = await runHistoryCommand({
|
|
18696
20796
|
cwd: options.cwd,
|
|
18697
|
-
json:
|
|
20797
|
+
json: commandOptions2.json,
|
|
18698
20798
|
stdout: options.stdout
|
|
18699
20799
|
});
|
|
18700
20800
|
if (exitCode !== 0) {
|
|
@@ -18702,15 +20802,15 @@ function attachCommandActions(program2, options) {
|
|
|
18702
20802
|
}
|
|
18703
20803
|
});
|
|
18704
20804
|
program2.commands.find((command) => command.name() === "open")?.action(
|
|
18705
|
-
async (branch,
|
|
20805
|
+
async (branch, commandOptions2) => {
|
|
18706
20806
|
const exitCode = await runOpenCommand({
|
|
18707
20807
|
branch,
|
|
18708
20808
|
cwd: options.cwd,
|
|
18709
|
-
editor:
|
|
18710
|
-
save:
|
|
20809
|
+
editor: commandOptions2.editor,
|
|
20810
|
+
save: commandOptions2.save,
|
|
18711
20811
|
stderr: options.stderr,
|
|
18712
20812
|
stdout: options.stdout,
|
|
18713
|
-
workspace:
|
|
20813
|
+
workspace: commandOptions2.workspace
|
|
18714
20814
|
});
|
|
18715
20815
|
if (exitCode !== 0) {
|
|
18716
20816
|
throw commanderExit(exitCode);
|
|
@@ -18718,11 +20818,11 @@ function attachCommandActions(program2, options) {
|
|
|
18718
20818
|
}
|
|
18719
20819
|
);
|
|
18720
20820
|
program2.commands.find((command) => command.name() === "go")?.action(
|
|
18721
|
-
async (branch,
|
|
20821
|
+
async (branch, commandOptions2) => {
|
|
18722
20822
|
const exitCode = await runGoCommand({
|
|
18723
20823
|
branch,
|
|
18724
20824
|
cwd: options.cwd,
|
|
18725
|
-
print:
|
|
20825
|
+
print: commandOptions2.print,
|
|
18726
20826
|
stderr: options.stderr,
|
|
18727
20827
|
stdout: options.stdout
|
|
18728
20828
|
});
|
|
@@ -18731,31 +20831,31 @@ function attachCommandActions(program2, options) {
|
|
|
18731
20831
|
}
|
|
18732
20832
|
}
|
|
18733
20833
|
);
|
|
18734
|
-
program2.commands.find((command) => command.name() === "root")?.action(async (
|
|
20834
|
+
program2.commands.find((command) => command.name() === "root")?.action(async (commandOptions2) => {
|
|
18735
20835
|
const exitCode = await runRootCommand({
|
|
18736
20836
|
cwd: options.cwd,
|
|
18737
|
-
print:
|
|
20837
|
+
print: commandOptions2.print,
|
|
18738
20838
|
stdout: options.stdout
|
|
18739
20839
|
});
|
|
18740
20840
|
if (exitCode !== 0) {
|
|
18741
20841
|
throw commanderExit(exitCode);
|
|
18742
20842
|
}
|
|
18743
20843
|
});
|
|
18744
|
-
program2.commands.find((command) => command.name() === "status")?.action(async (
|
|
20844
|
+
program2.commands.find((command) => command.name() === "status")?.action(async (commandOptions2) => {
|
|
18745
20845
|
const exitCode = await runStatusCommand({
|
|
18746
20846
|
cwd: options.cwd,
|
|
18747
|
-
json:
|
|
20847
|
+
json: commandOptions2.json,
|
|
18748
20848
|
stdout: options.stdout
|
|
18749
20849
|
});
|
|
18750
20850
|
if (exitCode !== 0) {
|
|
18751
20851
|
throw commanderExit(exitCode);
|
|
18752
20852
|
}
|
|
18753
20853
|
});
|
|
18754
|
-
program2.commands.find((command) => command.name() === "sync")?.action(async (
|
|
20854
|
+
program2.commands.find((command) => command.name() === "sync")?.action(async (commandOptions2) => {
|
|
18755
20855
|
const exitCode = await runSyncCommand({
|
|
18756
|
-
all:
|
|
20856
|
+
all: commandOptions2.all,
|
|
18757
20857
|
cwd: options.cwd,
|
|
18758
|
-
json:
|
|
20858
|
+
json: commandOptions2.json,
|
|
18759
20859
|
stderr: options.stderr,
|
|
18760
20860
|
stdout: options.stdout
|
|
18761
20861
|
});
|
|
@@ -18766,11 +20866,11 @@ function attachCommandActions(program2, options) {
|
|
|
18766
20866
|
const syncFilesCommand = program2.commands.find(
|
|
18767
20867
|
(command) => command.name() === "sync-files"
|
|
18768
20868
|
);
|
|
18769
|
-
syncFilesCommand?.action(async (
|
|
20869
|
+
syncFilesCommand?.action(async (commandOptions2) => {
|
|
18770
20870
|
const exitCode = await runSyncFilesCommand({
|
|
18771
20871
|
action: "list",
|
|
18772
20872
|
cwd: options.cwd,
|
|
18773
|
-
json:
|
|
20873
|
+
json: commandOptions2.json,
|
|
18774
20874
|
stderr: options.stderr,
|
|
18775
20875
|
stdout: options.stdout
|
|
18776
20876
|
});
|
|
@@ -18778,11 +20878,11 @@ function attachCommandActions(program2, options) {
|
|
|
18778
20878
|
throw commanderExit(exitCode);
|
|
18779
20879
|
}
|
|
18780
20880
|
});
|
|
18781
|
-
syncFilesCommand?.commands.find((command) => command.name() === "list")?.action(async (
|
|
20881
|
+
syncFilesCommand?.commands.find((command) => command.name() === "list")?.action(async (commandOptions2) => {
|
|
18782
20882
|
const exitCode = await runSyncFilesCommand({
|
|
18783
20883
|
action: "list",
|
|
18784
20884
|
cwd: options.cwd,
|
|
18785
|
-
json:
|
|
20885
|
+
json: commandOptions2.json || syncFilesCommand?.opts().json,
|
|
18786
20886
|
stderr: options.stderr,
|
|
18787
20887
|
stdout: options.stdout
|
|
18788
20888
|
});
|
|
@@ -18790,11 +20890,11 @@ function attachCommandActions(program2, options) {
|
|
|
18790
20890
|
throw commanderExit(exitCode);
|
|
18791
20891
|
}
|
|
18792
20892
|
});
|
|
18793
|
-
syncFilesCommand?.commands.find((command) => command.name() === "add")?.action(async (paths,
|
|
20893
|
+
syncFilesCommand?.commands.find((command) => command.name() === "add")?.action(async (paths, commandOptions2) => {
|
|
18794
20894
|
const exitCode = await runSyncFilesCommand({
|
|
18795
20895
|
action: "add",
|
|
18796
20896
|
cwd: options.cwd,
|
|
18797
|
-
json:
|
|
20897
|
+
json: commandOptions2.json || syncFilesCommand?.opts().json,
|
|
18798
20898
|
paths,
|
|
18799
20899
|
stderr: options.stderr,
|
|
18800
20900
|
stdout: options.stdout
|
|
@@ -18803,11 +20903,11 @@ function attachCommandActions(program2, options) {
|
|
|
18803
20903
|
throw commanderExit(exitCode);
|
|
18804
20904
|
}
|
|
18805
20905
|
});
|
|
18806
|
-
const runSyncFilesRemoveCommand = async (paths,
|
|
20906
|
+
const runSyncFilesRemoveCommand = async (paths, commandOptions2) => {
|
|
18807
20907
|
const exitCode = await runSyncFilesCommand({
|
|
18808
20908
|
action: "remove",
|
|
18809
20909
|
cwd: options.cwd,
|
|
18810
|
-
json:
|
|
20910
|
+
json: commandOptions2.json || syncFilesCommand?.opts().json,
|
|
18811
20911
|
paths,
|
|
18812
20912
|
stderr: options.stderr,
|
|
18813
20913
|
stdout: options.stdout
|
|
@@ -18817,11 +20917,11 @@ function attachCommandActions(program2, options) {
|
|
|
18817
20917
|
}
|
|
18818
20918
|
};
|
|
18819
20919
|
syncFilesCommand?.commands.find((command) => command.name() === "remove")?.action(runSyncFilesRemoveCommand);
|
|
18820
|
-
program2.commands.find((command) => command.name() === "ls")?.action(async (
|
|
20920
|
+
program2.commands.find((command) => command.name() === "ls")?.action(async (commandOptions2) => {
|
|
18821
20921
|
const exitCode = await runLsCommand({
|
|
18822
|
-
compact:
|
|
20922
|
+
compact: commandOptions2.compact,
|
|
18823
20923
|
cwd: options.cwd,
|
|
18824
|
-
json:
|
|
20924
|
+
json: commandOptions2.json,
|
|
18825
20925
|
stdout: options.stdout
|
|
18826
20926
|
});
|
|
18827
20927
|
if (exitCode !== 0) {
|
|
@@ -18829,13 +20929,13 @@ function attachCommandActions(program2, options) {
|
|
|
18829
20929
|
}
|
|
18830
20930
|
});
|
|
18831
20931
|
program2.commands.find((command) => command.name() === "clean")?.action(
|
|
18832
|
-
async (
|
|
20932
|
+
async (commandOptions2) => {
|
|
18833
20933
|
const exitCode = await runCleanCommand({
|
|
18834
20934
|
cwd: options.cwd,
|
|
18835
|
-
dryRun:
|
|
18836
|
-
force:
|
|
18837
|
-
json:
|
|
18838
|
-
stale:
|
|
20935
|
+
dryRun: commandOptions2.dryRun,
|
|
20936
|
+
force: commandOptions2.force,
|
|
20937
|
+
json: commandOptions2.json,
|
|
20938
|
+
stale: commandOptions2.stale,
|
|
18839
20939
|
stderr: options.stderr,
|
|
18840
20940
|
stdout: options.stdout
|
|
18841
20941
|
});
|
|
@@ -18844,13 +20944,13 @@ function attachCommandActions(program2, options) {
|
|
|
18844
20944
|
}
|
|
18845
20945
|
}
|
|
18846
20946
|
);
|
|
18847
|
-
const runRemovalCommand = async (branch,
|
|
20947
|
+
const runRemovalCommand = async (branch, commandOptions2 = {}) => {
|
|
18848
20948
|
const exitCode = await runRemoveCommand({
|
|
18849
20949
|
branch,
|
|
18850
20950
|
cwd: options.cwd,
|
|
18851
|
-
dryRun:
|
|
18852
|
-
force:
|
|
18853
|
-
json:
|
|
20951
|
+
dryRun: commandOptions2.dryRun,
|
|
20952
|
+
force: commandOptions2.force,
|
|
20953
|
+
json: commandOptions2.json,
|
|
18854
20954
|
stderr: options.stderr,
|
|
18855
20955
|
stdout: options.stdout
|
|
18856
20956
|
});
|
|
@@ -18870,14 +20970,14 @@ function attachCommandActions(program2, options) {
|
|
|
18870
20970
|
}
|
|
18871
20971
|
});
|
|
18872
20972
|
program2.commands.find((command) => command.name() === "warp")?.action(
|
|
18873
|
-
async (branch,
|
|
18874
|
-
const newFlag =
|
|
20973
|
+
async (branch, commandOptions2) => {
|
|
20974
|
+
const newFlag = commandOptions2.new;
|
|
18875
20975
|
const newWorktree = newFlag !== void 0 && newFlag !== false;
|
|
18876
20976
|
const newBranch = typeof newFlag === "string" ? newFlag : void 0;
|
|
18877
20977
|
const exitCode = await runWarpCommand({
|
|
18878
20978
|
branch: newWorktree ? newBranch ?? branch : branch,
|
|
18879
20979
|
cwd: options.cwd,
|
|
18880
|
-
json:
|
|
20980
|
+
json: commandOptions2.json,
|
|
18881
20981
|
newWorktree,
|
|
18882
20982
|
stderr: options.stderr,
|
|
18883
20983
|
stdout: options.stdout
|
|
@@ -18971,18 +21071,21 @@ async function main() {
|
|
|
18971
21071
|
}
|
|
18972
21072
|
async function warnIfMissingShellIntegration() {
|
|
18973
21073
|
try {
|
|
18974
|
-
const { config } = await loadGlobalConfig(
|
|
21074
|
+
const { config } = await loadGlobalConfig(homedir8());
|
|
18975
21075
|
if (!config.shellIntegration) {
|
|
18976
|
-
const
|
|
18977
|
-
const shellArg = shellBin && ["bash", "zsh", "fish"].includes(shellBin) ? ` ${shellBin}` : "";
|
|
21076
|
+
const setupCommand = isHeadless() ? legacyShellSetupCommand() : "gji init";
|
|
18978
21077
|
process.stderr.write(
|
|
18979
|
-
`gji: shell integration not set up \u2014 run
|
|
21078
|
+
`gji: shell integration not set up \u2014 run \`${setupCommand}\` to enable automatic cd.
|
|
18980
21079
|
`
|
|
18981
21080
|
);
|
|
18982
21081
|
}
|
|
18983
21082
|
} catch {
|
|
18984
21083
|
}
|
|
18985
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
|
+
}
|
|
18986
21089
|
void main();
|
|
18987
21090
|
/*! Bundled license information:
|
|
18988
21091
|
|