@solaqua/gji 0.5.0 → 0.6.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 +37 -3
- package/dist/cli.js +64 -1
- package/dist/config.js +1 -0
- package/dist/editor.d.ts +8 -0
- package/dist/editor.js +17 -0
- package/dist/gji-bundle.mjs +1306 -760
- package/dist/go.js +22 -4
- package/dist/hooks.d.ts +5 -4
- package/dist/hooks.js +61 -9
- package/dist/init.js +26 -11
- package/dist/install-prompt.js +9 -1
- package/dist/new.d.ts +3 -0
- package/dist/new.js +33 -1
- package/dist/open.d.ts +20 -0
- package/dist/open.js +155 -0
- package/dist/repo-registry.d.ts +8 -0
- package/dist/repo-registry.js +52 -0
- package/dist/warp.d.ts +20 -0
- package/dist/warp.js +196 -0
- 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-go.1 +1 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +1 -1
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +7 -1
- package/man/man1/gji-open.1 +19 -0
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-trigger-hook.1 +1 -1
- package/man/man1/gji-warp.1 +19 -0
- package/man/man1/gji.1 +11 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -107,6 +107,9 @@ gji completion fish > ~/.config/fish/completions/gji.fish
|
|
|
107
107
|
# start a new task
|
|
108
108
|
gji new feature/dark-mode
|
|
109
109
|
|
|
110
|
+
# start a task and open it straight in your editor
|
|
111
|
+
gji new feature/dark-mode --open --editor cursor
|
|
112
|
+
|
|
110
113
|
# review a pull request
|
|
111
114
|
gji pr 1234
|
|
112
115
|
|
|
@@ -117,6 +120,10 @@ gji status
|
|
|
117
120
|
gji go feature/dark-mode
|
|
118
121
|
gji go main
|
|
119
122
|
|
|
123
|
+
# open any worktree in an editor (interactive picker)
|
|
124
|
+
gji open
|
|
125
|
+
gji open feature/dark-mode --editor code
|
|
126
|
+
|
|
120
127
|
# clean up when done
|
|
121
128
|
gji remove feature/dark-mode
|
|
122
129
|
```
|
|
@@ -234,8 +241,9 @@ path=$(gji root --print)
|
|
|
234
241
|
|
|
235
242
|
| Command | Description |
|
|
236
243
|
|---|---|
|
|
237
|
-
| `gji new [branch] [--detached] [--json]` | create branch + worktree, cd in (validates branch name against Git rules) |
|
|
244
|
+
| `gji new [branch] [--detached] [--open] [--editor <cli>] [--json]` | create branch + worktree, cd in (validates branch name against Git rules) |
|
|
238
245
|
| `gji pr <ref> [--json]` | fetch PR ref, create worktree, cd in |
|
|
246
|
+
| `gji open [branch] [--editor <cli>] [--save] [--workspace]` | open a worktree in an editor |
|
|
239
247
|
| `gji go [branch] [--print]` | jump to a worktree |
|
|
240
248
|
| `gji root [--print]` | jump to the main repo root |
|
|
241
249
|
| `gji status [--json]` | repo overview, worktree health, ahead/behind |
|
|
@@ -260,6 +268,7 @@ No setup required. Optional config lives in:
|
|
|
260
268
|
| Key | Description |
|
|
261
269
|
|---|---|
|
|
262
270
|
| `branchPrefix` | prefix added to new branch names (e.g. `"feature/"`) |
|
|
271
|
+
| `editor` | default editor CLI for `gji open` and `gji new --open` (e.g. `"cursor"`, `"code"`, `"zed"`); set automatically with `gji open --save` |
|
|
263
272
|
| `worktreePath` | base directory for new worktrees (absolute or `~/…`); overrides the default `../worktrees/<repo>/` layout |
|
|
264
273
|
| `syncRemote` | remote for `gji sync` (default: `origin`) |
|
|
265
274
|
| `syncDefaultBranch` | branch to rebase onto (default: remote `HEAD`) |
|
|
@@ -314,8 +323,8 @@ Run scripts automatically at key lifecycle moments:
|
|
|
314
323
|
```json
|
|
315
324
|
{
|
|
316
325
|
"hooks": {
|
|
317
|
-
"afterCreate": "pnpm install",
|
|
318
|
-
"afterEnter": "
|
|
326
|
+
"afterCreate": ["pnpm", "install"],
|
|
327
|
+
"afterEnter": ["printf", "switched to %s\n", "{{branch}}"],
|
|
319
328
|
"beforeRemove": "pnpm run cleanup"
|
|
320
329
|
}
|
|
321
330
|
}
|
|
@@ -329,6 +338,31 @@ Run scripts automatically at key lifecycle moments:
|
|
|
329
338
|
|
|
330
339
|
Hooks receive `{{branch}}`, `{{path}}`, `{{repo}}` as template variables and `GJI_BRANCH`, `GJI_PATH`, `GJI_REPO` as environment variables. A failing hook emits a warning but never aborts the command.
|
|
331
340
|
|
|
341
|
+
Prefer argv-array hooks for simple commands:
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"hooks": {
|
|
346
|
+
"afterCreate": ["pnpm", "install"],
|
|
347
|
+
"afterEnter": ["printf", "switched to %s at %s\n", "{{branch}}", "{{path}}"]
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Array hooks run without a shell and pass each array item as exactly one argument. Use string hooks only when you need shell features like `&&`, pipes, redirects, shell functions, or `nvm use`.
|
|
353
|
+
|
|
354
|
+
Template values are interpolated before the shell parses string hooks, so avoid putting `{{branch}}`, `{{path}}`, or `{{repo}}` directly into shell strings. For shell-string hooks, the safer pattern is to use the environment variables and double-quote each expansion:
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"hooks": {
|
|
359
|
+
"afterCreate": "pnpm install && printf 'ready: %s\n' \"$GJI_PATH\""
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Avoid unquoted template values in shell strings, such as `echo {{branch}}` or `cd {{path}}`.
|
|
365
|
+
|
|
332
366
|
Hooks from all three config layers merge per key — different keys from different layers both apply, same key the higher-precedence layer wins:
|
|
333
367
|
|
|
334
368
|
```jsonc
|
package/dist/cli.js
CHANGED
|
@@ -7,16 +7,20 @@ import { runHistoryCommand } from './history-command.js';
|
|
|
7
7
|
import { runCompletionCommand } from './completion.js';
|
|
8
8
|
import { runConfigCommand } from './config-command.js';
|
|
9
9
|
import { runGoCommand } from './go.js';
|
|
10
|
+
import { runOpenCommand } from './open.js';
|
|
10
11
|
import { isHeadless } from './headless.js';
|
|
11
12
|
import { runInitCommand } from './init.js';
|
|
12
13
|
import { runLsCommand } from './ls.js';
|
|
13
14
|
import { runNewCommand } from './new.js';
|
|
14
15
|
import { runPrCommand } from './pr.js';
|
|
15
16
|
import { runRemoveCommand } from './remove.js';
|
|
17
|
+
import { registerRepo } from './repo-registry.js';
|
|
18
|
+
import { detectRepository } from './repo.js';
|
|
16
19
|
import { runRootCommand } from './root.js';
|
|
17
20
|
import { runStatusCommand } from './status.js';
|
|
18
21
|
import { runSyncCommand } from './sync.js';
|
|
19
22
|
import { runTriggerHookCommand } from './trigger-hook.js';
|
|
23
|
+
import { runWarpCommand } from './warp.js';
|
|
20
24
|
export function createProgram() {
|
|
21
25
|
const program = new Command();
|
|
22
26
|
const packageMetadata = readPackageMetadata();
|
|
@@ -39,6 +43,7 @@ function readPackageMetadata() {
|
|
|
39
43
|
}
|
|
40
44
|
export async function runCli(argv, options = {}) {
|
|
41
45
|
await maybeNotifyForUpdates(argv);
|
|
46
|
+
maybeRegisterCurrentRepo(options.cwd ?? process.cwd());
|
|
42
47
|
const program = createProgram();
|
|
43
48
|
const cwd = options.cwd ?? process.cwd();
|
|
44
49
|
const stdout = options.stdout ?? (() => undefined);
|
|
@@ -94,12 +99,19 @@ function defaultNotifyForUpdates(pkg) {
|
|
|
94
99
|
const notifier = updateNotifier({ pkg });
|
|
95
100
|
notifier.notify();
|
|
96
101
|
}
|
|
102
|
+
function maybeRegisterCurrentRepo(cwd) {
|
|
103
|
+
detectRepository(cwd)
|
|
104
|
+
.then(({ repoRoot }) => registerRepo(repoRoot))
|
|
105
|
+
.catch(() => undefined);
|
|
106
|
+
}
|
|
97
107
|
function registerCommands(program) {
|
|
98
108
|
program
|
|
99
109
|
.command('new [branch]')
|
|
100
110
|
.description('create a new branch or detached linked worktree')
|
|
101
111
|
.option('-f, --force', 'remove and recreate the worktree if the target path already exists')
|
|
102
112
|
.option('--detached', 'create a detached worktree without a branch')
|
|
113
|
+
.option('--open', 'open the new worktree in an editor after creation')
|
|
114
|
+
.option('--editor <cli>', 'editor CLI to use with --open (code, cursor, zed, …)')
|
|
103
115
|
.option('--dry-run', 'show what would be created without executing any git commands or writing files')
|
|
104
116
|
.option('--json', 'emit JSON on success or error instead of human-readable output')
|
|
105
117
|
.action(notImplemented('new'));
|
|
@@ -128,8 +140,16 @@ function registerCommands(program) {
|
|
|
128
140
|
.description('show navigation history')
|
|
129
141
|
.option('--json', 'print history as JSON')
|
|
130
142
|
.action(notImplemented('history'));
|
|
143
|
+
program
|
|
144
|
+
.command('open [branch]')
|
|
145
|
+
.description('open the worktree in an editor')
|
|
146
|
+
.option('--editor <cli>', 'editor CLI to use (code, cursor, zed, windsurf, subl, …)')
|
|
147
|
+
.option('--save', 'save the chosen editor to global config')
|
|
148
|
+
.option('--workspace', 'generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)')
|
|
149
|
+
.action(notImplemented('open'));
|
|
131
150
|
program
|
|
132
151
|
.command('go [branch]')
|
|
152
|
+
.alias('jump')
|
|
133
153
|
.description('print or select a worktree path')
|
|
134
154
|
.option('--print', 'print the resolved worktree path explicitly')
|
|
135
155
|
.action(notImplemented('go'));
|
|
@@ -175,6 +195,15 @@ function registerCommands(program) {
|
|
|
175
195
|
.command('trigger-hook <hook>')
|
|
176
196
|
.description('run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree')
|
|
177
197
|
.action(notImplemented('trigger-hook'));
|
|
198
|
+
program
|
|
199
|
+
.command('warp [branch]')
|
|
200
|
+
.description('jump to any worktree across all known repos')
|
|
201
|
+
.option('-n, --new [branch]', 'create a new worktree in a registered repo')
|
|
202
|
+
// --print is the shell-wrapper bypass signal (see SHELL_WRAPPED_COMMANDS in init.ts).
|
|
203
|
+
// The shell omits GJI_WARP_OUTPUT_FILE, so writeShellOutput falls through to stdout.
|
|
204
|
+
.option('--print', 'print the resolved worktree path without changing directory')
|
|
205
|
+
.option('--json', 'emit JSON on success or error instead of human-readable output')
|
|
206
|
+
.action(notImplemented('warp'));
|
|
178
207
|
const configCommand = program
|
|
179
208
|
.command('config')
|
|
180
209
|
.description('manage global config defaults')
|
|
@@ -196,7 +225,7 @@ function attachCommandActions(program, options) {
|
|
|
196
225
|
program.commands
|
|
197
226
|
.find((command) => command.name() === 'new')
|
|
198
227
|
?.action(async (branch, commandOptions) => {
|
|
199
|
-
const exitCode = await runNewCommand({ ...options, branch, detached: commandOptions.detached, dryRun: commandOptions.dryRun, force: commandOptions.force, json: commandOptions.json });
|
|
228
|
+
const exitCode = await runNewCommand({ ...options, branch, detached: commandOptions.detached, dryRun: commandOptions.dryRun, editor: commandOptions.editor, force: commandOptions.force, json: commandOptions.json, open: commandOptions.open });
|
|
200
229
|
if (exitCode !== 0) {
|
|
201
230
|
throw commanderExit(exitCode);
|
|
202
231
|
}
|
|
@@ -266,6 +295,22 @@ function attachCommandActions(program, options) {
|
|
|
266
295
|
throw commanderExit(exitCode);
|
|
267
296
|
}
|
|
268
297
|
});
|
|
298
|
+
program.commands
|
|
299
|
+
.find((command) => command.name() === 'open')
|
|
300
|
+
?.action(async (branch, commandOptions) => {
|
|
301
|
+
const exitCode = await runOpenCommand({
|
|
302
|
+
branch,
|
|
303
|
+
cwd: options.cwd,
|
|
304
|
+
editor: commandOptions.editor,
|
|
305
|
+
save: commandOptions.save,
|
|
306
|
+
stderr: options.stderr,
|
|
307
|
+
stdout: options.stdout,
|
|
308
|
+
workspace: commandOptions.workspace,
|
|
309
|
+
});
|
|
310
|
+
if (exitCode !== 0) {
|
|
311
|
+
throw commanderExit(exitCode);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
269
314
|
program.commands
|
|
270
315
|
.find((command) => command.name() === 'go')
|
|
271
316
|
?.action(async (branch, commandOptions) => {
|
|
@@ -376,6 +421,24 @@ function attachCommandActions(program, options) {
|
|
|
376
421
|
throw commanderExit(exitCode);
|
|
377
422
|
}
|
|
378
423
|
});
|
|
424
|
+
program.commands
|
|
425
|
+
.find((command) => command.name() === 'warp')
|
|
426
|
+
?.action(async (branch, commandOptions) => {
|
|
427
|
+
const newFlag = commandOptions.new;
|
|
428
|
+
const newWorktree = newFlag !== undefined && newFlag !== false;
|
|
429
|
+
const newBranch = typeof newFlag === 'string' ? newFlag : undefined;
|
|
430
|
+
const exitCode = await runWarpCommand({
|
|
431
|
+
branch: newWorktree ? (newBranch ?? branch) : branch,
|
|
432
|
+
cwd: options.cwd,
|
|
433
|
+
json: commandOptions.json,
|
|
434
|
+
newWorktree,
|
|
435
|
+
stderr: options.stderr,
|
|
436
|
+
stdout: options.stdout,
|
|
437
|
+
});
|
|
438
|
+
if (exitCode !== 0) {
|
|
439
|
+
throw commanderExit(exitCode);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
379
442
|
const configCommand = program.commands.find((command) => command.name() === 'config');
|
|
380
443
|
configCommand?.action(async () => {
|
|
381
444
|
const exitCode = await runConfigCommand({
|
package/dist/config.js
CHANGED
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface EditorDefinition {
|
|
2
|
+
cli: string;
|
|
3
|
+
name: string;
|
|
4
|
+
newWindowFlag?: string;
|
|
5
|
+
supportsWorkspace: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare const EDITORS: EditorDefinition[];
|
|
8
|
+
export declare function defaultSpawnEditor(cli: string, args: string[]): Promise<void>;
|
package/dist/editor.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
// Ordered by likely popularity among the target audience.
|
|
3
|
+
export const EDITORS = [
|
|
4
|
+
{ cli: 'cursor', name: 'Cursor', newWindowFlag: '--new-window', supportsWorkspace: true },
|
|
5
|
+
{ cli: 'code', name: 'VS Code', newWindowFlag: '--new-window', supportsWorkspace: true },
|
|
6
|
+
{ cli: 'windsurf', name: 'Windsurf', newWindowFlag: '--new-window', supportsWorkspace: true },
|
|
7
|
+
{ cli: 'zed', name: 'Zed', supportsWorkspace: false },
|
|
8
|
+
{ cli: 'subl', name: 'Sublime Text', newWindowFlag: '--new-window', supportsWorkspace: false },
|
|
9
|
+
];
|
|
10
|
+
export async function defaultSpawnEditor(cli, args) {
|
|
11
|
+
const child = spawn(cli, args, { detached: true, stdio: 'ignore' });
|
|
12
|
+
await new Promise((resolve, reject) => {
|
|
13
|
+
child.once('error', reject);
|
|
14
|
+
child.once('spawn', resolve);
|
|
15
|
+
});
|
|
16
|
+
child.unref();
|
|
17
|
+
}
|