@solaqua/gji 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -30
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +37 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +59 -4
- package/dist/doctor.d.ts +28 -0
- package/dist/doctor.js +454 -0
- package/dist/gji-bundle.mjs +2453 -350
- package/dist/go.d.ts +2 -1
- package/dist/go.js +4 -1
- package/dist/index.js +12 -6
- package/dist/init.d.ts +15 -0
- package/dist/init.js +232 -16
- package/dist/open.d.ts +2 -1
- package/dist/open.js +2 -1
- package/dist/pr-open.d.ts +19 -0
- package/dist/pr-open.js +290 -0
- package/dist/pull-requests.d.ts +33 -0
- package/dist/pull-requests.js +384 -0
- package/dist/repo-registry.d.ts +5 -0
- package/dist/repo-registry.js +87 -14
- package/dist/shell-completion.js +278 -10
- package/dist/shell-setup.d.ts +9 -0
- package/dist/shell-setup.js +56 -0
- package/dist/warp.d.ts +3 -0
- package/dist/warp.js +8 -3
- package/dist/worktree-picker.d.ts +12 -1
- package/dist/worktree-picker.js +84 -6
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +1 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-doctor.1 +19 -0
- package/man/man1/gji-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +6 -3
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +1 -1
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +5 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ That matters even more in AI-assisted workflows, where one repository often has
|
|
|
9
9
|
```sh
|
|
10
10
|
gji new feature/payment-refactor # new branch + worktree, cd in
|
|
11
11
|
gji pr 1234 # review PR in isolation, cd in
|
|
12
|
+
gji pr open # open the PR for the current worktree
|
|
12
13
|
gji go main # jump back, shell changes directory
|
|
13
14
|
gji remove feature/payment-refactor
|
|
14
15
|
```
|
|
@@ -69,36 +70,18 @@ That makes Git worktrees more important, because a single shared checkout become
|
|
|
69
70
|
npm install -g @solaqua/gji
|
|
70
71
|
```
|
|
71
72
|
|
|
72
|
-
Then
|
|
73
|
+
Then run the guided setup in an interactive terminal:
|
|
73
74
|
|
|
74
75
|
```sh
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# bash
|
|
79
|
-
echo 'eval "$(gji init bash)"' >> ~/.bashrc
|
|
80
|
-
|
|
81
|
-
# fish
|
|
82
|
-
gji init fish --write
|
|
83
|
-
source ~/.config/fish/config.fish
|
|
76
|
+
gji init
|
|
77
|
+
# restart your shell, or source the rc file shown by the wizard
|
|
78
|
+
gji doctor
|
|
84
79
|
```
|
|
85
80
|
|
|
86
|
-
|
|
81
|
+
The wizard installs shell integration, completions, and an available editor. In a non-interactive environment, use the existing explicit shell command instead:
|
|
87
82
|
|
|
88
83
|
```sh
|
|
89
|
-
|
|
90
|
-
mkdir -p ~/.zsh/completions
|
|
91
|
-
gji completion zsh > ~/.zsh/completions/_gji
|
|
92
|
-
# add this before running compinit in ~/.zshrc
|
|
93
|
-
fpath=(~/.zsh/completions $fpath)
|
|
94
|
-
|
|
95
|
-
# bash
|
|
96
|
-
mkdir -p ~/.local/share/bash-completion/completions
|
|
97
|
-
gji completion bash > ~/.local/share/bash-completion/completions/gji
|
|
98
|
-
|
|
99
|
-
# fish
|
|
100
|
-
mkdir -p ~/.config/fish/completions
|
|
101
|
-
gji completion fish > ~/.config/fish/completions/gji.fish
|
|
84
|
+
gji init zsh --write
|
|
102
85
|
```
|
|
103
86
|
|
|
104
87
|
## Quick start
|
|
@@ -112,6 +95,10 @@ gji new feature/dark-mode --open --editor cursor
|
|
|
112
95
|
|
|
113
96
|
# review a pull request
|
|
114
97
|
gji pr 1234
|
|
98
|
+
gji pr open # open the PR for the current worktree
|
|
99
|
+
gji pr open --select # choose a PR from any linked worktree
|
|
100
|
+
gji pr open feature/auth-refactor # open the PR for a branch
|
|
101
|
+
gji pr open '#1234' # open an open PR directly
|
|
115
102
|
|
|
116
103
|
# see what's open
|
|
117
104
|
gji status
|
|
@@ -178,13 +165,14 @@ See the full comparison in [website/docs/comparison.mdx](./website/docs/comparis
|
|
|
178
165
|
|
|
179
166
|
## Shell setup
|
|
180
167
|
|
|
181
|
-
Without shell integration `gji` prints paths and exits — which is fine for scripts but means it cannot `cd` you into a new worktree. Install the integration once:
|
|
168
|
+
Without shell integration `gji` prints paths and exits — which is fine for scripts but means it cannot `cd` you into a new worktree. Install the integration, completions, and an editor once with:
|
|
182
169
|
|
|
183
170
|
```sh
|
|
184
|
-
gji init
|
|
171
|
+
gji init
|
|
172
|
+
gji doctor
|
|
185
173
|
```
|
|
186
174
|
|
|
187
|
-
|
|
175
|
+
`gji init` is interactive. For dotfiles or CI, the explicit shell commands remain available and preserve their existing output:
|
|
188
176
|
|
|
189
177
|
```sh
|
|
190
178
|
# zsh
|
|
@@ -243,6 +231,7 @@ path=$(gji root --print)
|
|
|
243
231
|
|---|---|
|
|
244
232
|
| `gji new [branch] [--detached] [--open] [--editor <cli>] [--json]` | create branch + worktree, cd in (validates branch name against Git rules) |
|
|
245
233
|
| `gji pr <ref> [--json]` | fetch PR ref, create worktree, cd in |
|
|
234
|
+
| `gji pr open [branch|#N] [--select]` | open the current worktree PR, or choose a linked worktree with `--select` |
|
|
246
235
|
| `gji open [branch] [--editor <cli>] [--save] [--workspace]` | open a worktree in an editor |
|
|
247
236
|
| `gji go [branch] [--print]` | jump to a worktree |
|
|
248
237
|
| `gji root [--print]` | jump to the main repo root |
|
|
@@ -254,7 +243,8 @@ path=$(gji root --print)
|
|
|
254
243
|
| `gji remove [branch] [--force] [--json]` | remove a worktree and its branch |
|
|
255
244
|
| `gji trigger-hook <hook>` | run a hook in the current worktree |
|
|
256
245
|
| `gji config [get\|set\|unset] [key] [value]` | manage global defaults |
|
|
257
|
-
| `gji init [shell]` |
|
|
246
|
+
| `gji init [shell]` | interactively set up onboarding, or print/install a shell wrapper |
|
|
247
|
+
| `gji doctor [--json] [--fix] [--yes]` | check installation and configuration health; optionally remove stale repository entries |
|
|
258
248
|
| `gji completion [shell]` | print shell completion definitions |
|
|
259
249
|
|
|
260
250
|
## Configuration
|
|
@@ -275,7 +265,7 @@ No setup required. Optional config lives in:
|
|
|
275
265
|
| `syncDefaultBranch` | branch to rebase onto (default: remote `HEAD`) |
|
|
276
266
|
| `syncFiles` | files to copy from main worktree into each new worktree; use global per-repo config for private files |
|
|
277
267
|
| `skipInstallPrompt` | `true` to disable the auto-install prompt permanently |
|
|
278
|
-
| `installSaveTarget` | `"local"` or `"global"` — where **Always**/**Never** choices are persisted (default: `"local"`); set
|
|
268
|
+
| `installSaveTarget` | `"local"` or `"global"` — where **Always**/**Never** choices are persisted (default: `"local"`); set during `gji init <shell> --write` |
|
|
279
269
|
| `hooks` | lifecycle scripts (see [Hooks](#hooks)) |
|
|
280
270
|
| `repos` | per-repo overrides inside the global config (see below) |
|
|
281
271
|
|
|
@@ -477,7 +467,10 @@ GJI_NO_TUI=1 gji remove --force feature/ci-branch
|
|
|
477
467
|
GJI_NO_TUI=1 gji clean --force
|
|
478
468
|
```
|
|
479
469
|
|
|
480
|
-
`GJI_NO_TUI=1` disables all prompts. Commands that need confirmation require `--force
|
|
470
|
+
`GJI_NO_TUI=1` disables all prompts. Commands that need confirmation require their non-interactive approval flag (`--force` for cleanup commands, `--yes` for `doctor --fix`). `--json` implies the same behaviour.
|
|
471
|
+
|
|
472
|
+
`gji pr open --select` requires an interactive terminal; plain `gji pr open` opens the PR for the current worktree without prompting.
|
|
473
|
+
If the current worktree has no open PR or has multiple open PRs in headless mode, pass `gji pr open <branch|#N>` explicitly.
|
|
481
474
|
|
|
482
475
|
Update notifications are also suppressed automatically in non-interactive and `--json` runs. Users can opt out explicitly with `NO_UPDATE_NOTIFIER=1` or `--no-update-notifier`.
|
|
483
476
|
|
|
@@ -486,6 +479,7 @@ Update notifications are also suppressed automatically in non-interactive and `-
|
|
|
486
479
|
- Works from either the main repo root or inside any linked worktree
|
|
487
480
|
- The current worktree is never offered as a `gji clean` candidate
|
|
488
481
|
- `gji pr` fetches from `origin` using the first matching forge ref namespace: GitHub `refs/pull/<number>/head`, GitLab `refs/merge-requests/<number>/head`, then Bitbucket `refs/pull-requests/<number>/from`
|
|
482
|
+
- `gji pr open` reads open PRs from the `origin` forge (GitHub, GitLab, or Bitbucket), preferring an installed authenticated provider CLI and falling back to its public API
|
|
489
483
|
|
|
490
484
|
## License
|
|
491
485
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type BrowserCommandRunner = (command: string, args: string[]) => Promise<void>;
|
|
2
|
+
export interface BrowserOpenerDependencies {
|
|
3
|
+
platform?: NodeJS.Platform;
|
|
4
|
+
runCommand?: BrowserCommandRunner;
|
|
5
|
+
}
|
|
6
|
+
export declare function createBrowserOpener(dependencies?: BrowserOpenerDependencies): (url: string) => Promise<void>;
|
|
7
|
+
export declare const openBrowser: (url: string) => Promise<void>;
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { platform } from "node:process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
export function createBrowserOpener(dependencies = {}) {
|
|
6
|
+
const currentPlatform = dependencies.platform ?? platform;
|
|
7
|
+
const runCommand = dependencies.runCommand ?? defaultRunCommand;
|
|
8
|
+
return async function openBrowser(url) {
|
|
9
|
+
validateBrowserUrl(url);
|
|
10
|
+
const command = browserCommand(currentPlatform, url);
|
|
11
|
+
await runCommand(command.command, command.args);
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export const openBrowser = createBrowserOpener();
|
|
15
|
+
function browserCommand(currentPlatform, url) {
|
|
16
|
+
if (currentPlatform === "darwin")
|
|
17
|
+
return { args: [url], command: "open" };
|
|
18
|
+
if (currentPlatform === "win32") {
|
|
19
|
+
return { args: [url], command: "explorer.exe" };
|
|
20
|
+
}
|
|
21
|
+
return { args: [url], command: "xdg-open" };
|
|
22
|
+
}
|
|
23
|
+
function validateBrowserUrl(url) {
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = new URL(url);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new Error("browser URL is invalid");
|
|
30
|
+
}
|
|
31
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
32
|
+
throw new Error("browser URL must use http or https");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function defaultRunCommand(command, args) {
|
|
36
|
+
await execFileAsync(command, args, { timeout: 10_000 });
|
|
37
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { type PrOpenCommandDependencies } from "./pr-open.js";
|
|
2
3
|
export interface RunCliOptions {
|
|
3
4
|
cwd?: string;
|
|
5
|
+
prOpenDependencies?: Partial<PrOpenCommandDependencies>;
|
|
4
6
|
stderr?: (chunk: string) => void;
|
|
5
7
|
stdout?: (chunk: string) => void;
|
|
6
8
|
}
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { runBackCommand } from "./back.js";
|
|
|
5
5
|
import { runCleanCommand } from "./clean.js";
|
|
6
6
|
import { runCompletionCommand } from "./completion.js";
|
|
7
7
|
import { runConfigCommand } from "./config-command.js";
|
|
8
|
+
import { runDoctorCommand } from "./doctor.js";
|
|
8
9
|
import { runGoCommand } from "./go.js";
|
|
9
10
|
import { isHeadless } from "./headless.js";
|
|
10
11
|
import { runHistoryCommand } from "./history-command.js";
|
|
@@ -13,6 +14,7 @@ import { runLsCommand } from "./ls.js";
|
|
|
13
14
|
import { runNewCommand } from "./new.js";
|
|
14
15
|
import { runOpenCommand } from "./open.js";
|
|
15
16
|
import { runPrCommand } from "./pr.js";
|
|
17
|
+
import { createPrOpenCommand, } from "./pr-open.js";
|
|
16
18
|
import { runRemoveCommand } from "./remove.js";
|
|
17
19
|
import { detectRepository } from "./repo.js";
|
|
18
20
|
import { registerRepo } from "./repo-registry.js";
|
|
@@ -44,7 +46,9 @@ function readPackageMetadata() {
|
|
|
44
46
|
}
|
|
45
47
|
export async function runCli(argv, options = {}) {
|
|
46
48
|
await maybeNotifyForUpdates(argv);
|
|
47
|
-
|
|
49
|
+
if (argv[0] !== "doctor") {
|
|
50
|
+
maybeRegisterCurrentRepo(options.cwd ?? process.cwd());
|
|
51
|
+
}
|
|
48
52
|
const program = createProgram();
|
|
49
53
|
const cwd = options.cwd ?? process.cwd();
|
|
50
54
|
const stdout = options.stdout ?? (() => undefined);
|
|
@@ -59,7 +63,12 @@ export async function runCli(argv, options = {}) {
|
|
|
59
63
|
return { exitCode: 0 };
|
|
60
64
|
}
|
|
61
65
|
try {
|
|
62
|
-
attachCommandActions(program, {
|
|
66
|
+
attachCommandActions(program, {
|
|
67
|
+
cwd,
|
|
68
|
+
prOpenDependencies: options.prOpenDependencies,
|
|
69
|
+
stderr,
|
|
70
|
+
stdout,
|
|
71
|
+
});
|
|
63
72
|
await program.parseAsync(["node", "gji", ...argv], { from: "node" });
|
|
64
73
|
return { exitCode: 0 };
|
|
65
74
|
}
|
|
@@ -118,19 +127,33 @@ function registerCommands(program) {
|
|
|
118
127
|
.action(notImplemented("new"));
|
|
119
128
|
program
|
|
120
129
|
.command("init [shell]")
|
|
121
|
-
.description("
|
|
130
|
+
.description("set up shell integration interactively or print a shell wrapper")
|
|
122
131
|
.option("--write", "write the integration to the shell config file")
|
|
132
|
+
.option("--json", "emit a JSON error in non-interactive mode")
|
|
123
133
|
.action(notImplemented("init"));
|
|
134
|
+
program
|
|
135
|
+
.command("doctor")
|
|
136
|
+
.description("check gji installation and configuration health")
|
|
137
|
+
.option("--fix", "apply safe automatic fixes after showing the plan")
|
|
138
|
+
.option("--yes", "apply --fix without prompting")
|
|
139
|
+
.option("--json", "emit diagnostic checks as JSON")
|
|
140
|
+
.action(notImplemented("doctor"));
|
|
124
141
|
program
|
|
125
142
|
.command("completion [shell]")
|
|
126
143
|
.description("print shell completion definitions")
|
|
127
144
|
.action(notImplemented("completion"));
|
|
128
|
-
program
|
|
145
|
+
const prCommand = program
|
|
129
146
|
.command("pr <ref>")
|
|
147
|
+
.usage("[options] <ref>")
|
|
130
148
|
.description("fetch a pull request by number, #number, or URL into a linked worktree")
|
|
131
149
|
.option("--dry-run", "show what would be created without executing any git commands or writing files")
|
|
132
150
|
.option("--json", "emit JSON on success or error instead of human-readable output")
|
|
133
151
|
.action(notImplemented("pr"));
|
|
152
|
+
prCommand
|
|
153
|
+
.command("open [target]")
|
|
154
|
+
.description("open an existing pull request in the default browser")
|
|
155
|
+
.option("--select", "choose a pull request from any linked worktree")
|
|
156
|
+
.action(notImplemented("pr open"));
|
|
134
157
|
program
|
|
135
158
|
.command("back [n]")
|
|
136
159
|
.description("navigate to the previously visited worktree, optionally N steps back")
|
|
@@ -267,6 +290,7 @@ function attachCommandActions(program, options) {
|
|
|
267
290
|
?.action(async (shell, commandOptions) => {
|
|
268
291
|
const exitCode = await runInitCommand({
|
|
269
292
|
cwd: options.cwd,
|
|
293
|
+
json: commandOptions.json,
|
|
270
294
|
shell,
|
|
271
295
|
stderr: options.stderr,
|
|
272
296
|
stdout: options.stdout,
|
|
@@ -276,6 +300,21 @@ function attachCommandActions(program, options) {
|
|
|
276
300
|
throw commanderExit(exitCode);
|
|
277
301
|
}
|
|
278
302
|
});
|
|
303
|
+
program.commands
|
|
304
|
+
.find((command) => command.name() === "doctor")
|
|
305
|
+
?.action(async (commandOptions) => {
|
|
306
|
+
const exitCode = await runDoctorCommand({
|
|
307
|
+
cwd: options.cwd,
|
|
308
|
+
fix: commandOptions.fix,
|
|
309
|
+
json: commandOptions.json,
|
|
310
|
+
stderr: options.stderr,
|
|
311
|
+
stdout: options.stdout,
|
|
312
|
+
yes: commandOptions.yes,
|
|
313
|
+
});
|
|
314
|
+
if (exitCode !== 0) {
|
|
315
|
+
throw commanderExit(exitCode);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
279
318
|
program.commands
|
|
280
319
|
.find((command) => command.name() === "completion")
|
|
281
320
|
?.action(async (shell) => {
|
|
@@ -303,6 +342,22 @@ function attachCommandActions(program, options) {
|
|
|
303
342
|
throw commanderExit(exitCode);
|
|
304
343
|
}
|
|
305
344
|
});
|
|
345
|
+
const prOpenCommand = program.commands
|
|
346
|
+
.find((command) => command.name() === "pr")
|
|
347
|
+
?.commands.find((command) => command.name() === "open");
|
|
348
|
+
const runPrOpenCommand = createPrOpenCommand(options.prOpenDependencies);
|
|
349
|
+
prOpenCommand?.action(async (target, commandOptions) => {
|
|
350
|
+
const exitCode = await runPrOpenCommand({
|
|
351
|
+
cwd: options.cwd,
|
|
352
|
+
stderr: options.stderr,
|
|
353
|
+
stdout: options.stdout,
|
|
354
|
+
select: commandOptions.select,
|
|
355
|
+
target,
|
|
356
|
+
});
|
|
357
|
+
if (exitCode !== 0) {
|
|
358
|
+
throw commanderExit(exitCode);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
306
361
|
program.commands
|
|
307
362
|
.find((command) => command.name() === "back")
|
|
308
363
|
?.action(async (n, commandOptions) => {
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type DoctorCheckStatus = "fail" | "ok" | "skip";
|
|
2
|
+
export interface DoctorCheck {
|
|
3
|
+
hint?: string;
|
|
4
|
+
id: string;
|
|
5
|
+
message: string;
|
|
6
|
+
status: DoctorCheckStatus;
|
|
7
|
+
}
|
|
8
|
+
export type DoctorFixStatus = "applied" | "declined" | "failed" | "pending" | "skipped";
|
|
9
|
+
export interface DoctorFix {
|
|
10
|
+
hint?: string;
|
|
11
|
+
id: string;
|
|
12
|
+
message: string;
|
|
13
|
+
paths?: string[];
|
|
14
|
+
status: DoctorFixStatus;
|
|
15
|
+
}
|
|
16
|
+
export interface DoctorCommandOptions {
|
|
17
|
+
cwd: string;
|
|
18
|
+
home?: string;
|
|
19
|
+
fix?: boolean;
|
|
20
|
+
json?: boolean;
|
|
21
|
+
interactive?: boolean;
|
|
22
|
+
confirmFixes?: (fixes: DoctorFix[]) => Promise<boolean>;
|
|
23
|
+
shell?: string;
|
|
24
|
+
yes?: boolean;
|
|
25
|
+
stderr?: (chunk: string) => void;
|
|
26
|
+
stdout: (chunk: string) => void;
|
|
27
|
+
}
|
|
28
|
+
export declare function runDoctorCommand(options: DoctorCommandOptions): Promise<number>;
|