@solaqua/gji 0.7.0 → 0.7.1
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/dist/back.js +1 -1
- package/dist/cli.js +7 -6
- package/dist/gji-bundle.mjs +102 -84
- package/dist/go.js +1 -1
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.js +3 -3
- package/dist/init.d.ts +3 -3
- package/dist/init.js +12 -12
- package/dist/install-prompt.js +22 -21
- package/dist/new.js +2 -2
- package/dist/pr.js +1 -1
- package/dist/remove.js +1 -1
- package/dist/run-hook.d.ts +6 -0
- package/dist/{trigger-hook.js → run-hook.js} +13 -7
- package/dist/shell-completion.js +5 -5
- 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 +1 -1
- package/man/man1/gji-open.1 +1 -1
- 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-run-hook.1 +9 -0
- 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 -4
- package/package.json +1 -1
- package/dist/trigger-hook.d.ts +0 -6
- package/man/man1/gji-trigger-hook.1 +0 -9
package/dist/back.js
CHANGED
|
@@ -39,7 +39,7 @@ export async function runBackCommand(options) {
|
|
|
39
39
|
const repository = await detectRepository(target.path);
|
|
40
40
|
const config = await loadEffectiveConfig(repository.repoRoot, options.home, options.stderr);
|
|
41
41
|
const hooks = extractHooks(config);
|
|
42
|
-
await runHook(hooks
|
|
42
|
+
await runHook(hooks["after-enter"], target.path, {
|
|
43
43
|
branch: target.branch ?? undefined,
|
|
44
44
|
path: target.path,
|
|
45
45
|
repo: basename(repository.repoRoot),
|
package/dist/cli.js
CHANGED
|
@@ -17,10 +17,10 @@ import { runRemoveCommand } from "./remove.js";
|
|
|
17
17
|
import { detectRepository } from "./repo.js";
|
|
18
18
|
import { registerRepo } from "./repo-registry.js";
|
|
19
19
|
import { runRootCommand } from "./root.js";
|
|
20
|
+
import { runHookCommand } from "./run-hook.js";
|
|
20
21
|
import { runStatusCommand } from "./status.js";
|
|
21
22
|
import { runSyncCommand } from "./sync.js";
|
|
22
23
|
import { runSyncFilesCommand } from "./sync-files-command.js";
|
|
23
|
-
import { runTriggerHookCommand } from "./trigger-hook.js";
|
|
24
24
|
import { runWarpCommand } from "./warp.js";
|
|
25
25
|
export function createProgram() {
|
|
26
26
|
const program = new Command();
|
|
@@ -214,9 +214,10 @@ function registerCommands(program) {
|
|
|
214
214
|
.option("--json", "emit JSON on success or error instead of human-readable output")
|
|
215
215
|
.action(notImplemented("remove"));
|
|
216
216
|
program
|
|
217
|
-
.command("
|
|
218
|
-
.
|
|
219
|
-
.
|
|
217
|
+
.command("run-hook <hook>")
|
|
218
|
+
.alias("trigger-hook")
|
|
219
|
+
.description("run a named hook (after-create, after-enter, before-remove) in the current worktree")
|
|
220
|
+
.action(notImplemented("run-hook"));
|
|
220
221
|
program
|
|
221
222
|
.command("warp [branch]")
|
|
222
223
|
.description("jump to any worktree across all known repos")
|
|
@@ -506,9 +507,9 @@ function attachCommandActions(program, options) {
|
|
|
506
507
|
.find((command) => command.name() === "remove")
|
|
507
508
|
?.action(runRemovalCommand);
|
|
508
509
|
program.commands
|
|
509
|
-
.find((command) => command.name() === "
|
|
510
|
+
.find((command) => command.name() === "run-hook")
|
|
510
511
|
?.action(async (hook) => {
|
|
511
|
-
const exitCode = await
|
|
512
|
+
const exitCode = await runHookCommand({
|
|
512
513
|
cwd: options.cwd,
|
|
513
514
|
hook,
|
|
514
515
|
stderr: options.stderr,
|
package/dist/gji-bundle.mjs
CHANGED
|
@@ -13176,9 +13176,13 @@ function extractHooks(config) {
|
|
|
13176
13176
|
}
|
|
13177
13177
|
const hooks = raw;
|
|
13178
13178
|
return {
|
|
13179
|
-
|
|
13180
|
-
|
|
13181
|
-
|
|
13179
|
+
"after-create": parseHookCommand(
|
|
13180
|
+
hooks["after-create"] ?? hooks.afterCreate
|
|
13181
|
+
),
|
|
13182
|
+
"after-enter": parseHookCommand(hooks["after-enter"] ?? hooks.afterEnter),
|
|
13183
|
+
"before-remove": parseHookCommand(
|
|
13184
|
+
hooks["before-remove"] ?? hooks.beforeRemove
|
|
13185
|
+
)
|
|
13182
13186
|
};
|
|
13183
13187
|
}
|
|
13184
13188
|
function parseHookCommand(value) {
|
|
@@ -13450,7 +13454,7 @@ async function runBackCommand(options) {
|
|
|
13450
13454
|
);
|
|
13451
13455
|
const hooks = extractHooks(config);
|
|
13452
13456
|
await runHook(
|
|
13453
|
-
hooks
|
|
13457
|
+
hooks["after-enter"],
|
|
13454
13458
|
target.path,
|
|
13455
13459
|
{
|
|
13456
13460
|
branch: target.branch ?? void 0,
|
|
@@ -14623,14 +14627,14 @@ var TOP_LEVEL_COMMANDS = [
|
|
|
14623
14627
|
},
|
|
14624
14628
|
{ name: "rm", description: "alias of remove" },
|
|
14625
14629
|
{
|
|
14626
|
-
name: "
|
|
14630
|
+
name: "run-hook",
|
|
14627
14631
|
description: "run a named hook in the current worktree"
|
|
14628
14632
|
},
|
|
14629
14633
|
{ name: "warp", description: "jump to any worktree across all known repos" },
|
|
14630
14634
|
{ name: "config", description: "manage global config defaults" }
|
|
14631
14635
|
];
|
|
14632
14636
|
var SHELL_NAMES = ["bash", "fish", "zsh"];
|
|
14633
|
-
var HOOK_NAMES = ["
|
|
14637
|
+
var HOOK_NAMES = ["after-create", "after-enter", "before-remove"];
|
|
14634
14638
|
var CONFIG_KEYS = Array.from(KNOWN_GLOBAL_CONFIG_KEYS);
|
|
14635
14639
|
function renderShellCompletion(shell) {
|
|
14636
14640
|
switch (shell) {
|
|
@@ -14714,7 +14718,7 @@ _gji_completion() {
|
|
|
14714
14718
|
remove|rm)
|
|
14715
14719
|
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) -f --force --dry-run --json --help" -- "$cur") )
|
|
14716
14720
|
;;
|
|
14717
|
-
|
|
14721
|
+
run-hook)
|
|
14718
14722
|
COMPREPLY=( $(compgen -W "${hooks} --help" -- "$cur") )
|
|
14719
14723
|
;;
|
|
14720
14724
|
warp)
|
|
@@ -14752,7 +14756,7 @@ function renderFishCompletion() {
|
|
|
14752
14756
|
(shell) => `complete -c gji -n '__fish_seen_subcommand_from init' -a '${shell}' -d 'shell'`
|
|
14753
14757
|
).join("\n");
|
|
14754
14758
|
const hookLines = HOOK_NAMES.map(
|
|
14755
|
-
(hook) => `complete -c gji -n '__fish_seen_subcommand_from
|
|
14759
|
+
(hook) => `complete -c gji -n '__fish_seen_subcommand_from run-hook' -a '${hook}' -d 'hook'`
|
|
14756
14760
|
).join("\n");
|
|
14757
14761
|
const configKeyLines = CONFIG_KEYS.map(
|
|
14758
14762
|
(key) => `complete -c gji -n '__gji_should_complete_config_key' -a '${key}' -d 'config key'`
|
|
@@ -14917,7 +14921,7 @@ case "\${words[2]}" in
|
|
|
14917
14921
|
remove|rm)
|
|
14918
14922
|
_arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch]' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch:->worktrees'
|
|
14919
14923
|
;;
|
|
14920
|
-
|
|
14924
|
+
run-hook)
|
|
14921
14925
|
_arguments "2:hook:(${hooks})"
|
|
14922
14926
|
;;
|
|
14923
14927
|
warp)
|
|
@@ -15303,8 +15307,7 @@ async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dep
|
|
|
15303
15307
|
if (isHeadless() || nonInteractive) {
|
|
15304
15308
|
return;
|
|
15305
15309
|
}
|
|
15306
|
-
|
|
15307
|
-
if (isConfiguredHookCommand(hooks?.afterCreate)) {
|
|
15310
|
+
if (extractHooks(config)["after-create"]) {
|
|
15308
15311
|
return;
|
|
15309
15312
|
}
|
|
15310
15313
|
if (config.skipInstallPrompt === true) {
|
|
@@ -15337,17 +15340,28 @@ async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dep
|
|
|
15337
15340
|
if (choice === "always") {
|
|
15338
15341
|
try {
|
|
15339
15342
|
if (saveGlobal) {
|
|
15340
|
-
const
|
|
15343
|
+
const existingRaw = await loadExistingGlobalRepoHooks(repoRoot);
|
|
15344
|
+
const existing = extractHooks({ hooks: existingRaw });
|
|
15341
15345
|
await writeGlobalKey(repoRoot, "hooks", {
|
|
15342
|
-
...
|
|
15343
|
-
|
|
15346
|
+
...existing["after-enter"] !== void 0 && {
|
|
15347
|
+
"after-enter": existing["after-enter"]
|
|
15348
|
+
},
|
|
15349
|
+
...existing["before-remove"] !== void 0 && {
|
|
15350
|
+
"before-remove": existing["before-remove"]
|
|
15351
|
+
},
|
|
15352
|
+
"after-create": pm.installCommand
|
|
15344
15353
|
});
|
|
15345
15354
|
} else {
|
|
15346
15355
|
const { config: localConfig } = await loadConfig(repoRoot);
|
|
15347
|
-
const
|
|
15356
|
+
const existing = extractHooks(localConfig);
|
|
15348
15357
|
await writeKey(repoRoot, "hooks", {
|
|
15349
|
-
...
|
|
15350
|
-
|
|
15358
|
+
...existing["after-enter"] !== void 0 && {
|
|
15359
|
+
"after-enter": existing["after-enter"]
|
|
15360
|
+
},
|
|
15361
|
+
...existing["before-remove"] !== void 0 && {
|
|
15362
|
+
"before-remove": existing["before-remove"]
|
|
15363
|
+
},
|
|
15364
|
+
"after-create": pm.installCommand
|
|
15351
15365
|
});
|
|
15352
15366
|
}
|
|
15353
15367
|
} catch (error) {
|
|
@@ -15412,7 +15426,7 @@ async function defaultPromptForInstallChoice(pm) {
|
|
|
15412
15426
|
options: [
|
|
15413
15427
|
{ value: "yes", label: "Yes", hint: "run once" },
|
|
15414
15428
|
{ value: "no", label: "No", hint: "skip this time" },
|
|
15415
|
-
{ value: "always", label: "Always", hint: "save as
|
|
15429
|
+
{ value: "always", label: "Always", hint: "save as after-create hook" },
|
|
15416
15430
|
{
|
|
15417
15431
|
value: "never",
|
|
15418
15432
|
label: "Never",
|
|
@@ -15428,10 +15442,6 @@ async function defaultPromptForInstallChoice(pm) {
|
|
|
15428
15442
|
function isPlainObject2(value) {
|
|
15429
15443
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15430
15444
|
}
|
|
15431
|
-
function isConfiguredHookCommand(value) {
|
|
15432
|
-
if (typeof value === "string") return value.length > 0;
|
|
15433
|
-
return Array.isArray(value) && value.length > 0 && value[0] !== "" && value.every((item) => typeof item === "string");
|
|
15434
|
-
}
|
|
15435
15445
|
|
|
15436
15446
|
// src/new.ts
|
|
15437
15447
|
var execFileAsync3 = promisify4(execFile3);
|
|
@@ -15549,7 +15559,7 @@ function createNewCommand(dependencies = {}) {
|
|
|
15549
15559
|
`
|
|
15550
15560
|
);
|
|
15551
15561
|
options.stderr(
|
|
15552
|
-
`Hint: Use 'gji
|
|
15562
|
+
`Hint: Use 'gji run-hook after-create' inside the worktree to re-run setup hooks
|
|
15553
15563
|
`
|
|
15554
15564
|
);
|
|
15555
15565
|
}
|
|
@@ -15610,7 +15620,7 @@ function createNewCommand(dependencies = {}) {
|
|
|
15610
15620
|
);
|
|
15611
15621
|
const hooks = extractHooks(config);
|
|
15612
15622
|
await runHook(
|
|
15613
|
-
hooks
|
|
15623
|
+
hooks["after-create"],
|
|
15614
15624
|
worktreePath,
|
|
15615
15625
|
{
|
|
15616
15626
|
branch: worktreeName,
|
|
@@ -16116,7 +16126,7 @@ function createGoCommand(dependencies = {}) {
|
|
|
16116
16126
|
);
|
|
16117
16127
|
const hooks = extractHooks(config);
|
|
16118
16128
|
await runHook(
|
|
16119
|
-
hooks
|
|
16129
|
+
hooks["after-enter"],
|
|
16120
16130
|
resolvedPath,
|
|
16121
16131
|
{
|
|
16122
16132
|
branch: chosenWorktree?.branch ?? void 0,
|
|
@@ -16329,10 +16339,12 @@ async function saveWizardConfig(result, cwd, home) {
|
|
|
16329
16339
|
if (result.branchPrefix) values.branchPrefix = result.branchPrefix;
|
|
16330
16340
|
if (result.worktreePath) values.worktreePath = result.worktreePath;
|
|
16331
16341
|
const hooks = {};
|
|
16332
|
-
if (result.hooks?.
|
|
16333
|
-
|
|
16334
|
-
if (result.hooks?.
|
|
16335
|
-
hooks
|
|
16342
|
+
if (result.hooks?.["after-create"])
|
|
16343
|
+
hooks["after-create"] = result.hooks["after-create"];
|
|
16344
|
+
if (result.hooks?.["after-enter"])
|
|
16345
|
+
hooks["after-enter"] = result.hooks["after-enter"];
|
|
16346
|
+
if (result.hooks?.["before-remove"])
|
|
16347
|
+
hooks["before-remove"] = result.hooks["before-remove"];
|
|
16336
16348
|
if (Object.keys(hooks).length > 0) values.hooks = hooks;
|
|
16337
16349
|
if (Object.keys(values).length === 0) return;
|
|
16338
16350
|
if (result.installSaveTarget === "local") {
|
|
@@ -16463,7 +16475,7 @@ async function defaultPromptForSetup() {
|
|
|
16463
16475
|
return null;
|
|
16464
16476
|
}
|
|
16465
16477
|
const afterCreate = await he({
|
|
16466
|
-
message: "
|
|
16478
|
+
message: "after-create hook \u2014 run after creating a worktree?",
|
|
16467
16479
|
placeholder: "e.g. pnpm install \u2014 leave blank to skip"
|
|
16468
16480
|
});
|
|
16469
16481
|
if (pD(afterCreate)) {
|
|
@@ -16471,7 +16483,7 @@ async function defaultPromptForSetup() {
|
|
|
16471
16483
|
return null;
|
|
16472
16484
|
}
|
|
16473
16485
|
const afterEnter = await he({
|
|
16474
|
-
message: "
|
|
16486
|
+
message: "after-enter hook \u2014 run after entering a worktree?",
|
|
16475
16487
|
placeholder: "e.g. nvm use \u2014 leave blank to skip"
|
|
16476
16488
|
});
|
|
16477
16489
|
if (pD(afterEnter)) {
|
|
@@ -16479,7 +16491,7 @@ async function defaultPromptForSetup() {
|
|
|
16479
16491
|
return null;
|
|
16480
16492
|
}
|
|
16481
16493
|
const beforeRemove = await he({
|
|
16482
|
-
message: "
|
|
16494
|
+
message: "before-remove hook \u2014 run before removing a worktree?",
|
|
16483
16495
|
placeholder: "leave blank to skip"
|
|
16484
16496
|
});
|
|
16485
16497
|
if (pD(beforeRemove)) {
|
|
@@ -16488,9 +16500,9 @@ async function defaultPromptForSetup() {
|
|
|
16488
16500
|
}
|
|
16489
16501
|
Se("Setup complete!");
|
|
16490
16502
|
const hooks = {};
|
|
16491
|
-
if (afterCreate) hooks
|
|
16492
|
-
if (afterEnter) hooks
|
|
16493
|
-
if (beforeRemove) hooks
|
|
16503
|
+
if (afterCreate) hooks["after-create"] = afterCreate;
|
|
16504
|
+
if (afterEnter) hooks["after-enter"] = afterEnter;
|
|
16505
|
+
if (beforeRemove) hooks["before-remove"] = beforeRemove;
|
|
16494
16506
|
return {
|
|
16495
16507
|
branchPrefix: branchPrefix || void 0,
|
|
16496
16508
|
hooks: Object.keys(hooks).length > 0 ? hooks : void 0,
|
|
@@ -16899,7 +16911,7 @@ function createPrCommand(dependencies = {}) {
|
|
|
16899
16911
|
);
|
|
16900
16912
|
const hooks = extractHooks(config);
|
|
16901
16913
|
await runHook(
|
|
16902
|
-
hooks
|
|
16914
|
+
hooks["after-create"],
|
|
16903
16915
|
worktreePath,
|
|
16904
16916
|
{
|
|
16905
16917
|
branch: branchName,
|
|
@@ -17062,7 +17074,7 @@ function createRemoveCommand(dependencies = {}) {
|
|
|
17062
17074
|
);
|
|
17063
17075
|
const hooks = extractHooks(config);
|
|
17064
17076
|
await runHook(
|
|
17065
|
-
hooks
|
|
17077
|
+
hooks["before-remove"],
|
|
17066
17078
|
worktree.path,
|
|
17067
17079
|
{
|
|
17068
17080
|
branch: worktree.branch ?? void 0,
|
|
@@ -17180,6 +17192,54 @@ async function runRootCommand(options) {
|
|
|
17180
17192
|
return 0;
|
|
17181
17193
|
}
|
|
17182
17194
|
|
|
17195
|
+
// src/run-hook.ts
|
|
17196
|
+
var VALID_HOOKS = [
|
|
17197
|
+
"after-create",
|
|
17198
|
+
"after-enter",
|
|
17199
|
+
"before-remove"
|
|
17200
|
+
];
|
|
17201
|
+
var CAMEL_ALIASES = {
|
|
17202
|
+
afterCreate: "after-create",
|
|
17203
|
+
afterEnter: "after-enter",
|
|
17204
|
+
beforeRemove: "before-remove"
|
|
17205
|
+
};
|
|
17206
|
+
function isValidHook(hook) {
|
|
17207
|
+
return VALID_HOOKS.includes(hook);
|
|
17208
|
+
}
|
|
17209
|
+
async function runHookCommand(options) {
|
|
17210
|
+
const normalized = CAMEL_ALIASES[options.hook] ?? options.hook;
|
|
17211
|
+
if (!isValidHook(normalized)) {
|
|
17212
|
+
options.stderr(
|
|
17213
|
+
`gji run-hook: unknown hook '${options.hook}'. Valid hooks: ${VALID_HOOKS.join(", ")}
|
|
17214
|
+
`
|
|
17215
|
+
);
|
|
17216
|
+
return 1;
|
|
17217
|
+
}
|
|
17218
|
+
const hookName = normalized;
|
|
17219
|
+
const repository = await detectRepository(options.cwd);
|
|
17220
|
+
const config = await loadEffectiveConfig(
|
|
17221
|
+
repository.repoRoot,
|
|
17222
|
+
void 0,
|
|
17223
|
+
options.stderr
|
|
17224
|
+
);
|
|
17225
|
+
const hooks = extractHooks(config);
|
|
17226
|
+
const worktrees = await listWorktrees(options.cwd);
|
|
17227
|
+
const currentWorktree = worktrees.find(
|
|
17228
|
+
(w2) => w2.path === repository.currentRoot
|
|
17229
|
+
);
|
|
17230
|
+
await runHook(
|
|
17231
|
+
hooks[hookName],
|
|
17232
|
+
repository.currentRoot,
|
|
17233
|
+
{
|
|
17234
|
+
branch: currentWorktree?.branch ?? void 0,
|
|
17235
|
+
path: repository.currentRoot,
|
|
17236
|
+
repo: repository.repoName
|
|
17237
|
+
},
|
|
17238
|
+
options.stderr
|
|
17239
|
+
);
|
|
17240
|
+
return 0;
|
|
17241
|
+
}
|
|
17242
|
+
|
|
17183
17243
|
// src/status.ts
|
|
17184
17244
|
async function runStatusCommand(options) {
|
|
17185
17245
|
const repository = await detectRepository(options.cwd);
|
|
@@ -17552,48 +17612,6 @@ function expandTilde2(value, home) {
|
|
|
17552
17612
|
return value;
|
|
17553
17613
|
}
|
|
17554
17614
|
|
|
17555
|
-
// src/trigger-hook.ts
|
|
17556
|
-
var VALID_HOOKS = [
|
|
17557
|
-
"afterCreate",
|
|
17558
|
-
"afterEnter",
|
|
17559
|
-
"beforeRemove"
|
|
17560
|
-
];
|
|
17561
|
-
function isValidHook(hook) {
|
|
17562
|
-
return VALID_HOOKS.includes(hook);
|
|
17563
|
-
}
|
|
17564
|
-
async function runTriggerHookCommand(options) {
|
|
17565
|
-
if (!isValidHook(options.hook)) {
|
|
17566
|
-
options.stderr(
|
|
17567
|
-
`gji trigger-hook: unknown hook '${options.hook}'. Valid hooks: ${VALID_HOOKS.join(", ")}
|
|
17568
|
-
`
|
|
17569
|
-
);
|
|
17570
|
-
return 1;
|
|
17571
|
-
}
|
|
17572
|
-
const hookName = options.hook;
|
|
17573
|
-
const repository = await detectRepository(options.cwd);
|
|
17574
|
-
const config = await loadEffectiveConfig(
|
|
17575
|
-
repository.repoRoot,
|
|
17576
|
-
void 0,
|
|
17577
|
-
options.stderr
|
|
17578
|
-
);
|
|
17579
|
-
const hooks = extractHooks(config);
|
|
17580
|
-
const worktrees = await listWorktrees(options.cwd);
|
|
17581
|
-
const currentWorktree = worktrees.find(
|
|
17582
|
-
(w2) => w2.path === repository.currentRoot
|
|
17583
|
-
);
|
|
17584
|
-
await runHook(
|
|
17585
|
-
hooks[hookName],
|
|
17586
|
-
repository.currentRoot,
|
|
17587
|
-
{
|
|
17588
|
-
branch: currentWorktree?.branch ?? void 0,
|
|
17589
|
-
path: repository.currentRoot,
|
|
17590
|
-
repo: repository.repoName
|
|
17591
|
-
},
|
|
17592
|
-
options.stderr
|
|
17593
|
-
);
|
|
17594
|
-
return 0;
|
|
17595
|
-
}
|
|
17596
|
-
|
|
17597
17615
|
// src/cli.ts
|
|
17598
17616
|
function createProgram() {
|
|
17599
17617
|
const program2 = new Command();
|
|
@@ -17724,9 +17742,9 @@ function registerCommands(program2) {
|
|
|
17724
17742
|
"--json",
|
|
17725
17743
|
"emit JSON on success or error instead of human-readable output"
|
|
17726
17744
|
).action(notImplemented("remove"));
|
|
17727
|
-
program2.command("
|
|
17728
|
-
"run a named hook (
|
|
17729
|
-
).action(notImplemented("
|
|
17745
|
+
program2.command("run-hook <hook>").alias("trigger-hook").description(
|
|
17746
|
+
"run a named hook (after-create, after-enter, before-remove) in the current worktree"
|
|
17747
|
+
).action(notImplemented("run-hook"));
|
|
17730
17748
|
program2.command("warp [branch]").description("jump to any worktree across all known repos").option("-n, --new [branch]", "create a new worktree in a registered repo").option(
|
|
17731
17749
|
"--print",
|
|
17732
17750
|
"print the resolved worktree path without changing directory"
|
|
@@ -17984,8 +18002,8 @@ function attachCommandActions(program2, options) {
|
|
|
17984
18002
|
}
|
|
17985
18003
|
};
|
|
17986
18004
|
program2.commands.find((command) => command.name() === "remove")?.action(runRemovalCommand);
|
|
17987
|
-
program2.commands.find((command) => command.name() === "
|
|
17988
|
-
const exitCode = await
|
|
18005
|
+
program2.commands.find((command) => command.name() === "run-hook")?.action(async (hook) => {
|
|
18006
|
+
const exitCode = await runHookCommand({
|
|
17989
18007
|
cwd: options.cwd,
|
|
17990
18008
|
hook,
|
|
17991
18009
|
stderr: options.stderr
|
package/dist/go.js
CHANGED
|
@@ -59,7 +59,7 @@ export function createGoCommand(dependencies = {}) {
|
|
|
59
59
|
const chosenWorktree = worktrees.find((w) => w.path === resolvedPath);
|
|
60
60
|
const config = await loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
|
|
61
61
|
const hooks = extractHooks(config);
|
|
62
|
-
await runHook(hooks
|
|
62
|
+
await runHook(hooks["after-enter"], resolvedPath, {
|
|
63
63
|
branch: chosenWorktree?.branch ?? undefined,
|
|
64
64
|
path: resolvedPath,
|
|
65
65
|
repo: basename(repository.repoRoot),
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export type GjiHookCommand = string | string[];
|
|
2
2
|
export interface GjiHooks {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
"after-create"?: GjiHookCommand;
|
|
4
|
+
"after-enter"?: GjiHookCommand;
|
|
5
|
+
"before-remove"?: GjiHookCommand;
|
|
6
6
|
}
|
|
7
7
|
export interface HookContext {
|
|
8
8
|
branch?: string;
|
package/dist/hooks.js
CHANGED
|
@@ -84,9 +84,9 @@ export function extractHooks(config) {
|
|
|
84
84
|
}
|
|
85
85
|
const hooks = raw;
|
|
86
86
|
return {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
"after-create": parseHookCommand(hooks["after-create"] ?? hooks.afterCreate),
|
|
88
|
+
"after-enter": parseHookCommand(hooks["after-enter"] ?? hooks.afterEnter),
|
|
89
|
+
"before-remove": parseHookCommand(hooks["before-remove"] ?? hooks.beforeRemove),
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
function parseHookCommand(value) {
|
package/dist/init.d.ts
CHANGED
|
@@ -3,9 +3,9 @@ export type InstallSaveTarget = "local" | "global";
|
|
|
3
3
|
export interface SetupWizardResult {
|
|
4
4
|
branchPrefix?: string;
|
|
5
5
|
hooks?: {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
"after-create"?: string;
|
|
7
|
+
"after-enter"?: string;
|
|
8
|
+
"before-remove"?: string;
|
|
9
9
|
};
|
|
10
10
|
installSaveTarget: InstallSaveTarget;
|
|
11
11
|
worktreePath?: string;
|
package/dist/init.js
CHANGED
|
@@ -135,12 +135,12 @@ async function saveWizardConfig(result, cwd, home) {
|
|
|
135
135
|
if (result.worktreePath)
|
|
136
136
|
values.worktreePath = result.worktreePath;
|
|
137
137
|
const hooks = {};
|
|
138
|
-
if (result.hooks?.
|
|
139
|
-
hooks
|
|
140
|
-
if (result.hooks?.
|
|
141
|
-
hooks
|
|
142
|
-
if (result.hooks?.
|
|
143
|
-
hooks
|
|
138
|
+
if (result.hooks?.["after-create"])
|
|
139
|
+
hooks["after-create"] = result.hooks["after-create"];
|
|
140
|
+
if (result.hooks?.["after-enter"])
|
|
141
|
+
hooks["after-enter"] = result.hooks["after-enter"];
|
|
142
|
+
if (result.hooks?.["before-remove"])
|
|
143
|
+
hooks["before-remove"] = result.hooks["before-remove"];
|
|
144
144
|
if (Object.keys(hooks).length > 0)
|
|
145
145
|
values.hooks = hooks;
|
|
146
146
|
if (Object.keys(values).length === 0)
|
|
@@ -283,7 +283,7 @@ async function defaultPromptForSetup() {
|
|
|
283
283
|
return null;
|
|
284
284
|
}
|
|
285
285
|
const afterCreate = await text({
|
|
286
|
-
message: "
|
|
286
|
+
message: "after-create hook — run after creating a worktree?",
|
|
287
287
|
placeholder: "e.g. pnpm install — leave blank to skip",
|
|
288
288
|
});
|
|
289
289
|
if (isCancel(afterCreate)) {
|
|
@@ -291,7 +291,7 @@ async function defaultPromptForSetup() {
|
|
|
291
291
|
return null;
|
|
292
292
|
}
|
|
293
293
|
const afterEnter = await text({
|
|
294
|
-
message: "
|
|
294
|
+
message: "after-enter hook — run after entering a worktree?",
|
|
295
295
|
placeholder: "e.g. nvm use — leave blank to skip",
|
|
296
296
|
});
|
|
297
297
|
if (isCancel(afterEnter)) {
|
|
@@ -299,7 +299,7 @@ async function defaultPromptForSetup() {
|
|
|
299
299
|
return null;
|
|
300
300
|
}
|
|
301
301
|
const beforeRemove = await text({
|
|
302
|
-
message: "
|
|
302
|
+
message: "before-remove hook — run before removing a worktree?",
|
|
303
303
|
placeholder: "leave blank to skip",
|
|
304
304
|
});
|
|
305
305
|
if (isCancel(beforeRemove)) {
|
|
@@ -309,11 +309,11 @@ async function defaultPromptForSetup() {
|
|
|
309
309
|
outro("Setup complete!");
|
|
310
310
|
const hooks = {};
|
|
311
311
|
if (afterCreate)
|
|
312
|
-
hooks
|
|
312
|
+
hooks["after-create"] = afterCreate;
|
|
313
313
|
if (afterEnter)
|
|
314
|
-
hooks
|
|
314
|
+
hooks["after-enter"] = afterEnter;
|
|
315
315
|
if (beforeRemove)
|
|
316
|
-
hooks
|
|
316
|
+
hooks["before-remove"] = beforeRemove;
|
|
317
317
|
return {
|
|
318
318
|
branchPrefix: branchPrefix || undefined,
|
|
319
319
|
hooks: Object.keys(hooks).length > 0 ? hooks : undefined,
|
package/dist/install-prompt.js
CHANGED
|
@@ -2,15 +2,15 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { isCancel, select } from "@clack/prompts";
|
|
3
3
|
import { loadConfig, loadGlobalConfig, updateGlobalRepoConfigKey, updateLocalConfigKey, } from "./config.js";
|
|
4
4
|
import { isHeadless } from "./headless.js";
|
|
5
|
+
import { extractHooks } from "./hooks.js";
|
|
5
6
|
import { detectPackageManager, } from "./package-manager.js";
|
|
6
7
|
export async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dependencies = {}, nonInteractive = false) {
|
|
7
8
|
// Skip in non-interactive mode — no prompt can be shown.
|
|
8
9
|
if (isHeadless() || nonInteractive) {
|
|
9
10
|
return;
|
|
10
11
|
}
|
|
11
|
-
// Skip if
|
|
12
|
-
|
|
13
|
-
if (isConfiguredHookCommand(hooks?.afterCreate)) {
|
|
12
|
+
// Skip if an after-create hook is already configured (accepts both kebab and legacy camelCase).
|
|
13
|
+
if (extractHooks(config)["after-create"]) {
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
// Skip if user has permanently opted out of install prompts.
|
|
@@ -43,21 +43,30 @@ export async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stde
|
|
|
43
43
|
try {
|
|
44
44
|
if (saveGlobal) {
|
|
45
45
|
// Deep-merge with any existing per-repo global hooks so other keys are preserved.
|
|
46
|
-
const
|
|
46
|
+
const existingRaw = await loadExistingGlobalRepoHooks(repoRoot);
|
|
47
|
+
const existing = extractHooks({ hooks: existingRaw });
|
|
47
48
|
await writeGlobalKey(repoRoot, "hooks", {
|
|
48
|
-
...
|
|
49
|
-
|
|
49
|
+
...(existing["after-enter"] !== undefined && {
|
|
50
|
+
"after-enter": existing["after-enter"],
|
|
51
|
+
}),
|
|
52
|
+
...(existing["before-remove"] !== undefined && {
|
|
53
|
+
"before-remove": existing["before-remove"],
|
|
54
|
+
}),
|
|
55
|
+
"after-create": pm.installCommand,
|
|
50
56
|
});
|
|
51
57
|
}
|
|
52
58
|
else {
|
|
53
|
-
// Read local config hooks to deep-merge so other hook keys (e.g.
|
|
59
|
+
// Read local config hooks to deep-merge so other hook keys (e.g. after-enter) are preserved.
|
|
54
60
|
const { config: localConfig } = await loadConfig(repoRoot);
|
|
55
|
-
const
|
|
56
|
-
? localConfig.hooks
|
|
57
|
-
: {};
|
|
61
|
+
const existing = extractHooks(localConfig);
|
|
58
62
|
await writeKey(repoRoot, "hooks", {
|
|
59
|
-
...
|
|
60
|
-
|
|
63
|
+
...(existing["after-enter"] !== undefined && {
|
|
64
|
+
"after-enter": existing["after-enter"],
|
|
65
|
+
}),
|
|
66
|
+
...(existing["before-remove"] !== undefined && {
|
|
67
|
+
"before-remove": existing["before-remove"],
|
|
68
|
+
}),
|
|
69
|
+
"after-create": pm.installCommand,
|
|
61
70
|
});
|
|
62
71
|
}
|
|
63
72
|
}
|
|
@@ -122,7 +131,7 @@ async function defaultPromptForInstallChoice(pm) {
|
|
|
122
131
|
options: [
|
|
123
132
|
{ value: "yes", label: "Yes", hint: "run once" },
|
|
124
133
|
{ value: "no", label: "No", hint: "skip this time" },
|
|
125
|
-
{ value: "always", label: "Always", hint: "save as
|
|
134
|
+
{ value: "always", label: "Always", hint: "save as after-create hook" },
|
|
126
135
|
{
|
|
127
136
|
value: "never",
|
|
128
137
|
label: "Never",
|
|
@@ -138,11 +147,3 @@ async function defaultPromptForInstallChoice(pm) {
|
|
|
138
147
|
function isPlainObject(value) {
|
|
139
148
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
140
149
|
}
|
|
141
|
-
function isConfiguredHookCommand(value) {
|
|
142
|
-
if (typeof value === "string")
|
|
143
|
-
return value.length > 0;
|
|
144
|
-
return (Array.isArray(value) &&
|
|
145
|
-
value.length > 0 &&
|
|
146
|
-
value[0] !== "" &&
|
|
147
|
-
value.every((item) => typeof item === "string"));
|
|
148
|
-
}
|
package/dist/new.js
CHANGED
|
@@ -110,7 +110,7 @@ export function createNewCommand(dependencies = {}) {
|
|
|
110
110
|
else {
|
|
111
111
|
options.stderr(`gji new: ${message} in non-interactive mode (GJI_NO_TUI=1)\n`);
|
|
112
112
|
options.stderr(`Hint: Use 'gji remove ${worktreeName}' or 'gji clean' to remove the existing worktree\n`);
|
|
113
|
-
options.stderr(`Hint: Use 'gji
|
|
113
|
+
options.stderr(`Hint: Use 'gji run-hook after-create' inside the worktree to re-run setup hooks\n`);
|
|
114
114
|
}
|
|
115
115
|
return 1;
|
|
116
116
|
}
|
|
@@ -161,7 +161,7 @@ export function createNewCommand(dependencies = {}) {
|
|
|
161
161
|
}
|
|
162
162
|
await maybeRunInstallPrompt(worktreePath, repository.repoRoot, config, options.stderr, dependencies, !!options.json);
|
|
163
163
|
const hooks = extractHooks(config);
|
|
164
|
-
await runHook(hooks
|
|
164
|
+
await runHook(hooks["after-create"], worktreePath, {
|
|
165
165
|
branch: worktreeName,
|
|
166
166
|
path: worktreePath,
|
|
167
167
|
repo: basename(repository.repoRoot),
|
package/dist/pr.js
CHANGED
|
@@ -111,7 +111,7 @@ export function createPrCommand(dependencies = {}) {
|
|
|
111
111
|
}
|
|
112
112
|
await maybeRunInstallPrompt(worktreePath, repository.repoRoot, config, options.stderr, dependencies, !!options.json);
|
|
113
113
|
const hooks = extractHooks(config);
|
|
114
|
-
await runHook(hooks
|
|
114
|
+
await runHook(hooks["after-create"], worktreePath, {
|
|
115
115
|
branch: branchName,
|
|
116
116
|
path: worktreePath,
|
|
117
117
|
repo: basename(repository.repoRoot),
|
package/dist/remove.js
CHANGED
|
@@ -71,7 +71,7 @@ export function createRemoveCommand(dependencies = {}) {
|
|
|
71
71
|
}
|
|
72
72
|
const config = await loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
|
|
73
73
|
const hooks = extractHooks(config);
|
|
74
|
-
await runHook(hooks
|
|
74
|
+
await runHook(hooks["before-remove"], worktree.path, {
|
|
75
75
|
branch: worktree.branch ?? undefined,
|
|
76
76
|
path: worktree.path,
|
|
77
77
|
repo: basename(repository.repoRoot),
|
|
@@ -2,19 +2,25 @@ import { loadEffectiveConfig } from "./config.js";
|
|
|
2
2
|
import { extractHooks, runHook } from "./hooks.js";
|
|
3
3
|
import { detectRepository, listWorktrees } from "./repo.js";
|
|
4
4
|
const VALID_HOOKS = [
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
5
|
+
"after-create",
|
|
6
|
+
"after-enter",
|
|
7
|
+
"before-remove",
|
|
8
8
|
];
|
|
9
|
+
const CAMEL_ALIASES = {
|
|
10
|
+
afterCreate: "after-create",
|
|
11
|
+
afterEnter: "after-enter",
|
|
12
|
+
beforeRemove: "before-remove",
|
|
13
|
+
};
|
|
9
14
|
function isValidHook(hook) {
|
|
10
15
|
return VALID_HOOKS.includes(hook);
|
|
11
16
|
}
|
|
12
|
-
export async function
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
export async function runHookCommand(options) {
|
|
18
|
+
const normalized = CAMEL_ALIASES[options.hook] ?? options.hook;
|
|
19
|
+
if (!isValidHook(normalized)) {
|
|
20
|
+
options.stderr(`gji run-hook: unknown hook '${options.hook}'. Valid hooks: ${VALID_HOOKS.join(", ")}\n`);
|
|
15
21
|
return 1;
|
|
16
22
|
}
|
|
17
|
-
const hookName =
|
|
23
|
+
const hookName = normalized;
|
|
18
24
|
const repository = await detectRepository(options.cwd);
|
|
19
25
|
const config = await loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
|
|
20
26
|
const hooks = extractHooks(config);
|
package/dist/shell-completion.js
CHANGED
|
@@ -27,14 +27,14 @@ const TOP_LEVEL_COMMANDS = [
|
|
|
27
27
|
},
|
|
28
28
|
{ name: "rm", description: "alias of remove" },
|
|
29
29
|
{
|
|
30
|
-
name: "
|
|
30
|
+
name: "run-hook",
|
|
31
31
|
description: "run a named hook in the current worktree",
|
|
32
32
|
},
|
|
33
33
|
{ name: "warp", description: "jump to any worktree across all known repos" },
|
|
34
34
|
{ name: "config", description: "manage global config defaults" },
|
|
35
35
|
];
|
|
36
36
|
const SHELL_NAMES = ["bash", "fish", "zsh"];
|
|
37
|
-
const HOOK_NAMES = ["
|
|
37
|
+
const HOOK_NAMES = ["after-create", "after-enter", "before-remove"];
|
|
38
38
|
const CONFIG_KEYS = Array.from(KNOWN_GLOBAL_CONFIG_KEYS);
|
|
39
39
|
export function renderShellCompletion(shell) {
|
|
40
40
|
switch (shell) {
|
|
@@ -116,7 +116,7 @@ _gji_completion() {
|
|
|
116
116
|
remove|rm)
|
|
117
117
|
COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) -f --force --dry-run --json --help" -- "$cur") )
|
|
118
118
|
;;
|
|
119
|
-
|
|
119
|
+
run-hook)
|
|
120
120
|
COMPREPLY=( $(compgen -W "${hooks} --help" -- "$cur") )
|
|
121
121
|
;;
|
|
122
122
|
warp)
|
|
@@ -149,7 +149,7 @@ complete -F _gji_completion gji`;
|
|
|
149
149
|
function renderFishCompletion() {
|
|
150
150
|
const commandLines = TOP_LEVEL_COMMANDS.map((command) => `complete -c gji -n '__fish_use_subcommand' -a '${command.name}' -d '${escapeSingleQuotes(command.description)}'`).join("\n");
|
|
151
151
|
const shellLines = SHELL_NAMES.map((shell) => `complete -c gji -n '__fish_seen_subcommand_from init' -a '${shell}' -d 'shell'`).join("\n");
|
|
152
|
-
const hookLines = HOOK_NAMES.map((hook) => `complete -c gji -n '__fish_seen_subcommand_from
|
|
152
|
+
const hookLines = HOOK_NAMES.map((hook) => `complete -c gji -n '__fish_seen_subcommand_from run-hook' -a '${hook}' -d 'hook'`).join("\n");
|
|
153
153
|
const configKeyLines = CONFIG_KEYS.map((key) => `complete -c gji -n '__gji_should_complete_config_key' -a '${key}' -d 'config key'`).join("\n");
|
|
154
154
|
return `function __gji_worktree_branches
|
|
155
155
|
command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
|
|
@@ -309,7 +309,7 @@ case "\${words[2]}" in
|
|
|
309
309
|
remove|rm)
|
|
310
310
|
_arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch]' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch:->worktrees'
|
|
311
311
|
;;
|
|
312
|
-
|
|
312
|
+
run-hook)
|
|
313
313
|
_arguments "2:hook:(${hooks})"
|
|
314
314
|
;;
|
|
315
315
|
warp)
|
package/man/man1/gji-back.1
CHANGED
package/man/man1/gji-clean.1
CHANGED
package/man/man1/gji-config.1
CHANGED
package/man/man1/gji-go.1
CHANGED
package/man/man1/gji-history.1
CHANGED
package/man/man1/gji-init.1
CHANGED
package/man/man1/gji-ls.1
CHANGED
package/man/man1/gji-new.1
CHANGED
package/man/man1/gji-open.1
CHANGED
package/man/man1/gji-pr.1
CHANGED
package/man/man1/gji-remove.1
CHANGED
package/man/man1/gji-root.1
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
.TH GJI\-RUN\-HOOK 1 "June 2026" "gji 0.7.1" "User Commands"
|
|
2
|
+
.SH NAME
|
|
3
|
+
gji\-run\-hook \- run a named hook (after\-create, after\-enter, before\-remove) in the current worktree
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B gji run\-hook [options] <hook>
|
|
6
|
+
.SH DESCRIPTION
|
|
7
|
+
run a named hook (after\-create, after\-enter, before\-remove) in the current worktree
|
|
8
|
+
.SH "SEE ALSO"
|
|
9
|
+
.BR gji (1)
|
package/man/man1/gji-status.1
CHANGED
package/man/man1/gji-sync.1
CHANGED
package/man/man1/gji-warp.1
CHANGED
package/man/man1/gji.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH GJI 1 "
|
|
1
|
+
.TH GJI 1 "June 2026" "gji 0.7.1" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
gji \- Context switching without the mess.
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -60,8 +60,10 @@ remove a linked worktree and delete its branch when present
|
|
|
60
60
|
.br
|
|
61
61
|
Alias: \fBrm\fR
|
|
62
62
|
.TP
|
|
63
|
-
.B
|
|
64
|
-
run a named hook (
|
|
63
|
+
.B run\-hook [options] <hook>
|
|
64
|
+
run a named hook (after\-create, after\-enter, before\-remove) in the current worktree
|
|
65
|
+
.br
|
|
66
|
+
Alias: \fBtrigger\-hook\fR
|
|
65
67
|
.TP
|
|
66
68
|
.B warp [options] [branch]
|
|
67
69
|
jump to any worktree across all known repos
|
|
@@ -88,6 +90,6 @@ output the version number
|
|
|
88
90
|
.BR gji\-ls (1),
|
|
89
91
|
.BR gji\-clean (1),
|
|
90
92
|
.BR gji\-remove (1),
|
|
91
|
-
.BR gji\-
|
|
93
|
+
.BR gji\-run\-hook (1),
|
|
92
94
|
.BR gji\-warp (1),
|
|
93
95
|
.BR gji\-config (1)
|
package/package.json
CHANGED
package/dist/trigger-hook.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
.TH GJI\-TRIGGER\-HOOK 1 "May 2026" "gji 0.7.0" "User Commands"
|
|
2
|
-
.SH NAME
|
|
3
|
-
gji\-trigger\-hook \- run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree
|
|
4
|
-
.SH SYNOPSIS
|
|
5
|
-
.B gji trigger\-hook [options] <hook>
|
|
6
|
-
.SH DESCRIPTION
|
|
7
|
-
run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree
|
|
8
|
-
.SH "SEE ALSO"
|
|
9
|
-
.BR gji (1)
|