@solaqua/gji 0.8.1 → 0.10.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 +29 -33
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +37 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +63 -4
- package/dist/doctor.d.ts +28 -0
- package/dist/doctor.js +454 -0
- package/dist/gji-bundle.mjs +2507 -359
- 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/new.d.ts +1 -0
- package/dist/new.js +24 -1
- package/dist/open.d.ts +3 -1
- package/dist/open.js +16 -3
- 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 +284 -14
- 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 +4 -1
- package/man/man1/gji-open.1 +4 -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=()
|
|
@@ -15509,16 +16060,40 @@ _gji_completion() {
|
|
|
15509
16060
|
|
|
15510
16061
|
case "$command_name" in
|
|
15511
16062
|
new)
|
|
15512
|
-
COMPREPLY=( $(compgen -W "--detached --force --open --editor --dry-run --json --help" -- "$cur") )
|
|
16063
|
+
COMPREPLY=( $(compgen -W "--detached --from-current --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") )
|
|
@@ -15527,7 +16102,7 @@ _gji_completion() {
|
|
|
15527
16102
|
COMPREPLY=( $(compgen -W "--json --help" -- "$cur") )
|
|
15528
16103
|
;;
|
|
15529
16104
|
open)
|
|
15530
|
-
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) --editor --save --workspace --help" -- "$cur") )
|
|
16105
|
+
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) --editor --select --save --workspace --help" -- "$cur") )
|
|
15531
16106
|
;;
|
|
15532
16107
|
go|jump)
|
|
15533
16108
|
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) --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
|
|
@@ -15625,6 +16297,7 @@ complete -c gji -f
|
|
|
15625
16297
|
${commandLines}
|
|
15626
16298
|
|
|
15627
16299
|
complete -c gji -n '__fish_seen_subcommand_from new' -l detached -d 'create a detached worktree without a branch'
|
|
16300
|
+
complete -c gji -n '__fish_seen_subcommand_from new' -l from-current -d 'base the new branch on the current worktree instead of the main worktree'
|
|
15628
16301
|
complete -c gji -n '__fish_seen_subcommand_from new' -l force -d 'remove and recreate the worktree if the target path already exists'
|
|
15629
16302
|
complete -c gji -n '__fish_seen_subcommand_from new' -l open -d 'open the new worktree in an editor after creation'
|
|
15630
16303
|
complete -c gji -n '__fish_seen_subcommand_from new' -l editor -r -d 'editor CLI to use with --open (code, cursor, zed, \u2026)'
|
|
@@ -15632,20 +16305,29 @@ complete -c gji -n '__fish_seen_subcommand_from new' -l dry-run -d 'show what wo
|
|
|
15632
16305
|
complete -c gji -n '__fish_seen_subcommand_from new' -l json -d 'emit JSON on success or error instead of human-readable output'
|
|
15633
16306
|
|
|
15634
16307
|
complete -c gji -n '__fish_seen_subcommand_from init' -l write -d 'write the integration to the shell config file'
|
|
16308
|
+
complete -c gji -n '__fish_seen_subcommand_from init' -l json -d 'emit a JSON error in non-interactive mode'
|
|
15635
16309
|
${shellLines}
|
|
15636
16310
|
|
|
16311
|
+
complete -c gji -n '__fish_seen_subcommand_from doctor' -l fix -d 'apply safe automatic fixes after showing the plan'
|
|
16312
|
+
complete -c gji -n '__fish_seen_subcommand_from doctor' -l yes -d 'apply --fix without prompting'
|
|
16313
|
+
complete -c gji -n '__fish_seen_subcommand_from doctor' -l json -d 'emit diagnostic checks as JSON'
|
|
16314
|
+
|
|
15637
16315
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'bash' -d 'shell'
|
|
15638
16316
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'fish' -d 'shell'
|
|
15639
16317
|
complete -c gji -n '__fish_seen_subcommand_from completion' -a 'zsh' -d 'shell'
|
|
15640
16318
|
|
|
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'
|
|
16319
|
+
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'
|
|
16320
|
+
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'
|
|
16321
|
+
complete -c gji -n '__fish_seen_subcommand_from pr' -a 'open' -d 'open a pull request in the default browser'
|
|
16322
|
+
complete -c gji -n '__gji_should_complete_pr_select' -l select -d 'choose a pull request from any linked worktree'
|
|
16323
|
+
complete -c gji -n '__gji_should_complete_pr_target' -a '(__gji_pr_targets)' -d 'branch or PR number'
|
|
15643
16324
|
|
|
15644
16325
|
complete -c gji -n '__fish_seen_subcommand_from back' -l print -d 'print the resolved worktree path explicitly'
|
|
15645
16326
|
|
|
15646
16327
|
complete -c gji -n '__fish_seen_subcommand_from history' -l json -d 'print history as JSON'
|
|
15647
16328
|
|
|
15648
16329
|
complete -c gji -n '__fish_seen_subcommand_from open' -l editor -r -d 'editor CLI to use (code, cursor, zed, windsurf, subl, \u2026)'
|
|
16330
|
+
complete -c gji -n '__fish_seen_subcommand_from open' -l select -d 'choose a worktree with the interactive selector'
|
|
15649
16331
|
complete -c gji -n '__fish_seen_subcommand_from open' -l save -d 'save the chosen editor to global config'
|
|
15650
16332
|
complete -c gji -n '__fish_seen_subcommand_from open' -l workspace -d 'generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)'
|
|
15651
16333
|
complete -c gji -n '__fish_seen_subcommand_from open' -a '(__gji_worktree_branches)' -d 'worktree branch'
|
|
@@ -15701,30 +16383,99 @@ __gji_worktree_branches() {
|
|
|
15701
16383
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
15702
16384
|
}
|
|
15703
16385
|
|
|
16386
|
+
typeset -g __gji_pr_targets_cache=()
|
|
16387
|
+
typeset -g __gji_pr_targets_cache_at=0
|
|
16388
|
+
typeset -g __gji_pr_targets_cache_loaded=0
|
|
16389
|
+
typeset -g __gji_pr_targets_cache_repo=""
|
|
16390
|
+
|
|
16391
|
+
__gji_run_bounded() {
|
|
16392
|
+
local output_file pid status attempt
|
|
16393
|
+
output_file=$(mktemp "\${TMPDIR:-/tmp}/gji-completion.XXXXXX") || return 124
|
|
16394
|
+
"$@" >"$output_file" 2>/dev/null &
|
|
16395
|
+
pid=$!
|
|
16396
|
+
for ((attempt = 0; attempt < 20; attempt++)); do
|
|
16397
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
16398
|
+
wait "$pid"
|
|
16399
|
+
status=$?
|
|
16400
|
+
cat "$output_file"
|
|
16401
|
+
rm -f "$output_file"
|
|
16402
|
+
return "$status"
|
|
16403
|
+
fi
|
|
16404
|
+
sleep 0.1
|
|
16405
|
+
done
|
|
16406
|
+
kill "$pid" 2>/dev/null
|
|
16407
|
+
wait "$pid" 2>/dev/null
|
|
16408
|
+
rm -f "$output_file"
|
|
16409
|
+
return 124
|
|
16410
|
+
}
|
|
16411
|
+
|
|
16412
|
+
__gji_pr_targets() {
|
|
16413
|
+
local now repo_key
|
|
16414
|
+
local -a branches targets
|
|
16415
|
+
now=$(date +%s)
|
|
16416
|
+
repo_key=$(command git rev-parse --show-toplevel 2>/dev/null)
|
|
16417
|
+
if (( __gji_pr_targets_cache_loaded == 1 && "\${repo_key}" == "\${__gji_pr_targets_cache_repo}" && now - __gji_pr_targets_cache_at < 30 )); then
|
|
16418
|
+
print -rl -- "\${__gji_pr_targets_cache[@]}"
|
|
16419
|
+
return
|
|
16420
|
+
fi
|
|
16421
|
+
|
|
16422
|
+
branches=("\${(@f)$(__gji_worktree_branches)}")
|
|
16423
|
+
targets=()
|
|
16424
|
+
if [[ -n "\${repo_key}" && $+commands[gh] -eq 1 ]]; then
|
|
16425
|
+
targets=("\${(@f)$(__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
|
|
16426
|
+
tr '{},' '\\n' | sed -n 's/.*"number":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
|
|
16427
|
+
elif [[ -n "\${repo_key}" && $+commands[glab] -eq 1 ]]; then
|
|
16428
|
+
targets=("\${(@f)$(__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
|
|
16429
|
+
tr '{},' '\\n' | sed -n 's/.*"iid":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
|
|
16430
|
+
elif [[ -n "\${repo_key}" && $+commands[bb] -eq 1 ]]; then
|
|
16431
|
+
targets=("\${(@f)$(__gji_run_bounded bb pr list --state OPEN --format json |
|
|
16432
|
+
tr '{},' '\\n' | sed -n 's/.*"id":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
|
|
16433
|
+
fi
|
|
16434
|
+
|
|
16435
|
+
__gji_pr_targets_cache=("\${branches[@]}" "\${targets[@]}")
|
|
16436
|
+
__gji_pr_targets_cache_at="$now"
|
|
16437
|
+
__gji_pr_targets_cache_loaded=1
|
|
16438
|
+
__gji_pr_targets_cache_repo="$repo_key"
|
|
16439
|
+
print -rl -- "\${__gji_pr_targets_cache[@]}"
|
|
16440
|
+
}
|
|
16441
|
+
|
|
15704
16442
|
local context state line
|
|
15705
|
-
local -a
|
|
16443
|
+
local -a command_entries worktree_branches
|
|
15706
16444
|
|
|
15707
|
-
|
|
16445
|
+
command_entries=(
|
|
15708
16446
|
${commandLines}
|
|
15709
16447
|
)
|
|
15710
16448
|
|
|
15711
16449
|
if (( CURRENT == 2 )); then
|
|
15712
|
-
_describe 'command'
|
|
16450
|
+
_describe 'command' command_entries
|
|
15713
16451
|
return
|
|
15714
16452
|
fi
|
|
15715
16453
|
|
|
15716
16454
|
case "\${words[2]}" in
|
|
15717
16455
|
new)
|
|
15718
|
-
_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: '
|
|
16456
|
+
_arguments '--detached[create a detached worktree without a branch]' '--from-current[base the new branch on the current worktree instead of the main worktree]' '--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
16457
|
;;
|
|
15720
16458
|
init)
|
|
15721
|
-
_arguments '--write[write the integration to the shell config file]' '2:shell:(${shells})'
|
|
16459
|
+
_arguments '--write[write the integration to the shell config file]' '--json[emit a JSON error in non-interactive mode]' '2:shell:(${shells})'
|
|
16460
|
+
;;
|
|
16461
|
+
doctor)
|
|
16462
|
+
_arguments '--fix[apply safe automatic fixes after showing the plan]' '--yes[apply --fix without prompting]' '--json[emit diagnostic checks as JSON]'
|
|
15722
16463
|
;;
|
|
15723
16464
|
completion)
|
|
15724
16465
|
_arguments '2:shell:(${shells})'
|
|
15725
16466
|
;;
|
|
15726
16467
|
pr)
|
|
15727
|
-
|
|
16468
|
+
if [[ "\${words[3]}" == "open" ]]; then
|
|
16469
|
+
if (( \${words[(I)--select]} )); then
|
|
16470
|
+
_arguments
|
|
16471
|
+
elif [[ -n "\${words[4]}" && "\${words[4]}" != -* ]]; then
|
|
16472
|
+
_arguments
|
|
16473
|
+
else
|
|
16474
|
+
_arguments '--select[choose a pull request from any linked worktree]' '4:branch or PR number:->pr_targets'
|
|
16475
|
+
fi
|
|
16476
|
+
else
|
|
16477
|
+
_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)'
|
|
16478
|
+
fi
|
|
15728
16479
|
;;
|
|
15729
16480
|
back)
|
|
15730
16481
|
_arguments '--print[print the resolved worktree path explicitly]' '2:steps: '
|
|
@@ -15733,7 +16484,7 @@ case "\${words[2]}" in
|
|
|
15733
16484
|
_arguments '--json[print history as JSON]'
|
|
15734
16485
|
;;
|
|
15735
16486
|
open)
|
|
15736
|
-
_arguments '--editor[editor CLI to use (code, cursor, zed, windsurf, subl, \u2026)]:editor:' '--save[save the chosen editor to global config]' '--workspace[generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)]' '2:branch:->worktrees'
|
|
16487
|
+
_arguments '--editor[editor CLI to use (code, cursor, zed, windsurf, subl, \u2026)]:editor:' '--select[choose a worktree with the interactive selector]' '--save[save the chosen editor to global config]' '--workspace[generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)]' '2:branch:->worktrees'
|
|
15737
16488
|
;;
|
|
15738
16489
|
go|jump)
|
|
15739
16490
|
_arguments '--print[print the resolved worktree path explicitly]' '2:branch:->worktrees'
|
|
@@ -15787,6 +16538,10 @@ case "$state" in
|
|
|
15787
16538
|
worktree_branches=(\${(@f)$(__gji_worktree_branches)})
|
|
15788
16539
|
_describe 'worktree branch' worktree_branches
|
|
15789
16540
|
;;
|
|
16541
|
+
pr_targets)
|
|
16542
|
+
worktree_branches=(\${(@f)$(__gji_pr_targets)})
|
|
16543
|
+
_describe 'branch or PR number' worktree_branches
|
|
16544
|
+
;;
|
|
15790
16545
|
config_keys)
|
|
15791
16546
|
_values 'config key' ${configKeys}
|
|
15792
16547
|
;;
|
|
@@ -15850,51 +16605,13 @@ function writeJson(stdout2, value) {
|
|
|
15850
16605
|
`);
|
|
15851
16606
|
}
|
|
15852
16607
|
|
|
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
|
-
}
|
|
16608
|
+
// src/doctor.ts
|
|
16609
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
16610
|
+
import { constants as constants3 } from "node:fs";
|
|
16611
|
+
import { access as access4, readFile as readFile4 } from "node:fs/promises";
|
|
16612
|
+
import { homedir as homedir5 } from "node:os";
|
|
16613
|
+
import { basename as basename4, dirname as dirname5, join as join6 } from "node:path";
|
|
16614
|
+
import { promisify as promisify5 } from "node:util";
|
|
15898
16615
|
|
|
15899
16616
|
// src/editor.ts
|
|
15900
16617
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -15934,14 +16651,774 @@ async function defaultSpawnEditor(cli, args) {
|
|
|
15934
16651
|
child.unref();
|
|
15935
16652
|
}
|
|
15936
16653
|
|
|
16654
|
+
// src/repo-registry.ts
|
|
16655
|
+
import { randomUUID } from "node:crypto";
|
|
16656
|
+
import { constants } from "node:fs";
|
|
16657
|
+
import {
|
|
16658
|
+
access as access2,
|
|
16659
|
+
mkdir as mkdir3,
|
|
16660
|
+
readFile as readFile3,
|
|
16661
|
+
realpath,
|
|
16662
|
+
rename,
|
|
16663
|
+
rm as rm2,
|
|
16664
|
+
writeFile as writeFile4
|
|
16665
|
+
} from "node:fs/promises";
|
|
16666
|
+
import { homedir as homedir4 } from "node:os";
|
|
16667
|
+
import { basename as basename3, dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
|
|
16668
|
+
var REGISTRY_FILE_NAME = "repos.json";
|
|
16669
|
+
var MAX_REGISTRY_ENTRIES = 100;
|
|
16670
|
+
var registryLocks = /* @__PURE__ */ new Map();
|
|
16671
|
+
function REGISTRY_FILE_PATH(home = homedir4()) {
|
|
16672
|
+
const configDir = process.env.GJI_CONFIG_DIR;
|
|
16673
|
+
if (configDir) {
|
|
16674
|
+
return join4(resolve4(configDir), REGISTRY_FILE_NAME);
|
|
16675
|
+
}
|
|
16676
|
+
return join4(home, GLOBAL_CONFIG_DIRECTORY, REGISTRY_FILE_NAME);
|
|
16677
|
+
}
|
|
16678
|
+
async function loadRegistry(home = homedir4()) {
|
|
16679
|
+
const path9 = REGISTRY_FILE_PATH(home);
|
|
16680
|
+
try {
|
|
16681
|
+
const raw = await readFile3(path9, "utf8");
|
|
16682
|
+
const parsed = JSON.parse(raw);
|
|
16683
|
+
if (!Array.isArray(parsed)) return [];
|
|
16684
|
+
return parsed.filter(isRegistryEntry);
|
|
16685
|
+
} catch {
|
|
16686
|
+
return [];
|
|
16687
|
+
}
|
|
16688
|
+
}
|
|
16689
|
+
async function canonicalizeRepoPath(repoPath) {
|
|
16690
|
+
try {
|
|
16691
|
+
return await realpath(repoPath);
|
|
16692
|
+
} catch {
|
|
16693
|
+
return resolve4(repoPath);
|
|
16694
|
+
}
|
|
16695
|
+
}
|
|
16696
|
+
async function registerRepo(repoPath, home = homedir4()) {
|
|
16697
|
+
const registryPath = REGISTRY_FILE_PATH(home);
|
|
16698
|
+
await withRegistryLock(registryPath, async () => {
|
|
16699
|
+
const existing = await normalizeRegistryForWrite(await loadRegistry(home));
|
|
16700
|
+
const canonicalRepoPath = await canonicalizeRepoPath(repoPath);
|
|
16701
|
+
if (existing.length > 0 && existing[0].path === canonicalRepoPath) return;
|
|
16702
|
+
const entry = {
|
|
16703
|
+
lastUsed: Date.now(),
|
|
16704
|
+
name: basename3(canonicalRepoPath),
|
|
16705
|
+
path: canonicalRepoPath
|
|
16706
|
+
};
|
|
16707
|
+
const filtered = existing.filter((e2) => e2.path !== canonicalRepoPath);
|
|
16708
|
+
const next = [entry, ...filtered].slice(0, MAX_REGISTRY_ENTRIES);
|
|
16709
|
+
await writeRegistry(next, registryPath);
|
|
16710
|
+
});
|
|
16711
|
+
}
|
|
16712
|
+
async function removeMissingRegistryEntries(paths, home = homedir4()) {
|
|
16713
|
+
if (paths.size === 0) return { removedPaths: [], skippedPaths: [] };
|
|
16714
|
+
const registryPath = REGISTRY_FILE_PATH(home);
|
|
16715
|
+
return withRegistryLock(registryPath, async () => {
|
|
16716
|
+
const entries = await loadRegistry(home);
|
|
16717
|
+
const candidates = entries.filter((entry) => paths.has(entry.path));
|
|
16718
|
+
const status = await Promise.all(
|
|
16719
|
+
candidates.map(async (entry) => ({
|
|
16720
|
+
entry,
|
|
16721
|
+
missing: await isMissingPath(entry.path)
|
|
16722
|
+
}))
|
|
16723
|
+
);
|
|
16724
|
+
const removedPaths = status.filter(({ missing }) => missing).map(({ entry }) => entry.path);
|
|
16725
|
+
const skippedPaths = status.filter(({ missing }) => !missing).map(({ entry }) => entry.path);
|
|
16726
|
+
if (removedPaths.length > 0) {
|
|
16727
|
+
await writeRegistry(
|
|
16728
|
+
entries.filter((entry) => !removedPaths.includes(entry.path)),
|
|
16729
|
+
registryPath
|
|
16730
|
+
);
|
|
16731
|
+
}
|
|
16732
|
+
return { removedPaths, skippedPaths };
|
|
16733
|
+
});
|
|
16734
|
+
}
|
|
16735
|
+
async function withRegistryLock(registryPath, operation) {
|
|
16736
|
+
const previous = registryLocks.get(registryPath);
|
|
16737
|
+
let release;
|
|
16738
|
+
const current = new Promise((resolve6) => {
|
|
16739
|
+
release = resolve6;
|
|
16740
|
+
});
|
|
16741
|
+
registryLocks.set(registryPath, current);
|
|
16742
|
+
if (previous) await previous;
|
|
16743
|
+
try {
|
|
16744
|
+
return await operation();
|
|
16745
|
+
} finally {
|
|
16746
|
+
release();
|
|
16747
|
+
if (registryLocks.get(registryPath) === current) {
|
|
16748
|
+
registryLocks.delete(registryPath);
|
|
16749
|
+
}
|
|
16750
|
+
}
|
|
16751
|
+
}
|
|
16752
|
+
async function isMissingPath(path9) {
|
|
16753
|
+
try {
|
|
16754
|
+
await access2(path9, constants.F_OK);
|
|
16755
|
+
return false;
|
|
16756
|
+
} catch (error) {
|
|
16757
|
+
return isMissingPathError(error);
|
|
16758
|
+
}
|
|
16759
|
+
}
|
|
16760
|
+
function isMissingPathError(error) {
|
|
16761
|
+
if (!(error instanceof Error) || !("code" in error)) return false;
|
|
16762
|
+
const code = error.code;
|
|
16763
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
16764
|
+
}
|
|
16765
|
+
async function writeRegistry(entries, registryPath) {
|
|
16766
|
+
await mkdir3(dirname4(registryPath), { recursive: true });
|
|
16767
|
+
const temporaryPath = `${registryPath}.tmp-${process.pid}-${randomUUID()}`;
|
|
16768
|
+
try {
|
|
16769
|
+
await writeFile4(
|
|
16770
|
+
temporaryPath,
|
|
16771
|
+
`${JSON.stringify(entries, null, 2)}
|
|
16772
|
+
`,
|
|
16773
|
+
"utf8"
|
|
16774
|
+
);
|
|
16775
|
+
await rename(temporaryPath, registryPath);
|
|
16776
|
+
} catch (error) {
|
|
16777
|
+
await rm2(temporaryPath, { force: true }).catch(() => void 0);
|
|
16778
|
+
throw error;
|
|
16779
|
+
}
|
|
16780
|
+
}
|
|
16781
|
+
async function normalizeRegistryForWrite(entries) {
|
|
16782
|
+
const normalized = [];
|
|
16783
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
16784
|
+
for (const entry of entries) {
|
|
16785
|
+
const canonicalPath = await canonicalizeRepoPath(entry.path);
|
|
16786
|
+
if (seenPaths.has(canonicalPath)) {
|
|
16787
|
+
continue;
|
|
16788
|
+
}
|
|
16789
|
+
seenPaths.add(canonicalPath);
|
|
16790
|
+
normalized.push({
|
|
16791
|
+
...entry,
|
|
16792
|
+
name: basename3(canonicalPath),
|
|
16793
|
+
path: canonicalPath
|
|
16794
|
+
});
|
|
16795
|
+
}
|
|
16796
|
+
return normalized;
|
|
16797
|
+
}
|
|
16798
|
+
function isRegistryEntry(value) {
|
|
16799
|
+
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";
|
|
16800
|
+
}
|
|
16801
|
+
|
|
16802
|
+
// src/shell-setup.ts
|
|
16803
|
+
import { constants as constants2 } from "node:fs";
|
|
16804
|
+
import { access as access3 } from "node:fs/promises";
|
|
16805
|
+
import { delimiter, join as join5 } from "node:path";
|
|
16806
|
+
var SHELL_INTEGRATION_START_MARKER = "# >>> gji init >>>";
|
|
16807
|
+
var SHELL_INTEGRATION_END_MARKER = "# <<< gji init <<<";
|
|
16808
|
+
var ONBOARDING_SHELL_INTEGRATION_START_MARKER = "# >>> gji shell integration >>>";
|
|
16809
|
+
var ONBOARDING_SHELL_INTEGRATION_END_MARKER = "# <<< gji shell integration <<<";
|
|
16810
|
+
async function executableExists(command) {
|
|
16811
|
+
const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join5(directory, command));
|
|
16812
|
+
for (const path9 of candidates) {
|
|
16813
|
+
try {
|
|
16814
|
+
await access3(path9, constants2.X_OK);
|
|
16815
|
+
return true;
|
|
16816
|
+
} catch {
|
|
16817
|
+
}
|
|
16818
|
+
}
|
|
16819
|
+
return false;
|
|
16820
|
+
}
|
|
16821
|
+
function resolveShellConfigPath(shell, home) {
|
|
16822
|
+
switch (shell) {
|
|
16823
|
+
case "bash":
|
|
16824
|
+
return join5(home, ".bashrc");
|
|
16825
|
+
case "fish":
|
|
16826
|
+
return join5(home, ".config", "fish", "config.fish");
|
|
16827
|
+
case "zsh":
|
|
16828
|
+
return join5(home, ".zshrc");
|
|
16829
|
+
}
|
|
16830
|
+
}
|
|
16831
|
+
function resolveCompletionPath(shell, home) {
|
|
16832
|
+
switch (shell) {
|
|
16833
|
+
case "bash":
|
|
16834
|
+
return join5(
|
|
16835
|
+
home,
|
|
16836
|
+
".local",
|
|
16837
|
+
"share",
|
|
16838
|
+
"bash-completion",
|
|
16839
|
+
"completions",
|
|
16840
|
+
"gji"
|
|
16841
|
+
);
|
|
16842
|
+
case "fish":
|
|
16843
|
+
return join5(home, ".config", "fish", "completions", "gji.fish");
|
|
16844
|
+
case "zsh":
|
|
16845
|
+
return join5(home, ".zsh", "completions", "_gji");
|
|
16846
|
+
}
|
|
16847
|
+
}
|
|
16848
|
+
function hasShellIntegration(config, shell) {
|
|
16849
|
+
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)) {
|
|
16850
|
+
return true;
|
|
16851
|
+
}
|
|
16852
|
+
if (shell === "fish") return false;
|
|
16853
|
+
return new RegExp(
|
|
16854
|
+
`^[\\t ]*eval\\s+["']?\\$\\(\\s*gji\\s+init\\s+${shell}\\s*\\)["']?\\s*(?:#.*)?$`,
|
|
16855
|
+
"m"
|
|
16856
|
+
).test(config);
|
|
16857
|
+
}
|
|
16858
|
+
|
|
16859
|
+
// src/doctor.ts
|
|
16860
|
+
var execFileAsync4 = promisify5(execFile4);
|
|
16861
|
+
var MINIMUM_GIT_VERSION = { major: 2, minor: 17 };
|
|
16862
|
+
async function runDoctorCommand(options) {
|
|
16863
|
+
if (options.yes && !options.fix) {
|
|
16864
|
+
const message = "--yes requires --fix";
|
|
16865
|
+
if (options.json) {
|
|
16866
|
+
(options.stderr ?? options.stdout)(
|
|
16867
|
+
`${JSON.stringify({ error: message })}
|
|
16868
|
+
`
|
|
16869
|
+
);
|
|
16870
|
+
} else {
|
|
16871
|
+
(options.stderr ?? options.stdout)(`gji doctor: ${message}
|
|
16872
|
+
`);
|
|
16873
|
+
}
|
|
16874
|
+
return 1;
|
|
16875
|
+
}
|
|
16876
|
+
const home = options.home ?? homedir5();
|
|
16877
|
+
const shell = resolveSupportedShell(
|
|
16878
|
+
void 0,
|
|
16879
|
+
options.shell ?? process.env.SHELL
|
|
16880
|
+
);
|
|
16881
|
+
let inspection = await collectDoctorInspection(options.cwd, home, shell);
|
|
16882
|
+
let fixes = [];
|
|
16883
|
+
if (options.fix) {
|
|
16884
|
+
fixes = buildDoctorFixes(inspection.missingRegistryPaths);
|
|
16885
|
+
if (fixes.length > 0) {
|
|
16886
|
+
const approval = await requestFixApproval(fixes, options);
|
|
16887
|
+
if (approval === "apply") {
|
|
16888
|
+
fixes = await applyDoctorFixes(fixes, home);
|
|
16889
|
+
} else {
|
|
16890
|
+
fixes = fixes.map((fix) => ({
|
|
16891
|
+
...fix,
|
|
16892
|
+
status: approval,
|
|
16893
|
+
hint: approval === "pending" ? "re-run with --yes to apply this fix without a prompt" : "run gji doctor --fix again to review this fix"
|
|
16894
|
+
}));
|
|
16895
|
+
}
|
|
16896
|
+
}
|
|
16897
|
+
inspection = await collectDoctorInspection(options.cwd, home, shell);
|
|
16898
|
+
}
|
|
16899
|
+
const problems = inspection.checks.filter(
|
|
16900
|
+
(check) => check.status === "fail"
|
|
16901
|
+
).length;
|
|
16902
|
+
if (options.json) {
|
|
16903
|
+
const output = options.fix ? { checks: inspection.checks, problems, fixes } : { checks: inspection.checks, problems };
|
|
16904
|
+
options.stdout(`${JSON.stringify(output)}
|
|
16905
|
+
`);
|
|
16906
|
+
} else {
|
|
16907
|
+
options.stdout(renderDoctorChecks(inspection.checks, problems));
|
|
16908
|
+
if (options.fix) {
|
|
16909
|
+
options.stdout(renderDoctorFixes(fixes));
|
|
16910
|
+
}
|
|
16911
|
+
}
|
|
16912
|
+
return problems > 0 ? 1 : 0;
|
|
16913
|
+
}
|
|
16914
|
+
async function collectDoctorInspection(cwd, home, shell) {
|
|
16915
|
+
const repository = await detectRepositoryOrSkip(cwd);
|
|
16916
|
+
const globalConfig = await inspectConfig(
|
|
16917
|
+
GLOBAL_CONFIG_FILE_PATH(home),
|
|
16918
|
+
"global",
|
|
16919
|
+
KNOWN_GLOBAL_CONFIG_KEYS
|
|
16920
|
+
);
|
|
16921
|
+
const localConfig = repository ? await inspectConfig(
|
|
16922
|
+
join6(repository.repoRoot, CONFIG_FILE_NAME),
|
|
16923
|
+
"local",
|
|
16924
|
+
KNOWN_CONFIG_KEYS
|
|
16925
|
+
) : null;
|
|
16926
|
+
const effectiveConfig = resolveEffectiveConfig(
|
|
16927
|
+
globalConfig.config,
|
|
16928
|
+
localConfig?.config ?? {},
|
|
16929
|
+
repository?.repoRoot,
|
|
16930
|
+
home
|
|
16931
|
+
);
|
|
16932
|
+
const registry = await inspectRegistry(home);
|
|
16933
|
+
return {
|
|
16934
|
+
checks: [
|
|
16935
|
+
await checkGitVersion(),
|
|
16936
|
+
await checkShellIntegration(shell, home),
|
|
16937
|
+
await checkCompletion(shell, home),
|
|
16938
|
+
globalConfig.check,
|
|
16939
|
+
localConfig?.check ?? skippedCheck(
|
|
16940
|
+
"local-config",
|
|
16941
|
+
"local config not checked outside a Git repository"
|
|
16942
|
+
),
|
|
16943
|
+
await checkWorktreeBase(repository, effectiveConfig, home),
|
|
16944
|
+
registry.check,
|
|
16945
|
+
await checkEditor(effectiveConfig)
|
|
16946
|
+
],
|
|
16947
|
+
missingRegistryPaths: registry.missingPaths
|
|
16948
|
+
};
|
|
16949
|
+
}
|
|
16950
|
+
function buildDoctorFixes(missingRegistryPaths) {
|
|
16951
|
+
if (missingRegistryPaths.length === 0) return [];
|
|
16952
|
+
const count = missingRegistryPaths.length;
|
|
16953
|
+
return [
|
|
16954
|
+
{
|
|
16955
|
+
id: "repo-registry",
|
|
16956
|
+
message: `remove ${count} stale ${count === 1 ? "repository entry" : "repository entries"} from the registry`,
|
|
16957
|
+
paths: missingRegistryPaths,
|
|
16958
|
+
status: "pending"
|
|
16959
|
+
}
|
|
16960
|
+
];
|
|
16961
|
+
}
|
|
16962
|
+
async function requestFixApproval(fixes, options) {
|
|
16963
|
+
if (options.yes) return "apply";
|
|
16964
|
+
if (!isDoctorInteractive(options)) return "pending";
|
|
16965
|
+
const confirmed = options.confirmFixes ? await options.confirmFixes(fixes) : await ye({
|
|
16966
|
+
initialValue: true,
|
|
16967
|
+
message: `Apply ${fixes.length} automatic ${fixes.length === 1 ? "fix" : "fixes"} (${fixes.flatMap((fix) => fix.paths ?? []).join(", ")})?`
|
|
16968
|
+
});
|
|
16969
|
+
if (pD(confirmed) || !confirmed) return "declined";
|
|
16970
|
+
return "apply";
|
|
16971
|
+
}
|
|
16972
|
+
function isDoctorInteractive(options) {
|
|
16973
|
+
if (options.json || isHeadless()) return false;
|
|
16974
|
+
if (options.interactive !== void 0) return options.interactive;
|
|
16975
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
16976
|
+
}
|
|
16977
|
+
async function applyDoctorFixes(fixes, home) {
|
|
16978
|
+
return Promise.all(
|
|
16979
|
+
fixes.map(async (fix) => {
|
|
16980
|
+
try {
|
|
16981
|
+
const result = await removeMissingRegistryEntries(
|
|
16982
|
+
new Set(fix.paths ?? []),
|
|
16983
|
+
home
|
|
16984
|
+
);
|
|
16985
|
+
const removedCount = result.removedPaths.length;
|
|
16986
|
+
const skippedCount = result.skippedPaths.length;
|
|
16987
|
+
return {
|
|
16988
|
+
...fix,
|
|
16989
|
+
message: removedCount === 0 ? "no stale repository entries were removed" : `removed ${removedCount} stale ${removedCount === 1 ? "repository entry" : "repository entries"} from the registry`,
|
|
16990
|
+
status: removedCount === 0 ? "skipped" : "applied",
|
|
16991
|
+
hint: skippedCount > 0 ? `${skippedCount} path(s) were no longer confirmed missing` : void 0
|
|
16992
|
+
};
|
|
16993
|
+
} catch (error) {
|
|
16994
|
+
return {
|
|
16995
|
+
...fix,
|
|
16996
|
+
status: "failed",
|
|
16997
|
+
hint: error instanceof Error ? error.message : String(error)
|
|
16998
|
+
};
|
|
16999
|
+
}
|
|
17000
|
+
})
|
|
17001
|
+
);
|
|
17002
|
+
}
|
|
17003
|
+
async function detectRepositoryOrSkip(cwd) {
|
|
17004
|
+
try {
|
|
17005
|
+
return await detectRepository(cwd);
|
|
17006
|
+
} catch {
|
|
17007
|
+
return null;
|
|
17008
|
+
}
|
|
17009
|
+
}
|
|
17010
|
+
async function inspectConfig(path9, label, knownKeys) {
|
|
17011
|
+
try {
|
|
17012
|
+
const contents = await readFile4(path9, "utf8");
|
|
17013
|
+
const value = JSON.parse(contents);
|
|
17014
|
+
if (!isConfigObject(value)) {
|
|
17015
|
+
return {
|
|
17016
|
+
check: failedCheck(
|
|
17017
|
+
`${label}-config`,
|
|
17018
|
+
`${label} config must contain a JSON object (${path9})`,
|
|
17019
|
+
"replace the file contents with a JSON object"
|
|
17020
|
+
),
|
|
17021
|
+
config: {}
|
|
17022
|
+
};
|
|
17023
|
+
}
|
|
17024
|
+
const unknownKeys = Object.keys(value).filter((key) => !knownKeys.has(key));
|
|
17025
|
+
const warning = unknownKeys.length > 0 ? `; warning: unknown ${unknownKeys.length === 1 ? "key" : "keys"} ${unknownKeys.map((key) => `"${key}"`).join(", ")}` : "";
|
|
17026
|
+
return {
|
|
17027
|
+
check: okCheck(
|
|
17028
|
+
`${label}-config`,
|
|
17029
|
+
`${label} config valid (${path9}${warning})`
|
|
17030
|
+
),
|
|
17031
|
+
config: value
|
|
17032
|
+
};
|
|
17033
|
+
} catch (error) {
|
|
17034
|
+
if (isMissingFileError2(error)) {
|
|
17035
|
+
return {
|
|
17036
|
+
check: okCheck(
|
|
17037
|
+
`${label}-config`,
|
|
17038
|
+
`${label} config not found (optional)`
|
|
17039
|
+
),
|
|
17040
|
+
config: {}
|
|
17041
|
+
};
|
|
17042
|
+
}
|
|
17043
|
+
return {
|
|
17044
|
+
check: failedCheck(
|
|
17045
|
+
`${label}-config`,
|
|
17046
|
+
`${label} config is invalid (${path9})`,
|
|
17047
|
+
"fix the JSON syntax and run gji doctor again"
|
|
17048
|
+
),
|
|
17049
|
+
config: {}
|
|
17050
|
+
};
|
|
17051
|
+
}
|
|
17052
|
+
}
|
|
17053
|
+
function isConfigObject(value) {
|
|
17054
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17055
|
+
}
|
|
17056
|
+
function isMissingFileError2(error) {
|
|
17057
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
17058
|
+
}
|
|
17059
|
+
function resolveEffectiveConfig(globalConfig, localConfig, repoRoot, home) {
|
|
17060
|
+
const globalBase = { ...globalConfig };
|
|
17061
|
+
const repos = globalBase.repos;
|
|
17062
|
+
delete globalBase.repos;
|
|
17063
|
+
return {
|
|
17064
|
+
...globalBase,
|
|
17065
|
+
...resolvePerRepoConfig(repos, repoRoot, home),
|
|
17066
|
+
...localConfig
|
|
17067
|
+
};
|
|
17068
|
+
}
|
|
17069
|
+
function resolvePerRepoConfig(repos, repoRoot, home) {
|
|
17070
|
+
if (!repoRoot || !isConfigObject(repos)) {
|
|
17071
|
+
return {};
|
|
17072
|
+
}
|
|
17073
|
+
for (const [path9, config] of Object.entries(repos)) {
|
|
17074
|
+
if (expandTilde2(path9, home) === repoRoot && isConfigObject(config)) {
|
|
17075
|
+
return config;
|
|
17076
|
+
}
|
|
17077
|
+
}
|
|
17078
|
+
return {};
|
|
17079
|
+
}
|
|
17080
|
+
function expandTilde2(path9, home) {
|
|
17081
|
+
if (path9 === "~") return home;
|
|
17082
|
+
if (path9.startsWith("~/")) return join6(home, path9.slice(2));
|
|
17083
|
+
return path9;
|
|
17084
|
+
}
|
|
17085
|
+
async function checkGitVersion() {
|
|
17086
|
+
try {
|
|
17087
|
+
const { stdout: stdout2 } = await execFileAsync4("git", ["--version"]);
|
|
17088
|
+
const version = parseGitVersion(stdout2);
|
|
17089
|
+
if (!version) {
|
|
17090
|
+
return failedCheck(
|
|
17091
|
+
"git-version",
|
|
17092
|
+
"could not parse the installed Git version",
|
|
17093
|
+
"install Git 2.17 or newer"
|
|
17094
|
+
);
|
|
17095
|
+
}
|
|
17096
|
+
if (isGitVersionSupported(version)) {
|
|
17097
|
+
return okCheck("git-version", `git ${version.raw}`);
|
|
17098
|
+
}
|
|
17099
|
+
return failedCheck(
|
|
17100
|
+
"git-version",
|
|
17101
|
+
`git ${version.raw} is too old (requires Git 2.17 or newer)`,
|
|
17102
|
+
"upgrade Git and run gji doctor again"
|
|
17103
|
+
);
|
|
17104
|
+
} catch {
|
|
17105
|
+
return failedCheck(
|
|
17106
|
+
"git-version",
|
|
17107
|
+
"git is not available on PATH",
|
|
17108
|
+
"install Git 2.17 or newer"
|
|
17109
|
+
);
|
|
17110
|
+
}
|
|
17111
|
+
}
|
|
17112
|
+
function parseGitVersion(output) {
|
|
17113
|
+
const match = /(?:^|\s)(\d+)\.(\d+)(?:\.(\d+))?/.exec(output);
|
|
17114
|
+
if (!match) return null;
|
|
17115
|
+
return {
|
|
17116
|
+
major: Number(match[1]),
|
|
17117
|
+
minor: Number(match[2]),
|
|
17118
|
+
raw: match.slice(1).filter(Boolean).join(".")
|
|
17119
|
+
};
|
|
17120
|
+
}
|
|
17121
|
+
function isGitVersionSupported(version) {
|
|
17122
|
+
return version.major > MINIMUM_GIT_VERSION.major || version.major === MINIMUM_GIT_VERSION.major && version.minor >= MINIMUM_GIT_VERSION.minor;
|
|
17123
|
+
}
|
|
17124
|
+
async function checkShellIntegration(shell, home) {
|
|
17125
|
+
if (!shell) {
|
|
17126
|
+
return skippedCheck(
|
|
17127
|
+
"shell-integration",
|
|
17128
|
+
"shell integration not checked (unable to detect a supported shell)"
|
|
17129
|
+
);
|
|
17130
|
+
}
|
|
17131
|
+
const rcPath = resolveShellConfigPath(shell, home);
|
|
17132
|
+
try {
|
|
17133
|
+
const contents = await readFile4(rcPath, "utf8");
|
|
17134
|
+
if (hasShellIntegration(contents, shell)) {
|
|
17135
|
+
return okCheck(
|
|
17136
|
+
"shell-integration",
|
|
17137
|
+
`${shell} integration found in ${rcPath}`
|
|
17138
|
+
);
|
|
17139
|
+
}
|
|
17140
|
+
} catch (error) {
|
|
17141
|
+
if (isMissingFileError2(error)) {
|
|
17142
|
+
return failedCheck(
|
|
17143
|
+
"shell-integration",
|
|
17144
|
+
`shell integration not found because ${rcPath} does not exist`,
|
|
17145
|
+
`create ${rcPath}, then add: eval "$(gji init ${shell})"`
|
|
17146
|
+
);
|
|
17147
|
+
}
|
|
17148
|
+
return failedCheck(
|
|
17149
|
+
"shell-integration",
|
|
17150
|
+
`could not read ${rcPath}`,
|
|
17151
|
+
`add: eval "$(gji init ${shell})"`
|
|
17152
|
+
);
|
|
17153
|
+
}
|
|
17154
|
+
return failedCheck(
|
|
17155
|
+
"shell-integration",
|
|
17156
|
+
`shell integration not found in ${rcPath}`,
|
|
17157
|
+
`add: eval "$(gji init ${shell})"`
|
|
17158
|
+
);
|
|
17159
|
+
}
|
|
17160
|
+
async function checkCompletion(shell, home) {
|
|
17161
|
+
if (!shell) {
|
|
17162
|
+
return skippedCheck(
|
|
17163
|
+
"completion",
|
|
17164
|
+
"shell completion not checked (unable to detect a supported shell)"
|
|
17165
|
+
);
|
|
17166
|
+
}
|
|
17167
|
+
const path9 = resolveCompletionPath(shell, home);
|
|
17168
|
+
if (await pathExists(path9)) {
|
|
17169
|
+
return okCheck("completion", `${shell} completion installed (${path9})`);
|
|
17170
|
+
}
|
|
17171
|
+
return skippedCheck(
|
|
17172
|
+
"completion",
|
|
17173
|
+
`${shell} completion not installed (optional)`,
|
|
17174
|
+
`run: gji completion ${shell} > ${path9}`
|
|
17175
|
+
);
|
|
17176
|
+
}
|
|
17177
|
+
async function checkWorktreeBase(repository, config, home) {
|
|
17178
|
+
if (!repository) {
|
|
17179
|
+
return skippedCheck(
|
|
17180
|
+
"worktree-base",
|
|
17181
|
+
"worktree base not checked outside a Git repository"
|
|
17182
|
+
);
|
|
17183
|
+
}
|
|
17184
|
+
const basePath = resolveWorktreeBase(repository.repoRoot, config, home);
|
|
17185
|
+
const writablePath = await findNearestExistingPath(basePath);
|
|
17186
|
+
if (!writablePath) {
|
|
17187
|
+
return failedCheck(
|
|
17188
|
+
"worktree-base",
|
|
17189
|
+
`worktree base cannot be created (${basePath})`,
|
|
17190
|
+
"create a writable parent directory or update worktreePath"
|
|
17191
|
+
);
|
|
17192
|
+
}
|
|
17193
|
+
try {
|
|
17194
|
+
await access4(writablePath, constants3.W_OK | constants3.X_OK);
|
|
17195
|
+
return okCheck("worktree-base", `worktree base writable (${basePath})`);
|
|
17196
|
+
} catch {
|
|
17197
|
+
return failedCheck(
|
|
17198
|
+
"worktree-base",
|
|
17199
|
+
`worktree base is not writable (${writablePath})`,
|
|
17200
|
+
"update worktreePath to use a writable directory"
|
|
17201
|
+
);
|
|
17202
|
+
}
|
|
17203
|
+
}
|
|
17204
|
+
function resolveWorktreeBase(repoRoot, config, home) {
|
|
17205
|
+
const configuredPath = config.worktreePath;
|
|
17206
|
+
if (typeof configuredPath === "string" && (configuredPath.startsWith("/") || configuredPath.startsWith("~"))) {
|
|
17207
|
+
return expandTilde2(configuredPath, home);
|
|
17208
|
+
}
|
|
17209
|
+
return join6(dirname5(repoRoot), "worktrees", basename4(repoRoot));
|
|
17210
|
+
}
|
|
17211
|
+
async function findNearestExistingPath(path9) {
|
|
17212
|
+
let candidate = path9;
|
|
17213
|
+
while (true) {
|
|
17214
|
+
try {
|
|
17215
|
+
await access4(candidate, constants3.F_OK);
|
|
17216
|
+
return candidate;
|
|
17217
|
+
} catch {
|
|
17218
|
+
const parent = dirname5(candidate);
|
|
17219
|
+
if (parent === candidate) return null;
|
|
17220
|
+
candidate = parent;
|
|
17221
|
+
}
|
|
17222
|
+
}
|
|
17223
|
+
}
|
|
17224
|
+
async function inspectRegistry(home) {
|
|
17225
|
+
const entries = await loadRegistry(home);
|
|
17226
|
+
const missingEntries = await Promise.all(
|
|
17227
|
+
entries.map(async (entry) => ({
|
|
17228
|
+
entry,
|
|
17229
|
+
status: await inspectRegistryPath(entry.path)
|
|
17230
|
+
}))
|
|
17231
|
+
);
|
|
17232
|
+
const missingCount = missingEntries.filter(
|
|
17233
|
+
({ status }) => status === "missing"
|
|
17234
|
+
).length;
|
|
17235
|
+
const unreadableCount = missingEntries.filter(
|
|
17236
|
+
({ status }) => status === "unreadable"
|
|
17237
|
+
).length;
|
|
17238
|
+
const missingPaths = missingEntries.filter(({ status }) => status === "missing").map(({ entry }) => entry.path);
|
|
17239
|
+
if (missingCount === 0 && unreadableCount === 0) {
|
|
17240
|
+
return {
|
|
17241
|
+
check: okCheck(
|
|
17242
|
+
"repo-registry",
|
|
17243
|
+
`${entries.length} repos registered, all reachable`
|
|
17244
|
+
),
|
|
17245
|
+
missingPaths
|
|
17246
|
+
};
|
|
17247
|
+
}
|
|
17248
|
+
const messageParts = [];
|
|
17249
|
+
if (missingCount > 0) {
|
|
17250
|
+
messageParts.push(
|
|
17251
|
+
`${missingCount} ${missingCount === 1 ? "path is" : "paths are"} missing`
|
|
17252
|
+
);
|
|
17253
|
+
}
|
|
17254
|
+
if (unreadableCount > 0) {
|
|
17255
|
+
messageParts.push(
|
|
17256
|
+
`${unreadableCount} ${unreadableCount === 1 ? "path is" : "paths are"} not accessible`
|
|
17257
|
+
);
|
|
17258
|
+
}
|
|
17259
|
+
return {
|
|
17260
|
+
check: failedCheck(
|
|
17261
|
+
"repo-registry",
|
|
17262
|
+
`${entries.length} repos registered, ${messageParts.join(", ")}`,
|
|
17263
|
+
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"
|
|
17264
|
+
),
|
|
17265
|
+
missingPaths
|
|
17266
|
+
};
|
|
17267
|
+
}
|
|
17268
|
+
async function inspectRegistryPath(path9) {
|
|
17269
|
+
try {
|
|
17270
|
+
await access4(path9, constants3.F_OK);
|
|
17271
|
+
return "exists";
|
|
17272
|
+
} catch (error) {
|
|
17273
|
+
if (isMissingPathError2(error)) return "missing";
|
|
17274
|
+
return "unreadable";
|
|
17275
|
+
}
|
|
17276
|
+
}
|
|
17277
|
+
function isMissingPathError2(error) {
|
|
17278
|
+
if (!(error instanceof Error) || !("code" in error)) return false;
|
|
17279
|
+
const code = error.code;
|
|
17280
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
17281
|
+
}
|
|
17282
|
+
async function pathExists(path9) {
|
|
17283
|
+
try {
|
|
17284
|
+
await access4(path9, constants3.F_OK);
|
|
17285
|
+
return true;
|
|
17286
|
+
} catch {
|
|
17287
|
+
return false;
|
|
17288
|
+
}
|
|
17289
|
+
}
|
|
17290
|
+
async function checkEditor(config) {
|
|
17291
|
+
const editor = config.editor;
|
|
17292
|
+
if (typeof editor !== "string" || editor.length === 0) {
|
|
17293
|
+
return skippedCheck("editor", "editor not configured (optional)");
|
|
17294
|
+
}
|
|
17295
|
+
if (await executableExists(editor)) {
|
|
17296
|
+
return okCheck("editor", `editor "${editor}" found on PATH`);
|
|
17297
|
+
}
|
|
17298
|
+
const knownEditor = EDITORS.some(({ cli }) => cli === editor);
|
|
17299
|
+
return failedCheck(
|
|
17300
|
+
"editor",
|
|
17301
|
+
`editor "${editor}" was not found on PATH`,
|
|
17302
|
+
knownEditor ? `install ${editor} or choose another editor with: gji open --save` : "choose another editor with: gji open --save"
|
|
17303
|
+
);
|
|
17304
|
+
}
|
|
17305
|
+
function okCheck(id, message) {
|
|
17306
|
+
return { id, message, status: "ok" };
|
|
17307
|
+
}
|
|
17308
|
+
function failedCheck(id, message, hint) {
|
|
17309
|
+
return { hint, id, message, status: "fail" };
|
|
17310
|
+
}
|
|
17311
|
+
function skippedCheck(id, message, hint) {
|
|
17312
|
+
return { hint, id, message, status: "skip" };
|
|
17313
|
+
}
|
|
17314
|
+
function renderDoctorChecks(checks, problems) {
|
|
17315
|
+
const lines = ["gji doctor", ""];
|
|
17316
|
+
for (const check of checks) {
|
|
17317
|
+
lines.push(` ${statusSymbol(check.status)} ${check.message}`);
|
|
17318
|
+
if (check.hint) lines.push(` ${check.hint}`);
|
|
17319
|
+
}
|
|
17320
|
+
lines.push(
|
|
17321
|
+
"",
|
|
17322
|
+
`${problems} ${problems === 1 ? "problem" : "problems"} found.`
|
|
17323
|
+
);
|
|
17324
|
+
return `${lines.join("\n")}
|
|
17325
|
+
`;
|
|
17326
|
+
}
|
|
17327
|
+
function renderDoctorFixes(fixes) {
|
|
17328
|
+
const lines = ["", "Automatic fixes:"];
|
|
17329
|
+
if (fixes.length === 0) {
|
|
17330
|
+
lines.push(" No automatic fixes available.");
|
|
17331
|
+
} else {
|
|
17332
|
+
for (const fix of fixes) {
|
|
17333
|
+
lines.push(` ${fixStatusSymbol(fix.status)} ${fix.message}`);
|
|
17334
|
+
if (fix.paths && fix.paths.length > 0) {
|
|
17335
|
+
for (const path9 of fix.paths) lines.push(` ${path9}`);
|
|
17336
|
+
}
|
|
17337
|
+
if (fix.hint) lines.push(` ${fix.hint}`);
|
|
17338
|
+
}
|
|
17339
|
+
}
|
|
17340
|
+
return `${lines.join("\n")}
|
|
17341
|
+
`;
|
|
17342
|
+
}
|
|
17343
|
+
function fixStatusSymbol(status) {
|
|
17344
|
+
switch (status) {
|
|
17345
|
+
case "applied":
|
|
17346
|
+
return "\u2713";
|
|
17347
|
+
case "declined":
|
|
17348
|
+
return "-";
|
|
17349
|
+
case "failed":
|
|
17350
|
+
return "\u2717";
|
|
17351
|
+
case "pending":
|
|
17352
|
+
return "!";
|
|
17353
|
+
case "skipped":
|
|
17354
|
+
return "-";
|
|
17355
|
+
}
|
|
17356
|
+
}
|
|
17357
|
+
function statusSymbol(status) {
|
|
17358
|
+
switch (status) {
|
|
17359
|
+
case "fail":
|
|
17360
|
+
return "\u2717";
|
|
17361
|
+
case "ok":
|
|
17362
|
+
return "\u2713";
|
|
17363
|
+
case "skip":
|
|
17364
|
+
return "-";
|
|
17365
|
+
}
|
|
17366
|
+
}
|
|
17367
|
+
|
|
17368
|
+
// src/go.ts
|
|
17369
|
+
import { basename as basename7 } from "node:path";
|
|
17370
|
+
|
|
17371
|
+
// src/warp.ts
|
|
17372
|
+
import { realpath as realpath2 } from "node:fs/promises";
|
|
17373
|
+
import { basename as basename6, resolve as resolve5 } from "node:path";
|
|
17374
|
+
|
|
17375
|
+
// src/new.ts
|
|
17376
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
17377
|
+
import { mkdir as mkdir5 } from "node:fs/promises";
|
|
17378
|
+
import { basename as basename5, dirname as dirname7 } from "node:path";
|
|
17379
|
+
import { promisify as promisify6 } from "node:util";
|
|
17380
|
+
|
|
17381
|
+
// src/conflict.ts
|
|
17382
|
+
import { constants as constants4 } from "node:fs";
|
|
17383
|
+
import { access as access5 } from "node:fs/promises";
|
|
17384
|
+
async function pathExists2(path9) {
|
|
17385
|
+
try {
|
|
17386
|
+
await access5(path9, constants4.F_OK);
|
|
17387
|
+
return true;
|
|
17388
|
+
} catch {
|
|
17389
|
+
return false;
|
|
17390
|
+
}
|
|
17391
|
+
}
|
|
17392
|
+
async function promptForPathConflict(path9) {
|
|
17393
|
+
const choice = await ve({
|
|
17394
|
+
message: `Target path already exists: ${path9}`,
|
|
17395
|
+
options: [
|
|
17396
|
+
{
|
|
17397
|
+
value: "abort",
|
|
17398
|
+
label: "Abort",
|
|
17399
|
+
hint: "Keep the existing directory untouched"
|
|
17400
|
+
},
|
|
17401
|
+
{
|
|
17402
|
+
value: "reuse",
|
|
17403
|
+
label: "Reuse path",
|
|
17404
|
+
hint: "Print the existing path and stop"
|
|
17405
|
+
}
|
|
17406
|
+
]
|
|
17407
|
+
});
|
|
17408
|
+
if (pD(choice)) {
|
|
17409
|
+
return "abort";
|
|
17410
|
+
}
|
|
17411
|
+
return choice;
|
|
17412
|
+
}
|
|
17413
|
+
|
|
15937
17414
|
// src/file-sync.ts
|
|
15938
|
-
import { copyFile, mkdir as
|
|
15939
|
-
import { dirname as
|
|
17415
|
+
import { copyFile, mkdir as mkdir4, stat } from "node:fs/promises";
|
|
17416
|
+
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join7, normalize } from "node:path";
|
|
15940
17417
|
async function syncFiles(mainRoot, targetPath, patterns) {
|
|
15941
17418
|
for (const pattern of patterns) {
|
|
15942
17419
|
const normalized = validateSyncFilePattern(pattern);
|
|
15943
|
-
const sourcePath =
|
|
15944
|
-
const destPath =
|
|
17420
|
+
const sourcePath = join7(mainRoot, normalized);
|
|
17421
|
+
const destPath = join7(targetPath, normalized);
|
|
15945
17422
|
const sourceExists = await fileExists(sourcePath);
|
|
15946
17423
|
if (!sourceExists) {
|
|
15947
17424
|
continue;
|
|
@@ -15950,7 +17427,7 @@ async function syncFiles(mainRoot, targetPath, patterns) {
|
|
|
15950
17427
|
if (destExists) {
|
|
15951
17428
|
continue;
|
|
15952
17429
|
}
|
|
15953
|
-
await
|
|
17430
|
+
await mkdir4(dirname6(destPath), { recursive: true });
|
|
15954
17431
|
await copyFile(sourcePath, destPath);
|
|
15955
17432
|
}
|
|
15956
17433
|
}
|
|
@@ -15987,8 +17464,8 @@ function isNotFoundError(error) {
|
|
|
15987
17464
|
import { spawn as spawn4 } from "node:child_process";
|
|
15988
17465
|
|
|
15989
17466
|
// src/package-manager.ts
|
|
15990
|
-
import { access as
|
|
15991
|
-
import { join as
|
|
17467
|
+
import { access as access6, readdir } from "node:fs/promises";
|
|
17468
|
+
import { join as join8 } from "node:path";
|
|
15992
17469
|
var ENTRIES = [
|
|
15993
17470
|
// JavaScript / TypeScript
|
|
15994
17471
|
{ name: "pnpm", signals: ["pnpm-lock.yaml"], command: "pnpm install" },
|
|
@@ -16118,7 +17595,7 @@ async function detectPackageManager(repoRoot) {
|
|
|
16118
17595
|
async function matchesExact(repoRoot, signals) {
|
|
16119
17596
|
for (const signal of signals) {
|
|
16120
17597
|
try {
|
|
16121
|
-
await
|
|
17598
|
+
await access6(join8(repoRoot, signal));
|
|
16122
17599
|
return true;
|
|
16123
17600
|
} catch {
|
|
16124
17601
|
}
|
|
@@ -16282,7 +17759,7 @@ function isPlainObject2(value) {
|
|
|
16282
17759
|
}
|
|
16283
17760
|
|
|
16284
17761
|
// src/new.ts
|
|
16285
|
-
var
|
|
17762
|
+
var execFileAsync5 = promisify6(execFile5);
|
|
16286
17763
|
var NEW_OUTPUT_FILE_ENV = "GJI_NEW_OUTPUT_FILE";
|
|
16287
17764
|
function createNewCommand(dependencies = {}) {
|
|
16288
17765
|
const createBranchPlaceholder = dependencies.createBranchPlaceholder ?? generateBranchPlaceholder;
|
|
@@ -16290,6 +17767,17 @@ function createNewCommand(dependencies = {}) {
|
|
|
16290
17767
|
const prompt = dependencies.promptForPathConflict ?? promptForPathConflict;
|
|
16291
17768
|
const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
|
|
16292
17769
|
return async function runNewCommand2(options) {
|
|
17770
|
+
if (options.detached && options.fromCurrent) {
|
|
17771
|
+
const message = "--from-current cannot be used with --detached";
|
|
17772
|
+
if (options.json) {
|
|
17773
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}
|
|
17774
|
+
`);
|
|
17775
|
+
} else {
|
|
17776
|
+
options.stderr(`gji new: ${message}
|
|
17777
|
+
`);
|
|
17778
|
+
}
|
|
17779
|
+
return 1;
|
|
17780
|
+
}
|
|
16293
17781
|
const repository = await detectRepository(options.cwd);
|
|
16294
17782
|
const config = await loadEffectiveConfig(
|
|
16295
17783
|
repository.repoRoot,
|
|
@@ -16350,11 +17838,11 @@ function createNewCommand(dependencies = {}) {
|
|
|
16350
17838
|
worktreeName,
|
|
16351
17839
|
configuredBasePath
|
|
16352
17840
|
);
|
|
16353
|
-
if (!usesGeneratedDetachedName && await
|
|
17841
|
+
if (!usesGeneratedDetachedName && await pathExists2(worktreePath)) {
|
|
16354
17842
|
if (options.force) {
|
|
16355
17843
|
if (!options.dryRun) {
|
|
16356
17844
|
try {
|
|
16357
|
-
await
|
|
17845
|
+
await execFileAsync5(
|
|
16358
17846
|
"git",
|
|
16359
17847
|
["worktree", "remove", "--force", worktreePath],
|
|
16360
17848
|
{ cwd: repository.repoRoot }
|
|
@@ -16375,7 +17863,7 @@ function createNewCommand(dependencies = {}) {
|
|
|
16375
17863
|
}
|
|
16376
17864
|
if (!options.detached) {
|
|
16377
17865
|
try {
|
|
16378
|
-
await
|
|
17866
|
+
await execFileAsync5("git", ["branch", "-D", worktreeName], {
|
|
16379
17867
|
cwd: repository.repoRoot
|
|
16380
17868
|
});
|
|
16381
17869
|
} catch {
|
|
@@ -16432,9 +17920,17 @@ function createNewCommand(dependencies = {}) {
|
|
|
16432
17920
|
}
|
|
16433
17921
|
return 0;
|
|
16434
17922
|
}
|
|
16435
|
-
await
|
|
16436
|
-
const
|
|
16437
|
-
|
|
17923
|
+
await mkdir5(dirname7(worktreePath), { recursive: true });
|
|
17924
|
+
const startPoint = options.fromCurrent && !options.detached ? await resolveCurrentWorktreeHead(options.cwd) : void 0;
|
|
17925
|
+
const gitArgs = options.detached ? ["worktree", "add", "--detach", worktreePath] : await localBranchExists(repository.repoRoot, worktreeName) ? ["worktree", "add", worktreePath, worktreeName] : [
|
|
17926
|
+
"worktree",
|
|
17927
|
+
"add",
|
|
17928
|
+
"-b",
|
|
17929
|
+
worktreeName,
|
|
17930
|
+
worktreePath,
|
|
17931
|
+
...startPoint ? [startPoint] : []
|
|
17932
|
+
];
|
|
17933
|
+
await execFileAsync5("git", gitArgs, { cwd: repository.repoRoot });
|
|
16438
17934
|
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
|
|
16439
17935
|
(p2) => typeof p2 === "string"
|
|
16440
17936
|
) : [];
|
|
@@ -16463,7 +17959,7 @@ function createNewCommand(dependencies = {}) {
|
|
|
16463
17959
|
{
|
|
16464
17960
|
branch: worktreeName,
|
|
16465
17961
|
path: worktreePath,
|
|
16466
|
-
repo:
|
|
17962
|
+
repo: basename5(repository.repoRoot)
|
|
16467
17963
|
},
|
|
16468
17964
|
options.stderr
|
|
16469
17965
|
);
|
|
@@ -16615,7 +18111,7 @@ async function resolveUniqueDetachedWorktreePath(repoRoot, baseName, basePath) {
|
|
|
16615
18111
|
candidateName,
|
|
16616
18112
|
basePath
|
|
16617
18113
|
);
|
|
16618
|
-
if (!await
|
|
18114
|
+
if (!await pathExists2(candidatePath)) {
|
|
16619
18115
|
return candidatePath;
|
|
16620
18116
|
}
|
|
16621
18117
|
attempt += 1;
|
|
@@ -16638,7 +18134,7 @@ async function defaultPromptForBranch(placeholder) {
|
|
|
16638
18134
|
}
|
|
16639
18135
|
async function localBranchExists(repoRoot, branchName) {
|
|
16640
18136
|
try {
|
|
16641
|
-
await
|
|
18137
|
+
await execFileAsync5(
|
|
16642
18138
|
"git",
|
|
16643
18139
|
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
16644
18140
|
{ cwd: repoRoot }
|
|
@@ -16648,6 +18144,11 @@ async function localBranchExists(repoRoot, branchName) {
|
|
|
16648
18144
|
return false;
|
|
16649
18145
|
}
|
|
16650
18146
|
}
|
|
18147
|
+
async function resolveCurrentWorktreeHead(cwd) {
|
|
18148
|
+
return execFileAsync5("git", ["rev-parse", "--verify", "HEAD"], { cwd }).then(
|
|
18149
|
+
({ stdout: stdout2 }) => stdout2.trim()
|
|
18150
|
+
);
|
|
18151
|
+
}
|
|
16651
18152
|
async function writeOutput(worktreePath, stdout2) {
|
|
16652
18153
|
await writeShellOutput(NEW_OUTPUT_FILE_ENV, worktreePath, stdout2);
|
|
16653
18154
|
}
|
|
@@ -16683,74 +18184,6 @@ async function openWorktree(worktreePath, editorCli, spawnFn, stderr) {
|
|
|
16683
18184
|
}
|
|
16684
18185
|
}
|
|
16685
18186
|
|
|
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
18187
|
// src/warp.ts
|
|
16755
18188
|
var WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
|
|
16756
18189
|
async function runWarpCommand(options) {
|
|
@@ -16782,7 +18215,8 @@ async function runWarpNavigate(options) {
|
|
|
16782
18215
|
const target = await resolveWarpTarget({
|
|
16783
18216
|
...options,
|
|
16784
18217
|
commandName: "gji warp",
|
|
16785
|
-
json: options.json
|
|
18218
|
+
json: options.json,
|
|
18219
|
+
queryPullRequests: options.queryPullRequests
|
|
16786
18220
|
});
|
|
16787
18221
|
if (!target) return 1;
|
|
16788
18222
|
if (options.json) {
|
|
@@ -16862,7 +18296,7 @@ async function deduplicateRegistryForNew(registry) {
|
|
|
16862
18296
|
seenPaths.add(canonicalPath);
|
|
16863
18297
|
deduplicated.push({
|
|
16864
18298
|
...entry,
|
|
16865
|
-
name:
|
|
18299
|
+
name: basename6(canonicalPath),
|
|
16866
18300
|
path: canonicalPath
|
|
16867
18301
|
});
|
|
16868
18302
|
}
|
|
@@ -16899,15 +18333,16 @@ async function resolveWarpTarget(options) {
|
|
|
16899
18333
|
const results = await Promise.allSettled(
|
|
16900
18334
|
registry.map(async (entry) => {
|
|
16901
18335
|
const worktrees = await listWorktrees(entry.path);
|
|
16902
|
-
return { repoName: entry.name, worktrees };
|
|
18336
|
+
return { repoName: entry.name, repoRoot: entry.path, worktrees };
|
|
16903
18337
|
})
|
|
16904
18338
|
);
|
|
16905
18339
|
const allItems = [];
|
|
16906
18340
|
for (const result of results) {
|
|
16907
18341
|
if (result.status === "rejected") continue;
|
|
16908
|
-
const { repoName, worktrees } = result.value;
|
|
18342
|
+
const { repoName, repoRoot, worktrees } = result.value;
|
|
16909
18343
|
for (const worktree of worktrees) {
|
|
16910
18344
|
allItems.push({
|
|
18345
|
+
repoRoot,
|
|
16911
18346
|
repoName,
|
|
16912
18347
|
worktree: {
|
|
16913
18348
|
...worktree,
|
|
@@ -16921,6 +18356,7 @@ async function resolveWarpTarget(options) {
|
|
|
16921
18356
|
return null;
|
|
16922
18357
|
}
|
|
16923
18358
|
const promptSources = allItems.map((item) => ({
|
|
18359
|
+
repoRoot: item.repoRoot,
|
|
16924
18360
|
repoName: item.repoName,
|
|
16925
18361
|
worktree: item.worktree
|
|
16926
18362
|
}));
|
|
@@ -16932,7 +18368,9 @@ async function resolveWarpTarget(options) {
|
|
|
16932
18368
|
}
|
|
16933
18369
|
return { branch: match.worktree.branch, path: match.worktree.path };
|
|
16934
18370
|
}
|
|
16935
|
-
const promptEntries = await buildWorktreePromptEntries(promptSources
|
|
18371
|
+
const promptEntries = await buildWorktreePromptEntries(promptSources, {
|
|
18372
|
+
queryPullRequests: options.queryPullRequests
|
|
18373
|
+
});
|
|
16936
18374
|
const path9 = await promptForWarpTarget(promptEntries);
|
|
16937
18375
|
if (!path9) {
|
|
16938
18376
|
options.stderr("Aborted\n");
|
|
@@ -16980,10 +18418,13 @@ function createGoCommand(dependencies = {}) {
|
|
|
16980
18418
|
return 1;
|
|
16981
18419
|
}
|
|
16982
18420
|
const promptSources = worktrees.map((worktree) => ({
|
|
18421
|
+
repoRoot: repository.repoRoot,
|
|
16983
18422
|
repoName: repository.repoName,
|
|
16984
18423
|
worktree
|
|
16985
18424
|
}));
|
|
16986
|
-
const promptEntries = options.branch ? [] : await buildWorktreePromptEntries(promptSources
|
|
18425
|
+
const promptEntries = options.branch ? [] : await buildWorktreePromptEntries(promptSources, {
|
|
18426
|
+
queryPullRequests: dependencies.queryPullRequests
|
|
18427
|
+
});
|
|
16987
18428
|
const queried = options.branch ? resolveWorktreeQuery(promptSources, options.branch) : null;
|
|
16988
18429
|
const prompted = options.branch ? null : await prompt(promptEntries);
|
|
16989
18430
|
const resolvedPath = options.branch ? queried?.worktree.path : prompted ?? void 0;
|
|
@@ -17011,7 +18452,7 @@ function createGoCommand(dependencies = {}) {
|
|
|
17011
18452
|
{
|
|
17012
18453
|
branch: chosenWorktree?.branch ?? void 0,
|
|
17013
18454
|
path: resolvedPath,
|
|
17014
|
-
repo:
|
|
18455
|
+
repo: basename7(repository.repoRoot)
|
|
17015
18456
|
},
|
|
17016
18457
|
options.stderr
|
|
17017
18458
|
);
|
|
@@ -17042,11 +18483,11 @@ async function runHistoryCommand(options) {
|
|
|
17042
18483
|
}
|
|
17043
18484
|
|
|
17044
18485
|
// src/init.ts
|
|
17045
|
-
import {
|
|
17046
|
-
import {
|
|
17047
|
-
import {
|
|
17048
|
-
|
|
17049
|
-
var
|
|
18486
|
+
import { constants as constants5 } from "node:fs";
|
|
18487
|
+
import { access as access7, mkdir as mkdir6, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
|
|
18488
|
+
import { homedir as homedir6 } from "node:os";
|
|
18489
|
+
import { dirname as dirname8 } from "node:path";
|
|
18490
|
+
var ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
|
|
17050
18491
|
var SHELL_WRAPPED_COMMANDS = [
|
|
17051
18492
|
{
|
|
17052
18493
|
bypassOptions: ["--help"],
|
|
@@ -17097,10 +18538,256 @@ var SHELL_WRAPPED_COMMANDS = [
|
|
|
17097
18538
|
names: ["warp"],
|
|
17098
18539
|
tempPrefix: "gji-warp"
|
|
17099
18540
|
}
|
|
17100
|
-
];
|
|
17101
|
-
async function runInitCommand(options) {
|
|
18541
|
+
];
|
|
18542
|
+
async function runInitCommand(options) {
|
|
18543
|
+
if (options.shell === void 0) {
|
|
18544
|
+
return runOnboardingInitCommand(options);
|
|
18545
|
+
}
|
|
18546
|
+
return runLegacyInitCommand(options);
|
|
18547
|
+
}
|
|
18548
|
+
async function runOnboardingInitCommand(options) {
|
|
18549
|
+
if (options.json || isHeadless() || !canRunOnboarding(options)) {
|
|
18550
|
+
return writeNonInteractiveInitError(options);
|
|
18551
|
+
}
|
|
18552
|
+
const home = options.home ?? homedir6();
|
|
18553
|
+
const context = {
|
|
18554
|
+
detectedShell: resolveSupportedShell(void 0, process.env.SHELL),
|
|
18555
|
+
home
|
|
18556
|
+
};
|
|
18557
|
+
if (options.promptForOnboarding === void 0) {
|
|
18558
|
+
return runDefaultOnboarding(context);
|
|
18559
|
+
}
|
|
18560
|
+
const result = await options.promptForOnboarding(context);
|
|
18561
|
+
if (!result) {
|
|
18562
|
+
options.stderr?.("Aborted.\n");
|
|
18563
|
+
return 1;
|
|
18564
|
+
}
|
|
18565
|
+
await applyOnboardingResult(result, home);
|
|
18566
|
+
return 0;
|
|
18567
|
+
}
|
|
18568
|
+
function canRunOnboarding(options) {
|
|
18569
|
+
return options.interactive ?? (options.promptForOnboarding !== void 0 || process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
18570
|
+
}
|
|
18571
|
+
function writeNonInteractiveInitError(options) {
|
|
18572
|
+
const error = "run `gji init <shell> --write` in non-interactive mode";
|
|
18573
|
+
options.stderr?.(
|
|
18574
|
+
options.json ? `${JSON.stringify({ error })}
|
|
18575
|
+
` : `${error}
|
|
18576
|
+
`
|
|
18577
|
+
);
|
|
18578
|
+
return 1;
|
|
18579
|
+
}
|
|
18580
|
+
async function runDefaultOnboarding(context) {
|
|
18581
|
+
Ie("gji init");
|
|
18582
|
+
const shell = await ve({
|
|
18583
|
+
message: "Which shell should gji configure?",
|
|
18584
|
+
initialValue: context.detectedShell ?? "zsh",
|
|
18585
|
+
options: [
|
|
18586
|
+
{ value: "zsh", label: "zsh" },
|
|
18587
|
+
{ value: "bash", label: "bash" },
|
|
18588
|
+
{ value: "fish", label: "fish" }
|
|
18589
|
+
]
|
|
18590
|
+
});
|
|
18591
|
+
if (pD(shell)) return abortDefaultOnboarding();
|
|
18592
|
+
const rcPath = resolveShellConfigPath(shell, context.home);
|
|
18593
|
+
const currentConfig = await readExistingConfig(rcPath);
|
|
18594
|
+
const shellIntegration = await promptForShellIntegration(
|
|
18595
|
+
currentConfig,
|
|
18596
|
+
rcPath,
|
|
18597
|
+
shell
|
|
18598
|
+
);
|
|
18599
|
+
if (shellIntegration === null) return abortDefaultOnboarding();
|
|
18600
|
+
const completionPath = resolveCompletionPath(shell, context.home);
|
|
18601
|
+
const completionAlreadyInstalled = await fileExists2(completionPath);
|
|
18602
|
+
await applyShellIntegration(shell, shellIntegration, context.home);
|
|
18603
|
+
const installCompletion = await promptForCompletion(shell, completionPath);
|
|
18604
|
+
if (installCompletion === null) return abortDefaultOnboarding();
|
|
18605
|
+
if (installCompletion) {
|
|
18606
|
+
await mkdir6(dirname8(completionPath), { recursive: true });
|
|
18607
|
+
await writeFile5(completionPath, renderShellCompletion(shell), "utf8");
|
|
18608
|
+
}
|
|
18609
|
+
if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
|
|
18610
|
+
await ensureZshCompletionPath(rcPath);
|
|
18611
|
+
}
|
|
18612
|
+
const editor = await promptForEditor();
|
|
18613
|
+
if (editor === null) return abortDefaultOnboarding();
|
|
18614
|
+
if (editor) {
|
|
18615
|
+
await updateGlobalConfigKey("editor", editor, context.home);
|
|
18616
|
+
}
|
|
18617
|
+
Se(
|
|
18618
|
+
`Setup complete. Restart your shell or run: source ${rcPath}
|
|
18619
|
+
Verify with: gji doctor`
|
|
18620
|
+
);
|
|
18621
|
+
return 0;
|
|
18622
|
+
}
|
|
18623
|
+
async function applyShellIntegration(shell, choice, home) {
|
|
18624
|
+
if (choice === "install") {
|
|
18625
|
+
await installOnboardingShellIntegration(shell, home);
|
|
18626
|
+
}
|
|
18627
|
+
if (choice !== "skip") {
|
|
18628
|
+
await updateGlobalConfigKey("shellIntegration", true, home);
|
|
18629
|
+
}
|
|
18630
|
+
}
|
|
18631
|
+
function abortDefaultOnboarding() {
|
|
18632
|
+
Se("Aborted.");
|
|
18633
|
+
return 1;
|
|
18634
|
+
}
|
|
18635
|
+
async function promptForShellIntegration(currentConfig, rcPath, shell) {
|
|
18636
|
+
if (currentConfig.includes(ONBOARDING_SHELL_INTEGRATION_START_MARKER) && currentConfig.includes(ONBOARDING_SHELL_INTEGRATION_END_MARKER)) {
|
|
18637
|
+
M2.success(`Shell integration marker found in ${rcPath}; refreshing it.`);
|
|
18638
|
+
return "install";
|
|
18639
|
+
}
|
|
18640
|
+
if (hasShellIntegration(currentConfig, shell)) {
|
|
18641
|
+
M2.success(`Shell integration already installed in ${rcPath} \u2713`);
|
|
18642
|
+
return "existing";
|
|
18643
|
+
}
|
|
18644
|
+
const confirmed = await ye({
|
|
18645
|
+
message: `Install shell integration in ${rcPath}?`,
|
|
18646
|
+
initialValue: true
|
|
18647
|
+
});
|
|
18648
|
+
if (pD(confirmed)) return null;
|
|
18649
|
+
return confirmed ? "install" : "skip";
|
|
18650
|
+
}
|
|
18651
|
+
async function promptForCompletion(shell, completionPath) {
|
|
18652
|
+
if (await fileExists2(completionPath)) {
|
|
18653
|
+
M2.success(`${shell} completion already installed at ${completionPath} \u2713`);
|
|
18654
|
+
return false;
|
|
18655
|
+
}
|
|
18656
|
+
const confirmed = await ye({
|
|
18657
|
+
message: `Install ${shell} completion at ${completionPath}?`,
|
|
18658
|
+
initialValue: true
|
|
18659
|
+
});
|
|
18660
|
+
if (pD(confirmed)) return null;
|
|
18661
|
+
return confirmed;
|
|
18662
|
+
}
|
|
18663
|
+
async function promptForEditor() {
|
|
18664
|
+
const availableEditors = await findAvailableEditors();
|
|
18665
|
+
if (availableEditors.length === 0) {
|
|
18666
|
+
M2.info("No supported editor CLIs found on PATH; skipping editor setup.");
|
|
18667
|
+
return void 0;
|
|
18668
|
+
}
|
|
18669
|
+
const skipValue = "__gji_skip_editor__";
|
|
18670
|
+
const selected = await ve({
|
|
18671
|
+
message: "Which editor should gji use?",
|
|
18672
|
+
options: [
|
|
18673
|
+
...availableEditors.map(({ cli, name }) => ({
|
|
18674
|
+
value: cli,
|
|
18675
|
+
label: name,
|
|
18676
|
+
hint: cli
|
|
18677
|
+
})),
|
|
18678
|
+
{ value: skipValue, label: "Skip" }
|
|
18679
|
+
]
|
|
18680
|
+
});
|
|
18681
|
+
if (pD(selected)) return null;
|
|
18682
|
+
return selected === skipValue ? void 0 : selected;
|
|
18683
|
+
}
|
|
18684
|
+
async function findAvailableEditors() {
|
|
18685
|
+
const availableEditors = [];
|
|
18686
|
+
for (const editor of EDITORS) {
|
|
18687
|
+
if (await executableExists(editor.cli)) {
|
|
18688
|
+
availableEditors.push(editor);
|
|
18689
|
+
}
|
|
18690
|
+
}
|
|
18691
|
+
return availableEditors;
|
|
18692
|
+
}
|
|
18693
|
+
async function applyOnboardingResult(result, home) {
|
|
18694
|
+
const rcPath = resolveShellConfigPath(result.shell, home);
|
|
18695
|
+
const completionPath = resolveCompletionPath(result.shell, home);
|
|
18696
|
+
const completionAlreadyInstalled = await fileExists2(completionPath);
|
|
18697
|
+
await applyShellIntegration(result.shell, result.shellIntegration, home);
|
|
18698
|
+
if (result.installCompletion) {
|
|
18699
|
+
await mkdir6(dirname8(completionPath), { recursive: true });
|
|
18700
|
+
await writeFile5(
|
|
18701
|
+
completionPath,
|
|
18702
|
+
renderShellCompletion(result.shell),
|
|
18703
|
+
"utf8"
|
|
18704
|
+
);
|
|
18705
|
+
}
|
|
18706
|
+
if (result.shell === "zsh" && (completionAlreadyInstalled || result.installCompletion)) {
|
|
18707
|
+
await ensureZshCompletionPath(rcPath);
|
|
18708
|
+
}
|
|
18709
|
+
if (result.editor) {
|
|
18710
|
+
await updateGlobalConfigKey("editor", result.editor, home);
|
|
18711
|
+
}
|
|
18712
|
+
}
|
|
18713
|
+
async function installOnboardingShellIntegration(shell, home) {
|
|
18714
|
+
const rcPath = resolveShellConfigPath(shell, home);
|
|
18715
|
+
await mkdir6(dirname8(rcPath), { recursive: true });
|
|
18716
|
+
const current = await readExistingConfig(rcPath);
|
|
18717
|
+
if (shell === "fish") {
|
|
18718
|
+
await writeFile5(
|
|
18719
|
+
rcPath,
|
|
18720
|
+
upsertShellIntegration(current, renderShellIntegration(shell)),
|
|
18721
|
+
"utf8"
|
|
18722
|
+
);
|
|
18723
|
+
return;
|
|
18724
|
+
}
|
|
18725
|
+
await writeFile5(
|
|
18726
|
+
rcPath,
|
|
18727
|
+
upsertOnboardingShellIntegration(current, shell),
|
|
18728
|
+
"utf8"
|
|
18729
|
+
);
|
|
18730
|
+
}
|
|
18731
|
+
function upsertOnboardingShellIntegration(existingConfig, shell) {
|
|
18732
|
+
const script = renderOnboardingShellIntegration(shell).trimEnd();
|
|
18733
|
+
const blockPattern = new RegExp(
|
|
18734
|
+
`${escapeForRegExp(ONBOARDING_SHELL_INTEGRATION_START_MARKER)}[\\s\\S]*?${escapeForRegExp(ONBOARDING_SHELL_INTEGRATION_END_MARKER)}\\n?`,
|
|
18735
|
+
"m"
|
|
18736
|
+
);
|
|
18737
|
+
if (blockPattern.test(existingConfig)) {
|
|
18738
|
+
return ensureTrailingNewline(
|
|
18739
|
+
existingConfig.replace(blockPattern, `${script}
|
|
18740
|
+
`)
|
|
18741
|
+
);
|
|
18742
|
+
}
|
|
18743
|
+
const prefix = existingConfig.trimEnd();
|
|
18744
|
+
if (prefix.length === 0) return ensureTrailingNewline(script);
|
|
18745
|
+
return ensureTrailingNewline(`${prefix}
|
|
18746
|
+
|
|
18747
|
+
${script}`);
|
|
18748
|
+
}
|
|
18749
|
+
function renderOnboardingShellIntegration(shell) {
|
|
18750
|
+
return `${ONBOARDING_SHELL_INTEGRATION_START_MARKER}
|
|
18751
|
+
eval "$(gji init ${shell})"
|
|
18752
|
+
${ONBOARDING_SHELL_INTEGRATION_END_MARKER}
|
|
18753
|
+
`;
|
|
18754
|
+
}
|
|
18755
|
+
async function ensureZshCompletionPath(rcPath) {
|
|
18756
|
+
const current = await readExistingConfig(rcPath);
|
|
18757
|
+
const next = upsertZshCompletionPath(current);
|
|
18758
|
+
if (next !== current) {
|
|
18759
|
+
await writeFile5(rcPath, next, "utf8");
|
|
18760
|
+
}
|
|
18761
|
+
}
|
|
18762
|
+
function upsertZshCompletionPath(existingConfig) {
|
|
18763
|
+
const configWithoutCompletionPath = existingConfig.replace(
|
|
18764
|
+
/# >>> gji zsh completion path >>>[\s\S]*?# <<< gji zsh completion path <<<\n?/m,
|
|
18765
|
+
""
|
|
18766
|
+
).replace(
|
|
18767
|
+
new RegExp(
|
|
18768
|
+
`^[\\t ]*${escapeForRegExp(ZSH_COMPLETION_PATH_LINE)}[\\t ]*\\n?`,
|
|
18769
|
+
"gm"
|
|
18770
|
+
),
|
|
18771
|
+
""
|
|
18772
|
+
);
|
|
18773
|
+
const suffix = configWithoutCompletionPath.replace(/^(?:\r?\n)+/, "").trimEnd();
|
|
18774
|
+
return ensureTrailingNewline(
|
|
18775
|
+
suffix.length === 0 ? ZSH_COMPLETION_PATH_LINE : `${ZSH_COMPLETION_PATH_LINE}
|
|
18776
|
+
|
|
18777
|
+
${suffix}`
|
|
18778
|
+
);
|
|
18779
|
+
}
|
|
18780
|
+
async function fileExists2(path9) {
|
|
18781
|
+
try {
|
|
18782
|
+
await access7(path9, constants5.F_OK);
|
|
18783
|
+
return true;
|
|
18784
|
+
} catch {
|
|
18785
|
+
return false;
|
|
18786
|
+
}
|
|
18787
|
+
}
|
|
18788
|
+
async function runLegacyInitCommand(options) {
|
|
17102
18789
|
const shell = resolveSupportedShell(options.shell, process.env.SHELL);
|
|
17103
|
-
const home = options.home ??
|
|
18790
|
+
const home = options.home ?? homedir6();
|
|
17104
18791
|
if (!shell) {
|
|
17105
18792
|
options.stderr?.(
|
|
17106
18793
|
"Unable to detect a supported shell. Specify one explicitly: bash, fish, or zsh.\n"
|
|
@@ -17113,7 +18800,7 @@ async function runInitCommand(options) {
|
|
|
17113
18800
|
return 0;
|
|
17114
18801
|
}
|
|
17115
18802
|
const rcPath = resolveShellConfigPath(shell, home);
|
|
17116
|
-
await mkdir6(
|
|
18803
|
+
await mkdir6(dirname8(rcPath), { recursive: true });
|
|
17117
18804
|
const current = await readExistingConfig(rcPath);
|
|
17118
18805
|
const next = upsertShellIntegration(current, script);
|
|
17119
18806
|
await writeFile5(rcPath, next, "utf8");
|
|
@@ -17122,7 +18809,7 @@ async function runInitCommand(options) {
|
|
|
17122
18809
|
const { config: globalConfig } = await loadGlobalConfig(home);
|
|
17123
18810
|
const alreadyConfigured = "shellIntegration" in globalConfig || "installSaveTarget" in globalConfig;
|
|
17124
18811
|
const hasCustomPrompt = options.promptForSetup !== void 0;
|
|
17125
|
-
const canPrompt = hasCustomPrompt || process.stdout.isTTY === true;
|
|
18812
|
+
const canPrompt = !isHeadless() && (hasCustomPrompt || process.stdout.isTTY === true);
|
|
17126
18813
|
if (!alreadyConfigured && canPrompt) {
|
|
17127
18814
|
const prompt = options.promptForSetup ?? defaultPromptForSetup;
|
|
17128
18815
|
const result = await prompt();
|
|
@@ -17144,30 +18831,30 @@ function renderShellIntegration(shell) {
|
|
|
17144
18831
|
).join("\n\n");
|
|
17145
18832
|
switch (shell) {
|
|
17146
18833
|
case "fish":
|
|
17147
|
-
return `${
|
|
18834
|
+
return `${SHELL_INTEGRATION_START_MARKER}
|
|
17148
18835
|
function gji --wraps gji --description 'gji shell integration'
|
|
17149
18836
|
${indentBlock(commandBlocks, 4)}
|
|
17150
18837
|
|
|
17151
18838
|
command gji $argv
|
|
17152
18839
|
end
|
|
17153
|
-
${
|
|
18840
|
+
${SHELL_INTEGRATION_END_MARKER}
|
|
17154
18841
|
`;
|
|
17155
18842
|
case "bash":
|
|
17156
18843
|
case "zsh":
|
|
17157
|
-
return `${
|
|
18844
|
+
return `${SHELL_INTEGRATION_START_MARKER}
|
|
17158
18845
|
gji() {
|
|
17159
18846
|
${indentBlock(commandBlocks, 2)}
|
|
17160
18847
|
|
|
17161
18848
|
command gji "$@"
|
|
17162
18849
|
}
|
|
17163
|
-
${
|
|
18850
|
+
${SHELL_INTEGRATION_END_MARKER}
|
|
17164
18851
|
`;
|
|
17165
18852
|
}
|
|
17166
18853
|
}
|
|
17167
18854
|
function upsertShellIntegration(existingConfig, script) {
|
|
17168
18855
|
const trimmedScript = script.trimEnd();
|
|
17169
18856
|
const blockPattern = new RegExp(
|
|
17170
|
-
`${escapeForRegExp(
|
|
18857
|
+
`${escapeForRegExp(SHELL_INTEGRATION_START_MARKER)}[\\s\\S]*?${escapeForRegExp(SHELL_INTEGRATION_END_MARKER)}\\n?`,
|
|
17171
18858
|
"m"
|
|
17172
18859
|
);
|
|
17173
18860
|
if (blockPattern.test(existingConfig)) {
|
|
@@ -17205,21 +18892,11 @@ async function saveWizardConfig(result, cwd, home) {
|
|
|
17205
18892
|
await saveGlobalConfig({ ...existing, ...values }, home);
|
|
17206
18893
|
}
|
|
17207
18894
|
}
|
|
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
18895
|
async function readExistingConfig(path9) {
|
|
17219
18896
|
try {
|
|
17220
|
-
return await
|
|
18897
|
+
return await readFile5(path9, "utf8");
|
|
17221
18898
|
} catch (error) {
|
|
17222
|
-
if (
|
|
18899
|
+
if (isMissingFileError3(error)) {
|
|
17223
18900
|
return "";
|
|
17224
18901
|
}
|
|
17225
18902
|
throw error;
|
|
@@ -17232,7 +18909,7 @@ function ensureTrailingNewline(value) {
|
|
|
17232
18909
|
function escapeForRegExp(value) {
|
|
17233
18910
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17234
18911
|
}
|
|
17235
|
-
function
|
|
18912
|
+
function isMissingFileError3(error) {
|
|
17236
18913
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
17237
18914
|
}
|
|
17238
18915
|
function renderFishWrapper(command) {
|
|
@@ -17457,17 +19134,27 @@ function sortWorktrees(worktrees) {
|
|
|
17457
19134
|
}
|
|
17458
19135
|
|
|
17459
19136
|
// src/open.ts
|
|
17460
|
-
import { execFile as
|
|
17461
|
-
import { access as
|
|
17462
|
-
import { join as
|
|
17463
|
-
import { promisify as
|
|
17464
|
-
var
|
|
19137
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
19138
|
+
import { access as access8, writeFile as writeFile6 } from "node:fs/promises";
|
|
19139
|
+
import { join as join9 } from "node:path";
|
|
19140
|
+
import { promisify as promisify7 } from "node:util";
|
|
19141
|
+
var execFileAsync6 = promisify7(execFile6);
|
|
17465
19142
|
function createOpenCommand(dependencies = {}) {
|
|
17466
19143
|
const detectEditors = dependencies.detectEditors ?? detectInstalledEditors;
|
|
17467
|
-
const
|
|
19144
|
+
const promptForEditor2 = dependencies.promptForEditor ?? defaultPromptForEditor;
|
|
17468
19145
|
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree;
|
|
17469
19146
|
const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
|
|
17470
19147
|
return async function runOpenCommand2(options) {
|
|
19148
|
+
if (options.select && options.branch !== void 0) {
|
|
19149
|
+
options.stderr("gji open: --select cannot be used with a branch\n");
|
|
19150
|
+
return 1;
|
|
19151
|
+
}
|
|
19152
|
+
if (options.select && isHeadless()) {
|
|
19153
|
+
options.stderr(
|
|
19154
|
+
"gji open --select: selector is unavailable in non-interactive mode (GJI_NO_TUI=1)\n"
|
|
19155
|
+
);
|
|
19156
|
+
return 1;
|
|
19157
|
+
}
|
|
17471
19158
|
const [worktrees, repository] = await Promise.all([
|
|
17472
19159
|
listWorktrees(options.cwd),
|
|
17473
19160
|
detectRepository(options.cwd)
|
|
@@ -17493,15 +19180,21 @@ function createOpenCommand(dependencies = {}) {
|
|
|
17493
19180
|
}
|
|
17494
19181
|
targetPath = match.worktree.path;
|
|
17495
19182
|
targetWorktree = match.worktree;
|
|
17496
|
-
} else if (
|
|
19183
|
+
} else if (!options.select) {
|
|
17497
19184
|
targetWorktree = worktrees.find((w2) => w2.isCurrent);
|
|
17498
|
-
|
|
19185
|
+
if (!targetWorktree) {
|
|
19186
|
+
options.stderr("gji open: unable to identify the current worktree\n");
|
|
19187
|
+
return 1;
|
|
19188
|
+
}
|
|
19189
|
+
targetPath = targetWorktree.path;
|
|
17499
19190
|
} else {
|
|
17500
19191
|
const entries = await buildWorktreePromptEntries(
|
|
17501
19192
|
worktrees.map((worktree) => ({
|
|
19193
|
+
repoRoot: repository.repoRoot,
|
|
17502
19194
|
repoName: repository.repoName,
|
|
17503
19195
|
worktree
|
|
17504
|
-
}))
|
|
19196
|
+
})),
|
|
19197
|
+
{ queryPullRequests: dependencies.queryPullRequests }
|
|
17505
19198
|
);
|
|
17506
19199
|
const chosen = await promptForWorktree2(entries);
|
|
17507
19200
|
if (!chosen) {
|
|
@@ -17533,7 +19226,7 @@ function createOpenCommand(dependencies = {}) {
|
|
|
17533
19226
|
if (installed.length === 1 || isHeadless()) {
|
|
17534
19227
|
editorCli = installed[0].cli;
|
|
17535
19228
|
} else {
|
|
17536
|
-
const chosen = await
|
|
19229
|
+
const chosen = await promptForEditor2(installed);
|
|
17537
19230
|
if (!chosen) {
|
|
17538
19231
|
options.stderr("Aborted\n");
|
|
17539
19232
|
return 1;
|
|
@@ -17592,7 +19285,7 @@ async function detectInstalledEditors() {
|
|
|
17592
19285
|
}
|
|
17593
19286
|
async function isCommandAvailable(command) {
|
|
17594
19287
|
try {
|
|
17595
|
-
await
|
|
19288
|
+
await execFileAsync6("which", [command]);
|
|
17596
19289
|
return true;
|
|
17597
19290
|
} catch {
|
|
17598
19291
|
return false;
|
|
@@ -17610,9 +19303,9 @@ async function defaultPromptForEditor(editors) {
|
|
|
17610
19303
|
return choice;
|
|
17611
19304
|
}
|
|
17612
19305
|
async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
17613
|
-
const workspacePath =
|
|
19306
|
+
const workspacePath = join9(worktreePath, `${repoName}.code-workspace`);
|
|
17614
19307
|
try {
|
|
17615
|
-
await
|
|
19308
|
+
await access8(workspacePath);
|
|
17616
19309
|
return workspacePath;
|
|
17617
19310
|
} catch {
|
|
17618
19311
|
}
|
|
@@ -17627,11 +19320,11 @@ async function ensureWorkspaceFile(worktreePath, repoName) {
|
|
|
17627
19320
|
}
|
|
17628
19321
|
|
|
17629
19322
|
// src/pr.ts
|
|
17630
|
-
import { execFile as
|
|
19323
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
17631
19324
|
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
17632
|
-
import { basename as
|
|
17633
|
-
import { promisify as
|
|
17634
|
-
var
|
|
19325
|
+
import { basename as basename8, dirname as dirname9 } from "node:path";
|
|
19326
|
+
import { promisify as promisify8 } from "node:util";
|
|
19327
|
+
var execFileAsync7 = promisify8(execFile7);
|
|
17635
19328
|
var PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
|
|
17636
19329
|
function parsePrInput(input) {
|
|
17637
19330
|
if (/^\d+$/.test(input)) return input;
|
|
@@ -17673,7 +19366,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
17673
19366
|
branchName,
|
|
17674
19367
|
configuredBasePath
|
|
17675
19368
|
);
|
|
17676
|
-
if (await
|
|
19369
|
+
if (await pathExists2(worktreePath)) {
|
|
17677
19370
|
if (options.json || isHeadless()) {
|
|
17678
19371
|
const message = `target worktree path already exists: ${worktreePath}`;
|
|
17679
19372
|
if (options.json) {
|
|
@@ -17739,13 +19432,13 @@ function createPrCommand(dependencies = {}) {
|
|
|
17739
19432
|
}
|
|
17740
19433
|
return 1;
|
|
17741
19434
|
}
|
|
17742
|
-
await mkdir7(
|
|
19435
|
+
await mkdir7(dirname9(worktreePath), { recursive: true });
|
|
17743
19436
|
const branchAlreadyExists = await localBranchExists2(
|
|
17744
19437
|
repository.repoRoot,
|
|
17745
19438
|
branchName
|
|
17746
19439
|
);
|
|
17747
19440
|
const worktreeArgs = branchAlreadyExists ? ["worktree", "add", worktreePath, branchName] : ["worktree", "add", "-b", branchName, worktreePath, remoteRef];
|
|
17748
|
-
await
|
|
19441
|
+
await execFileAsync7("git", worktreeArgs, { cwd: repository.repoRoot });
|
|
17749
19442
|
const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
|
|
17750
19443
|
(p2) => typeof p2 === "string"
|
|
17751
19444
|
) : [];
|
|
@@ -17774,7 +19467,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
17774
19467
|
{
|
|
17775
19468
|
branch: branchName,
|
|
17776
19469
|
path: worktreePath,
|
|
17777
|
-
repo:
|
|
19470
|
+
repo: basename8(repository.repoRoot)
|
|
17778
19471
|
},
|
|
17779
19472
|
options.stderr
|
|
17780
19473
|
);
|
|
@@ -17792,7 +19485,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
17792
19485
|
}
|
|
17793
19486
|
async function localBranchExists2(repoRoot, branchName) {
|
|
17794
19487
|
try {
|
|
17795
|
-
await
|
|
19488
|
+
await execFileAsync7(
|
|
17796
19489
|
"git",
|
|
17797
19490
|
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
17798
19491
|
{ cwd: repoRoot }
|
|
@@ -17806,7 +19499,7 @@ var runPrCommand = createPrCommand();
|
|
|
17806
19499
|
async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef) {
|
|
17807
19500
|
for (const sourceRef of listPullRequestSourceRefs(input, prNumber)) {
|
|
17808
19501
|
try {
|
|
17809
|
-
await
|
|
19502
|
+
await execFileAsync7(
|
|
17810
19503
|
"git",
|
|
17811
19504
|
["fetch", "origin", `${sourceRef}:${remoteRef}`],
|
|
17812
19505
|
{ cwd: repoRoot }
|
|
@@ -17856,11 +19549,415 @@ async function writeOutput2(worktreePath, stdout2) {
|
|
|
17856
19549
|
await writeShellOutput(PR_OUTPUT_FILE_ENV, worktreePath, stdout2);
|
|
17857
19550
|
}
|
|
17858
19551
|
|
|
19552
|
+
// src/browser.ts
|
|
19553
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
19554
|
+
import { platform as platform2 } from "node:process";
|
|
19555
|
+
import { promisify as promisify9 } from "node:util";
|
|
19556
|
+
var execFileAsync8 = promisify9(execFile8);
|
|
19557
|
+
function createBrowserOpener(dependencies = {}) {
|
|
19558
|
+
const currentPlatform = dependencies.platform ?? platform2;
|
|
19559
|
+
const runCommand = dependencies.runCommand ?? defaultRunCommand2;
|
|
19560
|
+
return async function openBrowser2(url) {
|
|
19561
|
+
validateBrowserUrl(url);
|
|
19562
|
+
const command = browserCommand(currentPlatform, url);
|
|
19563
|
+
await runCommand(command.command, command.args);
|
|
19564
|
+
};
|
|
19565
|
+
}
|
|
19566
|
+
var openBrowser = createBrowserOpener();
|
|
19567
|
+
function browserCommand(currentPlatform, url) {
|
|
19568
|
+
if (currentPlatform === "darwin") return { args: [url], command: "open" };
|
|
19569
|
+
if (currentPlatform === "win32") {
|
|
19570
|
+
return { args: [url], command: "explorer.exe" };
|
|
19571
|
+
}
|
|
19572
|
+
return { args: [url], command: "xdg-open" };
|
|
19573
|
+
}
|
|
19574
|
+
function validateBrowserUrl(url) {
|
|
19575
|
+
let parsed;
|
|
19576
|
+
try {
|
|
19577
|
+
parsed = new URL(url);
|
|
19578
|
+
} catch {
|
|
19579
|
+
throw new Error("browser URL is invalid");
|
|
19580
|
+
}
|
|
19581
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
19582
|
+
throw new Error("browser URL must use http or https");
|
|
19583
|
+
}
|
|
19584
|
+
}
|
|
19585
|
+
async function defaultRunCommand2(command, args) {
|
|
19586
|
+
await execFileAsync8(command, args, { timeout: 1e4 });
|
|
19587
|
+
}
|
|
19588
|
+
|
|
19589
|
+
// src/pr-open.ts
|
|
19590
|
+
function createPrOpenCommand(dependencies = {}) {
|
|
19591
|
+
const query = createPullRequestQuery();
|
|
19592
|
+
const findOpenPullRequest2 = dependencies.findOpenPullRequest ?? query.findOpenPullRequest;
|
|
19593
|
+
const openInBrowser = dependencies.openBrowser ?? openBrowser;
|
|
19594
|
+
const promptForPullRequest = dependencies.promptForPullRequest ?? defaultPromptForPullRequest;
|
|
19595
|
+
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree2;
|
|
19596
|
+
const queryPullRequests = dependencies.queryPullRequests ?? query.listOpenPullRequests;
|
|
19597
|
+
const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ?? (dependencies.queryPullRequests === void 0 ? query.listOpenPullRequestsForRepository : void 0);
|
|
19598
|
+
return async function runPrOpenCommand2(options) {
|
|
19599
|
+
if (options.select && options.target !== void 0) {
|
|
19600
|
+
options.stderr("gji pr open: --select cannot be used with a target\n");
|
|
19601
|
+
return 1;
|
|
19602
|
+
}
|
|
19603
|
+
if (options.select && isHeadless()) {
|
|
19604
|
+
options.stderr(
|
|
19605
|
+
"gji pr open --select: selector is unavailable in non-interactive mode (GJI_NO_TUI=1)\n"
|
|
19606
|
+
);
|
|
19607
|
+
return 1;
|
|
19608
|
+
}
|
|
19609
|
+
const repository = await detectRepository(options.cwd).catch((error) => {
|
|
19610
|
+
options.stderr(
|
|
19611
|
+
`gji pr open: unable to detect the current repository: ${formatError(error)}
|
|
19612
|
+
`
|
|
19613
|
+
);
|
|
19614
|
+
return null;
|
|
19615
|
+
});
|
|
19616
|
+
if (repository === null) return 1;
|
|
19617
|
+
if (options.target === void 0) {
|
|
19618
|
+
if (!options.select) {
|
|
19619
|
+
return openCurrentWorktree(
|
|
19620
|
+
repository.repoRoot,
|
|
19621
|
+
options,
|
|
19622
|
+
queryPullRequests,
|
|
19623
|
+
promptForPullRequest,
|
|
19624
|
+
openInBrowser
|
|
19625
|
+
);
|
|
19626
|
+
}
|
|
19627
|
+
return openFromWorktreeSelector(
|
|
19628
|
+
repository.repoRoot,
|
|
19629
|
+
repository.repoName,
|
|
19630
|
+
options,
|
|
19631
|
+
promptForWorktree2,
|
|
19632
|
+
promptForPullRequest,
|
|
19633
|
+
queryPullRequests,
|
|
19634
|
+
queryRepositoryPullRequests,
|
|
19635
|
+
openInBrowser
|
|
19636
|
+
);
|
|
19637
|
+
}
|
|
19638
|
+
const number = parsePrNumberTarget(options.target);
|
|
19639
|
+
if (number !== null) {
|
|
19640
|
+
return openByNumber(
|
|
19641
|
+
repository.repoRoot,
|
|
19642
|
+
number,
|
|
19643
|
+
options,
|
|
19644
|
+
findOpenPullRequest2,
|
|
19645
|
+
openInBrowser
|
|
19646
|
+
);
|
|
19647
|
+
}
|
|
19648
|
+
const worktrees = await listWorktrees(options.cwd).catch((error) => {
|
|
19649
|
+
options.stderr(
|
|
19650
|
+
`gji pr open: unable to list worktrees: ${formatError(error)}
|
|
19651
|
+
`
|
|
19652
|
+
);
|
|
19653
|
+
return null;
|
|
19654
|
+
});
|
|
19655
|
+
if (worktrees === null) return 1;
|
|
19656
|
+
const match = resolveWorktreeQuery(
|
|
19657
|
+
worktrees.map((worktree) => ({
|
|
19658
|
+
repoRoot: repository.repoRoot,
|
|
19659
|
+
repoName: repository.repoName,
|
|
19660
|
+
worktree
|
|
19661
|
+
})),
|
|
19662
|
+
options.target
|
|
19663
|
+
);
|
|
19664
|
+
if (match === null) {
|
|
19665
|
+
options.stderr(
|
|
19666
|
+
`gji pr open: no worktree found matching: ${options.target}
|
|
19667
|
+
`
|
|
19668
|
+
);
|
|
19669
|
+
return 1;
|
|
19670
|
+
}
|
|
19671
|
+
return openForBranch(
|
|
19672
|
+
repository.repoRoot,
|
|
19673
|
+
match.worktree.branch,
|
|
19674
|
+
options,
|
|
19675
|
+
queryPullRequests,
|
|
19676
|
+
promptForPullRequest,
|
|
19677
|
+
openInBrowser
|
|
19678
|
+
);
|
|
19679
|
+
};
|
|
19680
|
+
}
|
|
19681
|
+
var runPrOpenCommand = createPrOpenCommand();
|
|
19682
|
+
async function openCurrentWorktree(repoRoot, options, queryPullRequests, promptForPullRequest, openInBrowser) {
|
|
19683
|
+
const worktrees = await listWorktrees(options.cwd).catch((error) => {
|
|
19684
|
+
options.stderr(
|
|
19685
|
+
`gji pr open: unable to list worktrees: ${formatError(error)}
|
|
19686
|
+
`
|
|
19687
|
+
);
|
|
19688
|
+
return null;
|
|
19689
|
+
});
|
|
19690
|
+
if (worktrees === null) return 1;
|
|
19691
|
+
const currentWorktree = worktrees.find((worktree) => worktree.isCurrent);
|
|
19692
|
+
if (currentWorktree === void 0) {
|
|
19693
|
+
options.stderr("gji pr open: unable to identify the current worktree\n");
|
|
19694
|
+
return 1;
|
|
19695
|
+
}
|
|
19696
|
+
if (currentWorktree.branch === null) {
|
|
19697
|
+
options.stderr(
|
|
19698
|
+
"gji pr open: current worktree is detached and has no PR branch\n"
|
|
19699
|
+
);
|
|
19700
|
+
return 1;
|
|
19701
|
+
}
|
|
19702
|
+
let pullRequests;
|
|
19703
|
+
try {
|
|
19704
|
+
pullRequests = await queryPullRequests(repoRoot, currentWorktree.branch);
|
|
19705
|
+
} catch (error) {
|
|
19706
|
+
options.stderr(
|
|
19707
|
+
`gji pr open: failed to look up PRs for current branch ${currentWorktree.branch}: ${formatError(error)}
|
|
19708
|
+
`
|
|
19709
|
+
);
|
|
19710
|
+
return 1;
|
|
19711
|
+
}
|
|
19712
|
+
if (pullRequests.length === 0) {
|
|
19713
|
+
options.stderr(
|
|
19714
|
+
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"
|
|
19715
|
+
);
|
|
19716
|
+
return 1;
|
|
19717
|
+
}
|
|
19718
|
+
return openPullRequests(
|
|
19719
|
+
pullRequests,
|
|
19720
|
+
options,
|
|
19721
|
+
promptForPullRequest,
|
|
19722
|
+
openInBrowser
|
|
19723
|
+
);
|
|
19724
|
+
}
|
|
19725
|
+
async function openFromWorktreeSelector(repoRoot, repoName, options, promptForWorktree2, promptForPullRequest, queryPullRequests, queryRepositoryPullRequests, openInBrowser) {
|
|
19726
|
+
const worktrees = await listWorktrees(options.cwd).catch((error) => {
|
|
19727
|
+
options.stderr(
|
|
19728
|
+
`gji pr open: unable to list worktrees: ${formatError(error)}
|
|
19729
|
+
`
|
|
19730
|
+
);
|
|
19731
|
+
return null;
|
|
19732
|
+
});
|
|
19733
|
+
if (worktrees === null) return 1;
|
|
19734
|
+
let lookupError = null;
|
|
19735
|
+
const queryForSelector = async (root, branch) => {
|
|
19736
|
+
try {
|
|
19737
|
+
return await queryPullRequests(root, branch);
|
|
19738
|
+
} catch (error) {
|
|
19739
|
+
lookupError ??= error;
|
|
19740
|
+
throw error;
|
|
19741
|
+
}
|
|
19742
|
+
};
|
|
19743
|
+
const queryRepositoryForSelector = queryRepositoryPullRequests === void 0 ? void 0 : async (root) => {
|
|
19744
|
+
try {
|
|
19745
|
+
return await queryRepositoryPullRequests(root);
|
|
19746
|
+
} catch (error) {
|
|
19747
|
+
lookupError ??= error;
|
|
19748
|
+
throw error;
|
|
19749
|
+
}
|
|
19750
|
+
};
|
|
19751
|
+
let connectedWorktrees;
|
|
19752
|
+
try {
|
|
19753
|
+
connectedWorktrees = await readConnectedWorktreeSources(
|
|
19754
|
+
repoRoot,
|
|
19755
|
+
repoName,
|
|
19756
|
+
worktrees,
|
|
19757
|
+
queryForSelector,
|
|
19758
|
+
queryRepositoryForSelector
|
|
19759
|
+
);
|
|
19760
|
+
} catch {
|
|
19761
|
+
connectedWorktrees = { pullRequestsByBranch: /* @__PURE__ */ new Map(), sources: [] };
|
|
19762
|
+
}
|
|
19763
|
+
const { pullRequestsByBranch, sources } = connectedWorktrees;
|
|
19764
|
+
const entries = await buildWorktreePromptEntries(sources, {
|
|
19765
|
+
queryPullRequests: async (_root, branch) => pullRequestsByBranch.get(branch) ?? []
|
|
19766
|
+
});
|
|
19767
|
+
const connectedEntries = entries.filter(
|
|
19768
|
+
(entry) => (entry.pullRequestNumbers?.length ?? 0) > 0
|
|
19769
|
+
);
|
|
19770
|
+
if (connectedEntries.length === 0) {
|
|
19771
|
+
if (lookupError !== null) {
|
|
19772
|
+
options.stderr(
|
|
19773
|
+
`gji pr open: failed to look up open PRs: ${formatError(lookupError)}
|
|
19774
|
+
`
|
|
19775
|
+
);
|
|
19776
|
+
return 1;
|
|
19777
|
+
}
|
|
19778
|
+
options.stderr("gji pr open: no open PR worktrees found\n");
|
|
19779
|
+
return 1;
|
|
19780
|
+
}
|
|
19781
|
+
const selectedPath = await promptForWorktree2(connectedEntries);
|
|
19782
|
+
if (selectedPath === null) {
|
|
19783
|
+
options.stderr("Aborted\n");
|
|
19784
|
+
return 1;
|
|
19785
|
+
}
|
|
19786
|
+
const selectedEntry = connectedEntries.find(
|
|
19787
|
+
(entry) => entry.path === selectedPath
|
|
19788
|
+
);
|
|
19789
|
+
if (selectedEntry === void 0) {
|
|
19790
|
+
options.stderr("gji pr open: selected worktree is no longer available\n");
|
|
19791
|
+
return 1;
|
|
19792
|
+
}
|
|
19793
|
+
return openSelectedEntry(
|
|
19794
|
+
selectedEntry,
|
|
19795
|
+
options,
|
|
19796
|
+
promptForPullRequest,
|
|
19797
|
+
openInBrowser
|
|
19798
|
+
);
|
|
19799
|
+
}
|
|
19800
|
+
async function readConnectedWorktreeSources(repoRoot, repoName, worktrees, queryPullRequests, queryRepositoryPullRequests) {
|
|
19801
|
+
const pullRequestsByBranch = /* @__PURE__ */ new Map();
|
|
19802
|
+
if (queryRepositoryPullRequests !== void 0) {
|
|
19803
|
+
const pullRequests = await queryRepositoryPullRequests(repoRoot);
|
|
19804
|
+
for (const worktree of worktrees) {
|
|
19805
|
+
if (worktree.branch === null) continue;
|
|
19806
|
+
const branchPullRequests = pullRequests.filter(
|
|
19807
|
+
(pullRequest) => pullRequest.sourceBranch === worktree.branch
|
|
19808
|
+
);
|
|
19809
|
+
if (branchPullRequests.length > 0) {
|
|
19810
|
+
pullRequestsByBranch.set(worktree.branch, branchPullRequests);
|
|
19811
|
+
}
|
|
19812
|
+
}
|
|
19813
|
+
} else {
|
|
19814
|
+
const branchResults = await Promise.all(
|
|
19815
|
+
worktrees.map(async (worktree) => {
|
|
19816
|
+
if (worktree.branch === null) {
|
|
19817
|
+
return { pullRequests: [], worktree };
|
|
19818
|
+
}
|
|
19819
|
+
try {
|
|
19820
|
+
return {
|
|
19821
|
+
pullRequests: await queryPullRequests(repoRoot, worktree.branch),
|
|
19822
|
+
worktree
|
|
19823
|
+
};
|
|
19824
|
+
} catch {
|
|
19825
|
+
return { pullRequests: [], worktree };
|
|
19826
|
+
}
|
|
19827
|
+
})
|
|
19828
|
+
);
|
|
19829
|
+
for (const { pullRequests, worktree } of branchResults) {
|
|
19830
|
+
if (worktree.branch !== null && pullRequests.length > 0) {
|
|
19831
|
+
pullRequestsByBranch.set(worktree.branch, pullRequests);
|
|
19832
|
+
}
|
|
19833
|
+
}
|
|
19834
|
+
}
|
|
19835
|
+
const sources = worktrees.flatMap((worktree) => {
|
|
19836
|
+
if (worktree.branch === null || !pullRequestsByBranch.has(worktree.branch)) {
|
|
19837
|
+
return [];
|
|
19838
|
+
}
|
|
19839
|
+
return [{ repoRoot, repoName, worktree }];
|
|
19840
|
+
});
|
|
19841
|
+
return { pullRequestsByBranch, sources };
|
|
19842
|
+
}
|
|
19843
|
+
async function openByNumber(repoRoot, number, options, findOpenPullRequest2, openInBrowser) {
|
|
19844
|
+
let pullRequest;
|
|
19845
|
+
try {
|
|
19846
|
+
pullRequest = await findOpenPullRequest2(repoRoot, number);
|
|
19847
|
+
} catch (error) {
|
|
19848
|
+
options.stderr(
|
|
19849
|
+
`gji pr open: failed to look up PR #${number}: ${formatError(error)}
|
|
19850
|
+
`
|
|
19851
|
+
);
|
|
19852
|
+
return 1;
|
|
19853
|
+
}
|
|
19854
|
+
if (pullRequest === null) {
|
|
19855
|
+
options.stderr(`gji pr open: no open PR found for #${number}
|
|
19856
|
+
`);
|
|
19857
|
+
return 1;
|
|
19858
|
+
}
|
|
19859
|
+
return launchBrowser(pullRequest, options, openInBrowser);
|
|
19860
|
+
}
|
|
19861
|
+
async function openForBranch(repoRoot, branch, options, queryPullRequests, promptForPullRequest, openInBrowser) {
|
|
19862
|
+
if (branch === null) {
|
|
19863
|
+
options.stderr("gji pr open: detached worktrees have no PR branch\n");
|
|
19864
|
+
return 1;
|
|
19865
|
+
}
|
|
19866
|
+
let pullRequests;
|
|
19867
|
+
try {
|
|
19868
|
+
pullRequests = await queryPullRequests(repoRoot, branch);
|
|
19869
|
+
} catch (error) {
|
|
19870
|
+
options.stderr(
|
|
19871
|
+
`gji pr open: failed to look up PRs for branch ${branch}: ${formatError(error)}
|
|
19872
|
+
`
|
|
19873
|
+
);
|
|
19874
|
+
return 1;
|
|
19875
|
+
}
|
|
19876
|
+
return openPullRequests(
|
|
19877
|
+
pullRequests,
|
|
19878
|
+
options,
|
|
19879
|
+
promptForPullRequest,
|
|
19880
|
+
openInBrowser
|
|
19881
|
+
);
|
|
19882
|
+
}
|
|
19883
|
+
async function openSelectedEntry(entry, options, promptForPullRequest, openInBrowser) {
|
|
19884
|
+
const numbers = entry.pullRequestNumbers ?? [];
|
|
19885
|
+
const urls = entry.pullRequestUrls ?? [];
|
|
19886
|
+
const pullRequests = numbers.map((number, index) => ({
|
|
19887
|
+
number,
|
|
19888
|
+
sourceBranch: entry.branch ?? "",
|
|
19889
|
+
url: urls[index] ?? ""
|
|
19890
|
+
})).filter((pullRequest) => pullRequest.url.length > 0);
|
|
19891
|
+
return openPullRequests(
|
|
19892
|
+
pullRequests,
|
|
19893
|
+
options,
|
|
19894
|
+
promptForPullRequest,
|
|
19895
|
+
openInBrowser
|
|
19896
|
+
);
|
|
19897
|
+
}
|
|
19898
|
+
async function openPullRequests(pullRequests, options, promptForPullRequest, openInBrowser) {
|
|
19899
|
+
if (pullRequests.length === 0) {
|
|
19900
|
+
options.stderr("gji pr open: no open PR found for the selected branch\n");
|
|
19901
|
+
return 1;
|
|
19902
|
+
}
|
|
19903
|
+
if (pullRequests.length > 1 && isHeadless()) {
|
|
19904
|
+
options.stderr(
|
|
19905
|
+
"gji pr open: multiple open PRs found; pass an explicit PR number in non-interactive mode (GJI_NO_TUI=1)\n"
|
|
19906
|
+
);
|
|
19907
|
+
return 1;
|
|
19908
|
+
}
|
|
19909
|
+
const selected = pullRequests.length === 1 ? pullRequests[0] : await promptForPullRequest(pullRequests);
|
|
19910
|
+
if (selected === null || selected === void 0) {
|
|
19911
|
+
options.stderr("Aborted\n");
|
|
19912
|
+
return 1;
|
|
19913
|
+
}
|
|
19914
|
+
return launchBrowser(selected, options, openInBrowser);
|
|
19915
|
+
}
|
|
19916
|
+
async function launchBrowser(pullRequest, options, openInBrowser) {
|
|
19917
|
+
try {
|
|
19918
|
+
await openInBrowser(pullRequest.url);
|
|
19919
|
+
} catch (error) {
|
|
19920
|
+
options.stderr(
|
|
19921
|
+
`gji pr open: failed to open PR #${pullRequest.number} in the browser: ${formatError(error)}
|
|
19922
|
+
`
|
|
19923
|
+
);
|
|
19924
|
+
return 1;
|
|
19925
|
+
}
|
|
19926
|
+
options.stdout(`Opened PR #${pullRequest.number}: ${pullRequest.url}
|
|
19927
|
+
`);
|
|
19928
|
+
return 0;
|
|
19929
|
+
}
|
|
19930
|
+
function parsePrNumberTarget(target) {
|
|
19931
|
+
const match = target.match(/^#?(\d+)$/);
|
|
19932
|
+
return match === null ? null : Number(match[1]);
|
|
19933
|
+
}
|
|
19934
|
+
async function defaultPromptForWorktree2(worktrees) {
|
|
19935
|
+
return promptForSingleWorktree(
|
|
19936
|
+
"Choose a worktree with an open PR",
|
|
19937
|
+
worktrees
|
|
19938
|
+
);
|
|
19939
|
+
}
|
|
19940
|
+
async function defaultPromptForPullRequest(pullRequests) {
|
|
19941
|
+
const choice = await ve({
|
|
19942
|
+
message: "Choose a pull request",
|
|
19943
|
+
options: pullRequests.map((pullRequest) => ({
|
|
19944
|
+
value: pullRequest.number,
|
|
19945
|
+
label: `#${pullRequest.number}`,
|
|
19946
|
+
hint: pullRequest.url
|
|
19947
|
+
}))
|
|
19948
|
+
});
|
|
19949
|
+
if (pD(choice)) return null;
|
|
19950
|
+
return pullRequests.find((pullRequest) => pullRequest.number === choice) ?? null;
|
|
19951
|
+
}
|
|
19952
|
+
function formatError(error) {
|
|
19953
|
+
return error instanceof Error ? error.message : String(error);
|
|
19954
|
+
}
|
|
19955
|
+
|
|
17859
19956
|
// src/remove.ts
|
|
17860
|
-
import { basename as
|
|
19957
|
+
import { basename as basename9 } from "node:path";
|
|
17861
19958
|
var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
|
|
17862
19959
|
function createRemoveCommand(dependencies = {}) {
|
|
17863
|
-
const promptForWorktree2 = dependencies.promptForWorktree ??
|
|
19960
|
+
const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree3;
|
|
17864
19961
|
const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval2;
|
|
17865
19962
|
const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ?? defaultConfirmForceRemoveWorktree;
|
|
17866
19963
|
const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
|
|
@@ -17944,7 +20041,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
17944
20041
|
{
|
|
17945
20042
|
branch: worktree.branch ?? void 0,
|
|
17946
20043
|
path: worktree.path,
|
|
17947
|
-
repo:
|
|
20044
|
+
repo: basename9(repository.repoRoot)
|
|
17948
20045
|
},
|
|
17949
20046
|
options.stderr
|
|
17950
20047
|
);
|
|
@@ -18004,7 +20101,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
18004
20101
|
};
|
|
18005
20102
|
}
|
|
18006
20103
|
var runRemoveCommand = createRemoveCommand();
|
|
18007
|
-
async function
|
|
20104
|
+
async function defaultPromptForWorktree3(worktrees) {
|
|
18008
20105
|
return promptForSingleWorktree("Choose a worktree to finish", worktrees);
|
|
18009
20106
|
}
|
|
18010
20107
|
async function defaultConfirmRemoval2(worktree) {
|
|
@@ -18329,11 +20426,11 @@ function resolveConfiguredString2(value) {
|
|
|
18329
20426
|
}
|
|
18330
20427
|
|
|
18331
20428
|
// src/sync-files-command.ts
|
|
18332
|
-
import { homedir as
|
|
18333
|
-
import { join as
|
|
20429
|
+
import { homedir as homedir7 } from "node:os";
|
|
20430
|
+
import { join as join10 } from "node:path";
|
|
18334
20431
|
async function runSyncFilesCommand(options) {
|
|
18335
20432
|
const repository = await detectRepository(options.cwd);
|
|
18336
|
-
const home = options.home ??
|
|
20433
|
+
const home = options.home ?? homedir7();
|
|
18337
20434
|
const loaded = await loadGlobalConfig(home);
|
|
18338
20435
|
const repoEntry = findRepoConfigEntry(
|
|
18339
20436
|
loaded.config,
|
|
@@ -18379,7 +20476,7 @@ function findRepoConfigEntry(config, repoRoot, home) {
|
|
|
18379
20476
|
const repos = config.repos;
|
|
18380
20477
|
if (!isPlainObject3(repos)) return null;
|
|
18381
20478
|
for (const [key, value] of Object.entries(repos)) {
|
|
18382
|
-
if (
|
|
20479
|
+
if (expandTilde3(key, home) === repoRoot && isPlainObject3(value)) {
|
|
18383
20480
|
return { config: value, key };
|
|
18384
20481
|
}
|
|
18385
20482
|
}
|
|
@@ -18463,9 +20560,9 @@ function writeError(options, message) {
|
|
|
18463
20560
|
options.stderr(`gji sync-files: ${message}
|
|
18464
20561
|
`);
|
|
18465
20562
|
}
|
|
18466
|
-
function
|
|
20563
|
+
function expandTilde3(value, home) {
|
|
18467
20564
|
if (value === "~") return home;
|
|
18468
|
-
if (value.startsWith("~/")) return
|
|
20565
|
+
if (value.startsWith("~/")) return join10(home, value.slice(2));
|
|
18469
20566
|
return value;
|
|
18470
20567
|
}
|
|
18471
20568
|
|
|
@@ -18487,7 +20584,9 @@ function readPackageMetadata() {
|
|
|
18487
20584
|
}
|
|
18488
20585
|
async function runCli(argv, options = {}) {
|
|
18489
20586
|
await maybeNotifyForUpdates(argv);
|
|
18490
|
-
|
|
20587
|
+
if (argv[0] !== "doctor") {
|
|
20588
|
+
maybeRegisterCurrentRepo(options.cwd ?? process.cwd());
|
|
20589
|
+
}
|
|
18491
20590
|
const program2 = createProgram();
|
|
18492
20591
|
const cwd = options.cwd ?? process.cwd();
|
|
18493
20592
|
const stdout2 = options.stdout ?? (() => void 0);
|
|
@@ -18502,7 +20601,12 @@ async function runCli(argv, options = {}) {
|
|
|
18502
20601
|
return { exitCode: 0 };
|
|
18503
20602
|
}
|
|
18504
20603
|
try {
|
|
18505
|
-
attachCommandActions(program2, {
|
|
20604
|
+
attachCommandActions(program2, {
|
|
20605
|
+
cwd,
|
|
20606
|
+
prOpenDependencies: options.prOpenDependencies,
|
|
20607
|
+
stderr,
|
|
20608
|
+
stdout: stdout2
|
|
20609
|
+
});
|
|
18506
20610
|
await program2.parseAsync(["node", "gji", ...argv], { from: "node" });
|
|
18507
20611
|
return { exitCode: 0 };
|
|
18508
20612
|
} catch (error) {
|
|
@@ -18538,7 +20642,10 @@ function registerCommands(program2) {
|
|
|
18538
20642
|
program2.command("new [branch]").description("create a new branch or detached linked worktree").option(
|
|
18539
20643
|
"-f, --force",
|
|
18540
20644
|
"remove and recreate the worktree if the target path already exists"
|
|
18541
|
-
).option("--detached", "create a detached worktree without a branch").option(
|
|
20645
|
+
).option("--detached", "create a detached worktree without a branch").option(
|
|
20646
|
+
"--from-current",
|
|
20647
|
+
"base the new branch on the current worktree instead of the main worktree (cannot be combined with --detached)"
|
|
20648
|
+
).option("--open", "open the new worktree in an editor after creation").option(
|
|
18542
20649
|
"--editor <cli>",
|
|
18543
20650
|
"editor CLI to use with --open (code, cursor, zed, \u2026)"
|
|
18544
20651
|
).option(
|
|
@@ -18548,9 +20655,12 @@ function registerCommands(program2) {
|
|
|
18548
20655
|
"--json",
|
|
18549
20656
|
"emit JSON on success or error instead of human-readable output"
|
|
18550
20657
|
).action(notImplemented("new"));
|
|
18551
|
-
program2.command("init [shell]").description(
|
|
20658
|
+
program2.command("init [shell]").description(
|
|
20659
|
+
"set up shell integration interactively or print a shell wrapper"
|
|
20660
|
+
).option("--write", "write the integration to the shell config file").option("--json", "emit a JSON error in non-interactive mode").action(notImplemented("init"));
|
|
20661
|
+
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
20662
|
program2.command("completion [shell]").description("print shell completion definitions").action(notImplemented("completion"));
|
|
18553
|
-
program2.command("pr <ref>").description(
|
|
20663
|
+
const prCommand = program2.command("pr <ref>").usage("[options] <ref>").description(
|
|
18554
20664
|
"fetch a pull request by number, #number, or URL into a linked worktree"
|
|
18555
20665
|
).option(
|
|
18556
20666
|
"--dry-run",
|
|
@@ -18559,11 +20669,12 @@ function registerCommands(program2) {
|
|
|
18559
20669
|
"--json",
|
|
18560
20670
|
"emit JSON on success or error instead of human-readable output"
|
|
18561
20671
|
).action(notImplemented("pr"));
|
|
20672
|
+
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
20673
|
program2.command("back [n]").description(
|
|
18563
20674
|
"navigate to the previously visited worktree, optionally N steps back"
|
|
18564
20675
|
).option("--print", "print the resolved worktree path explicitly").action(notImplemented("back"));
|
|
18565
20676
|
program2.command("history").description("show navigation history").option("--json", "print history as JSON").action(notImplemented("history"));
|
|
18566
|
-
program2.command("open [branch]").description("open the worktree in an editor").option(
|
|
20677
|
+
program2.command("open [branch]").description("open the worktree in an editor").option("--select", "choose a worktree with the interactive selector").option(
|
|
18567
20678
|
"--editor <cli>",
|
|
18568
20679
|
"editor CLI to use (code, cursor, zed, windsurf, subl, \u2026)"
|
|
18569
20680
|
).option("--save", "save the chosen editor to global config").option(
|
|
@@ -18616,16 +20727,17 @@ function registerCommands(program2) {
|
|
|
18616
20727
|
}
|
|
18617
20728
|
function attachCommandActions(program2, options) {
|
|
18618
20729
|
program2.commands.find((command) => command.name() === "new")?.action(
|
|
18619
|
-
async (branch,
|
|
20730
|
+
async (branch, commandOptions2) => {
|
|
18620
20731
|
const exitCode = await runNewCommand({
|
|
18621
20732
|
...options,
|
|
18622
20733
|
branch,
|
|
18623
|
-
detached:
|
|
18624
|
-
dryRun:
|
|
18625
|
-
editor:
|
|
18626
|
-
|
|
18627
|
-
|
|
18628
|
-
|
|
20734
|
+
detached: commandOptions2.detached,
|
|
20735
|
+
dryRun: commandOptions2.dryRun,
|
|
20736
|
+
editor: commandOptions2.editor,
|
|
20737
|
+
fromCurrent: commandOptions2.fromCurrent,
|
|
20738
|
+
force: commandOptions2.force,
|
|
20739
|
+
json: commandOptions2.json,
|
|
20740
|
+
open: commandOptions2.open
|
|
18629
20741
|
});
|
|
18630
20742
|
if (exitCode !== 0) {
|
|
18631
20743
|
throw commanderExit(exitCode);
|
|
@@ -18633,13 +20745,29 @@ function attachCommandActions(program2, options) {
|
|
|
18633
20745
|
}
|
|
18634
20746
|
);
|
|
18635
20747
|
program2.commands.find((command) => command.name() === "init")?.action(
|
|
18636
|
-
async (shell,
|
|
20748
|
+
async (shell, commandOptions2) => {
|
|
18637
20749
|
const exitCode = await runInitCommand({
|
|
18638
20750
|
cwd: options.cwd,
|
|
20751
|
+
json: commandOptions2.json,
|
|
18639
20752
|
shell,
|
|
18640
20753
|
stderr: options.stderr,
|
|
18641
20754
|
stdout: options.stdout,
|
|
18642
|
-
write:
|
|
20755
|
+
write: commandOptions2.write
|
|
20756
|
+
});
|
|
20757
|
+
if (exitCode !== 0) {
|
|
20758
|
+
throw commanderExit(exitCode);
|
|
20759
|
+
}
|
|
20760
|
+
}
|
|
20761
|
+
);
|
|
20762
|
+
program2.commands.find((command) => command.name() === "doctor")?.action(
|
|
20763
|
+
async (commandOptions2) => {
|
|
20764
|
+
const exitCode = await runDoctorCommand({
|
|
20765
|
+
cwd: options.cwd,
|
|
20766
|
+
fix: commandOptions2.fix,
|
|
20767
|
+
json: commandOptions2.json,
|
|
20768
|
+
stderr: options.stderr,
|
|
20769
|
+
stdout: options.stdout,
|
|
20770
|
+
yes: commandOptions2.yes
|
|
18643
20771
|
});
|
|
18644
20772
|
if (exitCode !== 0) {
|
|
18645
20773
|
throw commanderExit(exitCode);
|
|
@@ -18657,11 +20785,11 @@ function attachCommandActions(program2, options) {
|
|
|
18657
20785
|
}
|
|
18658
20786
|
});
|
|
18659
20787
|
program2.commands.find((command) => command.name() === "pr")?.action(
|
|
18660
|
-
async (number,
|
|
20788
|
+
async (number, commandOptions2) => {
|
|
18661
20789
|
const exitCode = await runPrCommand({
|
|
18662
20790
|
cwd: options.cwd,
|
|
18663
|
-
dryRun:
|
|
18664
|
-
json:
|
|
20791
|
+
dryRun: commandOptions2.dryRun,
|
|
20792
|
+
json: commandOptions2.json,
|
|
18665
20793
|
number,
|
|
18666
20794
|
stderr: options.stderr,
|
|
18667
20795
|
stdout: options.stdout
|
|
@@ -18671,8 +20799,24 @@ function attachCommandActions(program2, options) {
|
|
|
18671
20799
|
}
|
|
18672
20800
|
}
|
|
18673
20801
|
);
|
|
20802
|
+
const prOpenCommand = program2.commands.find((command) => command.name() === "pr")?.commands.find((command) => command.name() === "open");
|
|
20803
|
+
const runPrOpenCommand2 = createPrOpenCommand(options.prOpenDependencies);
|
|
20804
|
+
prOpenCommand?.action(
|
|
20805
|
+
async (target, commandOptions2) => {
|
|
20806
|
+
const exitCode = await runPrOpenCommand2({
|
|
20807
|
+
cwd: options.cwd,
|
|
20808
|
+
stderr: options.stderr,
|
|
20809
|
+
stdout: options.stdout,
|
|
20810
|
+
select: commandOptions2.select,
|
|
20811
|
+
target
|
|
20812
|
+
});
|
|
20813
|
+
if (exitCode !== 0) {
|
|
20814
|
+
throw commanderExit(exitCode);
|
|
20815
|
+
}
|
|
20816
|
+
}
|
|
20817
|
+
);
|
|
18674
20818
|
program2.commands.find((command) => command.name() === "back")?.action(
|
|
18675
|
-
async (n,
|
|
20819
|
+
async (n, commandOptions2) => {
|
|
18676
20820
|
if (n !== void 0 && !/^\d+$/.test(n)) {
|
|
18677
20821
|
options.stderr(`gji back: invalid step count: ${n}
|
|
18678
20822
|
`);
|
|
@@ -18682,7 +20826,7 @@ function attachCommandActions(program2, options) {
|
|
|
18682
20826
|
const exitCode = await runBackCommand({
|
|
18683
20827
|
cwd: options.cwd,
|
|
18684
20828
|
n: steps,
|
|
18685
|
-
print:
|
|
20829
|
+
print: commandOptions2.print,
|
|
18686
20830
|
stderr: options.stderr,
|
|
18687
20831
|
stdout: options.stdout
|
|
18688
20832
|
});
|
|
@@ -18691,10 +20835,10 @@ function attachCommandActions(program2, options) {
|
|
|
18691
20835
|
}
|
|
18692
20836
|
}
|
|
18693
20837
|
);
|
|
18694
|
-
program2.commands.find((command) => command.name() === "history")?.action(async (
|
|
20838
|
+
program2.commands.find((command) => command.name() === "history")?.action(async (commandOptions2) => {
|
|
18695
20839
|
const exitCode = await runHistoryCommand({
|
|
18696
20840
|
cwd: options.cwd,
|
|
18697
|
-
json:
|
|
20841
|
+
json: commandOptions2.json,
|
|
18698
20842
|
stdout: options.stdout
|
|
18699
20843
|
});
|
|
18700
20844
|
if (exitCode !== 0) {
|
|
@@ -18702,15 +20846,16 @@ function attachCommandActions(program2, options) {
|
|
|
18702
20846
|
}
|
|
18703
20847
|
});
|
|
18704
20848
|
program2.commands.find((command) => command.name() === "open")?.action(
|
|
18705
|
-
async (branch,
|
|
20849
|
+
async (branch, commandOptions2) => {
|
|
18706
20850
|
const exitCode = await runOpenCommand({
|
|
18707
20851
|
branch,
|
|
18708
20852
|
cwd: options.cwd,
|
|
18709
|
-
editor:
|
|
18710
|
-
save:
|
|
20853
|
+
editor: commandOptions2.editor,
|
|
20854
|
+
save: commandOptions2.save,
|
|
20855
|
+
select: commandOptions2.select,
|
|
18711
20856
|
stderr: options.stderr,
|
|
18712
20857
|
stdout: options.stdout,
|
|
18713
|
-
workspace:
|
|
20858
|
+
workspace: commandOptions2.workspace
|
|
18714
20859
|
});
|
|
18715
20860
|
if (exitCode !== 0) {
|
|
18716
20861
|
throw commanderExit(exitCode);
|
|
@@ -18718,11 +20863,11 @@ function attachCommandActions(program2, options) {
|
|
|
18718
20863
|
}
|
|
18719
20864
|
);
|
|
18720
20865
|
program2.commands.find((command) => command.name() === "go")?.action(
|
|
18721
|
-
async (branch,
|
|
20866
|
+
async (branch, commandOptions2) => {
|
|
18722
20867
|
const exitCode = await runGoCommand({
|
|
18723
20868
|
branch,
|
|
18724
20869
|
cwd: options.cwd,
|
|
18725
|
-
print:
|
|
20870
|
+
print: commandOptions2.print,
|
|
18726
20871
|
stderr: options.stderr,
|
|
18727
20872
|
stdout: options.stdout
|
|
18728
20873
|
});
|
|
@@ -18731,31 +20876,31 @@ function attachCommandActions(program2, options) {
|
|
|
18731
20876
|
}
|
|
18732
20877
|
}
|
|
18733
20878
|
);
|
|
18734
|
-
program2.commands.find((command) => command.name() === "root")?.action(async (
|
|
20879
|
+
program2.commands.find((command) => command.name() === "root")?.action(async (commandOptions2) => {
|
|
18735
20880
|
const exitCode = await runRootCommand({
|
|
18736
20881
|
cwd: options.cwd,
|
|
18737
|
-
print:
|
|
20882
|
+
print: commandOptions2.print,
|
|
18738
20883
|
stdout: options.stdout
|
|
18739
20884
|
});
|
|
18740
20885
|
if (exitCode !== 0) {
|
|
18741
20886
|
throw commanderExit(exitCode);
|
|
18742
20887
|
}
|
|
18743
20888
|
});
|
|
18744
|
-
program2.commands.find((command) => command.name() === "status")?.action(async (
|
|
20889
|
+
program2.commands.find((command) => command.name() === "status")?.action(async (commandOptions2) => {
|
|
18745
20890
|
const exitCode = await runStatusCommand({
|
|
18746
20891
|
cwd: options.cwd,
|
|
18747
|
-
json:
|
|
20892
|
+
json: commandOptions2.json,
|
|
18748
20893
|
stdout: options.stdout
|
|
18749
20894
|
});
|
|
18750
20895
|
if (exitCode !== 0) {
|
|
18751
20896
|
throw commanderExit(exitCode);
|
|
18752
20897
|
}
|
|
18753
20898
|
});
|
|
18754
|
-
program2.commands.find((command) => command.name() === "sync")?.action(async (
|
|
20899
|
+
program2.commands.find((command) => command.name() === "sync")?.action(async (commandOptions2) => {
|
|
18755
20900
|
const exitCode = await runSyncCommand({
|
|
18756
|
-
all:
|
|
20901
|
+
all: commandOptions2.all,
|
|
18757
20902
|
cwd: options.cwd,
|
|
18758
|
-
json:
|
|
20903
|
+
json: commandOptions2.json,
|
|
18759
20904
|
stderr: options.stderr,
|
|
18760
20905
|
stdout: options.stdout
|
|
18761
20906
|
});
|
|
@@ -18766,11 +20911,11 @@ function attachCommandActions(program2, options) {
|
|
|
18766
20911
|
const syncFilesCommand = program2.commands.find(
|
|
18767
20912
|
(command) => command.name() === "sync-files"
|
|
18768
20913
|
);
|
|
18769
|
-
syncFilesCommand?.action(async (
|
|
20914
|
+
syncFilesCommand?.action(async (commandOptions2) => {
|
|
18770
20915
|
const exitCode = await runSyncFilesCommand({
|
|
18771
20916
|
action: "list",
|
|
18772
20917
|
cwd: options.cwd,
|
|
18773
|
-
json:
|
|
20918
|
+
json: commandOptions2.json,
|
|
18774
20919
|
stderr: options.stderr,
|
|
18775
20920
|
stdout: options.stdout
|
|
18776
20921
|
});
|
|
@@ -18778,11 +20923,11 @@ function attachCommandActions(program2, options) {
|
|
|
18778
20923
|
throw commanderExit(exitCode);
|
|
18779
20924
|
}
|
|
18780
20925
|
});
|
|
18781
|
-
syncFilesCommand?.commands.find((command) => command.name() === "list")?.action(async (
|
|
20926
|
+
syncFilesCommand?.commands.find((command) => command.name() === "list")?.action(async (commandOptions2) => {
|
|
18782
20927
|
const exitCode = await runSyncFilesCommand({
|
|
18783
20928
|
action: "list",
|
|
18784
20929
|
cwd: options.cwd,
|
|
18785
|
-
json:
|
|
20930
|
+
json: commandOptions2.json || syncFilesCommand?.opts().json,
|
|
18786
20931
|
stderr: options.stderr,
|
|
18787
20932
|
stdout: options.stdout
|
|
18788
20933
|
});
|
|
@@ -18790,11 +20935,11 @@ function attachCommandActions(program2, options) {
|
|
|
18790
20935
|
throw commanderExit(exitCode);
|
|
18791
20936
|
}
|
|
18792
20937
|
});
|
|
18793
|
-
syncFilesCommand?.commands.find((command) => command.name() === "add")?.action(async (paths,
|
|
20938
|
+
syncFilesCommand?.commands.find((command) => command.name() === "add")?.action(async (paths, commandOptions2) => {
|
|
18794
20939
|
const exitCode = await runSyncFilesCommand({
|
|
18795
20940
|
action: "add",
|
|
18796
20941
|
cwd: options.cwd,
|
|
18797
|
-
json:
|
|
20942
|
+
json: commandOptions2.json || syncFilesCommand?.opts().json,
|
|
18798
20943
|
paths,
|
|
18799
20944
|
stderr: options.stderr,
|
|
18800
20945
|
stdout: options.stdout
|
|
@@ -18803,11 +20948,11 @@ function attachCommandActions(program2, options) {
|
|
|
18803
20948
|
throw commanderExit(exitCode);
|
|
18804
20949
|
}
|
|
18805
20950
|
});
|
|
18806
|
-
const runSyncFilesRemoveCommand = async (paths,
|
|
20951
|
+
const runSyncFilesRemoveCommand = async (paths, commandOptions2) => {
|
|
18807
20952
|
const exitCode = await runSyncFilesCommand({
|
|
18808
20953
|
action: "remove",
|
|
18809
20954
|
cwd: options.cwd,
|
|
18810
|
-
json:
|
|
20955
|
+
json: commandOptions2.json || syncFilesCommand?.opts().json,
|
|
18811
20956
|
paths,
|
|
18812
20957
|
stderr: options.stderr,
|
|
18813
20958
|
stdout: options.stdout
|
|
@@ -18817,11 +20962,11 @@ function attachCommandActions(program2, options) {
|
|
|
18817
20962
|
}
|
|
18818
20963
|
};
|
|
18819
20964
|
syncFilesCommand?.commands.find((command) => command.name() === "remove")?.action(runSyncFilesRemoveCommand);
|
|
18820
|
-
program2.commands.find((command) => command.name() === "ls")?.action(async (
|
|
20965
|
+
program2.commands.find((command) => command.name() === "ls")?.action(async (commandOptions2) => {
|
|
18821
20966
|
const exitCode = await runLsCommand({
|
|
18822
|
-
compact:
|
|
20967
|
+
compact: commandOptions2.compact,
|
|
18823
20968
|
cwd: options.cwd,
|
|
18824
|
-
json:
|
|
20969
|
+
json: commandOptions2.json,
|
|
18825
20970
|
stdout: options.stdout
|
|
18826
20971
|
});
|
|
18827
20972
|
if (exitCode !== 0) {
|
|
@@ -18829,13 +20974,13 @@ function attachCommandActions(program2, options) {
|
|
|
18829
20974
|
}
|
|
18830
20975
|
});
|
|
18831
20976
|
program2.commands.find((command) => command.name() === "clean")?.action(
|
|
18832
|
-
async (
|
|
20977
|
+
async (commandOptions2) => {
|
|
18833
20978
|
const exitCode = await runCleanCommand({
|
|
18834
20979
|
cwd: options.cwd,
|
|
18835
|
-
dryRun:
|
|
18836
|
-
force:
|
|
18837
|
-
json:
|
|
18838
|
-
stale:
|
|
20980
|
+
dryRun: commandOptions2.dryRun,
|
|
20981
|
+
force: commandOptions2.force,
|
|
20982
|
+
json: commandOptions2.json,
|
|
20983
|
+
stale: commandOptions2.stale,
|
|
18839
20984
|
stderr: options.stderr,
|
|
18840
20985
|
stdout: options.stdout
|
|
18841
20986
|
});
|
|
@@ -18844,13 +20989,13 @@ function attachCommandActions(program2, options) {
|
|
|
18844
20989
|
}
|
|
18845
20990
|
}
|
|
18846
20991
|
);
|
|
18847
|
-
const runRemovalCommand = async (branch,
|
|
20992
|
+
const runRemovalCommand = async (branch, commandOptions2 = {}) => {
|
|
18848
20993
|
const exitCode = await runRemoveCommand({
|
|
18849
20994
|
branch,
|
|
18850
20995
|
cwd: options.cwd,
|
|
18851
|
-
dryRun:
|
|
18852
|
-
force:
|
|
18853
|
-
json:
|
|
20996
|
+
dryRun: commandOptions2.dryRun,
|
|
20997
|
+
force: commandOptions2.force,
|
|
20998
|
+
json: commandOptions2.json,
|
|
18854
20999
|
stderr: options.stderr,
|
|
18855
21000
|
stdout: options.stdout
|
|
18856
21001
|
});
|
|
@@ -18870,14 +21015,14 @@ function attachCommandActions(program2, options) {
|
|
|
18870
21015
|
}
|
|
18871
21016
|
});
|
|
18872
21017
|
program2.commands.find((command) => command.name() === "warp")?.action(
|
|
18873
|
-
async (branch,
|
|
18874
|
-
const newFlag =
|
|
21018
|
+
async (branch, commandOptions2) => {
|
|
21019
|
+
const newFlag = commandOptions2.new;
|
|
18875
21020
|
const newWorktree = newFlag !== void 0 && newFlag !== false;
|
|
18876
21021
|
const newBranch = typeof newFlag === "string" ? newFlag : void 0;
|
|
18877
21022
|
const exitCode = await runWarpCommand({
|
|
18878
21023
|
branch: newWorktree ? newBranch ?? branch : branch,
|
|
18879
21024
|
cwd: options.cwd,
|
|
18880
|
-
json:
|
|
21025
|
+
json: commandOptions2.json,
|
|
18881
21026
|
newWorktree,
|
|
18882
21027
|
stderr: options.stderr,
|
|
18883
21028
|
stdout: options.stdout
|
|
@@ -18971,18 +21116,21 @@ async function main() {
|
|
|
18971
21116
|
}
|
|
18972
21117
|
async function warnIfMissingShellIntegration() {
|
|
18973
21118
|
try {
|
|
18974
|
-
const { config } = await loadGlobalConfig(
|
|
21119
|
+
const { config } = await loadGlobalConfig(homedir8());
|
|
18975
21120
|
if (!config.shellIntegration) {
|
|
18976
|
-
const
|
|
18977
|
-
const shellArg = shellBin && ["bash", "zsh", "fish"].includes(shellBin) ? ` ${shellBin}` : "";
|
|
21121
|
+
const setupCommand = isHeadless() ? legacyShellSetupCommand() : "gji init";
|
|
18978
21122
|
process.stderr.write(
|
|
18979
|
-
`gji: shell integration not set up \u2014 run
|
|
21123
|
+
`gji: shell integration not set up \u2014 run \`${setupCommand}\` to enable automatic cd.
|
|
18980
21124
|
`
|
|
18981
21125
|
);
|
|
18982
21126
|
}
|
|
18983
21127
|
} catch {
|
|
18984
21128
|
}
|
|
18985
21129
|
}
|
|
21130
|
+
function legacyShellSetupCommand() {
|
|
21131
|
+
const shell = (process.env.SHELL ?? "").split("/").at(-1);
|
|
21132
|
+
return shell && ["bash", "fish", "zsh"].includes(shell) ? `gji init ${shell} --write` : "gji init <shell> --write";
|
|
21133
|
+
}
|
|
18986
21134
|
void main();
|
|
18987
21135
|
/*! Bundled license information:
|
|
18988
21136
|
|