@solaqua/gji 0.6.1 → 0.7.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 +26 -1
- package/dist/back.d.ts +1 -1
- package/dist/back.js +23 -17
- package/dist/clean.d.ts +1 -1
- package/dist/clean.js +44 -35
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +264 -164
- package/dist/completion.js +3 -3
- package/dist/config-command.js +5 -5
- package/dist/config.js +41 -35
- package/dist/conflict.d.ts +1 -1
- package/dist/conflict.js +14 -6
- package/dist/editor.js +29 -9
- package/dist/file-sync.d.ts +1 -0
- package/dist/file-sync.js +15 -11
- package/dist/git.d.ts +1 -1
- package/dist/git.js +21 -19
- package/dist/gji-bundle.mjs +1709 -850
- package/dist/go.d.ts +2 -2
- package/dist/go.js +39 -26
- package/dist/headless.js +1 -1
- package/dist/history-command.js +3 -3
- package/dist/history.js +12 -12
- package/dist/hooks.js +16 -16
- package/dist/index.js +13 -9
- package/dist/init.d.ts +2 -2
- package/dist/init.js +106 -94
- package/dist/install-prompt.d.ts +3 -3
- package/dist/install-prompt.js +46 -28
- package/dist/ls.d.ts +2 -2
- package/dist/ls.js +29 -29
- package/dist/new.d.ts +2 -2
- package/dist/new.js +96 -81
- package/dist/open.d.ts +2 -2
- package/dist/open.js +24 -21
- package/dist/package-manager.js +96 -45
- package/dist/pr.d.ts +2 -2
- package/dist/pr.js +47 -34
- package/dist/remove.d.ts +1 -1
- package/dist/remove.js +39 -27
- package/dist/repo-registry.js +45 -19
- package/dist/repo.js +29 -28
- package/dist/root.js +3 -3
- package/dist/shell-completion.d.ts +1 -1
- package/dist/shell-completion.js +65 -37
- package/dist/shell-handoff.js +2 -2
- package/dist/shell.d.ts +1 -1
- package/dist/shell.js +4 -4
- package/dist/status.d.ts +5 -5
- package/dist/status.js +23 -23
- package/dist/sync-files-command.d.ts +10 -0
- package/dist/sync-files-command.js +137 -0
- package/dist/sync.js +23 -15
- package/dist/trigger-hook.js +9 -5
- package/dist/warp.js +66 -34
- package/dist/worktree-info.d.ts +9 -9
- package/dist/worktree-info.js +31 -29
- package/dist/worktree-management.d.ts +1 -1
- package/dist/worktree-management.js +26 -11
- package/dist/worktree-prompts.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-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +23 -0
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-trigger-hook.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +5 -1
- package/package.json +8 -2
package/dist/go.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type WorktreeHealth } from
|
|
2
|
-
import { type WorktreeEntry } from
|
|
1
|
+
import { type WorktreeHealth } from "./git.js";
|
|
2
|
+
import { type WorktreeEntry } from "./repo.js";
|
|
3
3
|
export interface GoCommandOptions {
|
|
4
4
|
branch?: string;
|
|
5
5
|
cwd: string;
|
package/dist/go.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { basename } from
|
|
2
|
-
import { isCancel, select } from
|
|
3
|
-
import { loadEffectiveConfig } from
|
|
4
|
-
import { readWorktreeHealth } from
|
|
5
|
-
import { isHeadless } from
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { detectRepository, listWorktrees, sortByCurrentFirst } from
|
|
9
|
-
import { writeShellOutput } from
|
|
10
|
-
import { resolveWarpTarget } from
|
|
11
|
-
const GO_OUTPUT_FILE_ENV =
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
import { isCancel, select } from "@clack/prompts";
|
|
3
|
+
import { loadEffectiveConfig } from "./config.js";
|
|
4
|
+
import { readWorktreeHealth } from "./git.js";
|
|
5
|
+
import { isHeadless } from "./headless.js";
|
|
6
|
+
import { appendHistory } from "./history.js";
|
|
7
|
+
import { extractHooks, runHook } from "./hooks.js";
|
|
8
|
+
import { detectRepository, listWorktrees, sortByCurrentFirst, } from "./repo.js";
|
|
9
|
+
import { writeShellOutput } from "./shell-handoff.js";
|
|
10
|
+
import { resolveWarpTarget } from "./warp.js";
|
|
11
|
+
const GO_OUTPUT_FILE_ENV = "GJI_GO_OUTPUT_FILE";
|
|
12
12
|
export function createGoCommand(dependencies = {}) {
|
|
13
13
|
const prompt = dependencies.promptForWorktree ?? promptForWorktree;
|
|
14
14
|
return async function runGoCommand(options) {
|
|
@@ -23,10 +23,13 @@ export function createGoCommand(dependencies = {}) {
|
|
|
23
23
|
catch {
|
|
24
24
|
// Not inside a git repo — fall back to cross-repo navigation.
|
|
25
25
|
if (isHeadless() && !options.branch) {
|
|
26
|
-
options.stderr(
|
|
26
|
+
options.stderr("gji go: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n");
|
|
27
27
|
return 1;
|
|
28
28
|
}
|
|
29
|
-
const target = await resolveWarpTarget({
|
|
29
|
+
const target = await resolveWarpTarget({
|
|
30
|
+
...options,
|
|
31
|
+
commandName: "gji go",
|
|
32
|
+
});
|
|
30
33
|
if (!target)
|
|
31
34
|
return 1;
|
|
32
35
|
appendHistory(target.path, target.branch).catch(() => undefined);
|
|
@@ -34,27 +37,33 @@ export function createGoCommand(dependencies = {}) {
|
|
|
34
37
|
return 0;
|
|
35
38
|
}
|
|
36
39
|
if (!options.branch && isHeadless()) {
|
|
37
|
-
options.stderr(
|
|
40
|
+
options.stderr("gji go: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n");
|
|
38
41
|
return 1;
|
|
39
42
|
}
|
|
40
|
-
const prompted = options.branch
|
|
43
|
+
const prompted = options.branch
|
|
44
|
+
? null
|
|
45
|
+
: await prompt(sortByCurrentFirst(worktrees));
|
|
41
46
|
const resolvedPath = options.branch
|
|
42
47
|
? worktrees.find((entry) => entry.branch === options.branch)?.path
|
|
43
|
-
: prompted ?? undefined;
|
|
48
|
+
: (prompted ?? undefined);
|
|
44
49
|
if (!resolvedPath) {
|
|
45
50
|
if (options.branch) {
|
|
46
51
|
options.stderr(`No worktree found for branch: ${options.branch}\n`);
|
|
47
52
|
options.stderr(`Hint: Use 'gji ls' to see available worktrees\n`);
|
|
48
53
|
}
|
|
49
54
|
else {
|
|
50
|
-
options.stderr(
|
|
55
|
+
options.stderr("Aborted\n");
|
|
51
56
|
}
|
|
52
57
|
return 1;
|
|
53
58
|
}
|
|
54
59
|
const chosenWorktree = worktrees.find((w) => w.path === resolvedPath);
|
|
55
60
|
const config = await loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
|
|
56
61
|
const hooks = extractHooks(config);
|
|
57
|
-
await runHook(hooks.afterEnter, resolvedPath, {
|
|
62
|
+
await runHook(hooks.afterEnter, resolvedPath, {
|
|
63
|
+
branch: chosenWorktree?.branch ?? undefined,
|
|
64
|
+
path: resolvedPath,
|
|
65
|
+
repo: basename(repository.repoRoot),
|
|
66
|
+
}, options.stderr);
|
|
58
67
|
appendHistory(resolvedPath, chosenWorktree?.branch ?? null).catch(() => undefined);
|
|
59
68
|
await writeShellOutput(GO_OUTPUT_FILE_ENV, resolvedPath, options.stdout);
|
|
60
69
|
return 0;
|
|
@@ -64,14 +73,18 @@ export const runGoCommand = createGoCommand();
|
|
|
64
73
|
async function promptForWorktree(worktrees) {
|
|
65
74
|
const healthResults = await Promise.allSettled(worktrees.map((w) => readWorktreeHealth(w.path)));
|
|
66
75
|
const choice = await select({
|
|
67
|
-
message:
|
|
76
|
+
message: "Choose a worktree",
|
|
68
77
|
options: worktrees.map((worktree, i) => {
|
|
69
|
-
const health = healthResults[i].status ===
|
|
70
|
-
const pathHint = worktree.isCurrent
|
|
71
|
-
|
|
78
|
+
const health = healthResults[i].status === "fulfilled" ? healthResults[i].value : null;
|
|
79
|
+
const pathHint = worktree.isCurrent
|
|
80
|
+
? `${worktree.path} (current)`
|
|
81
|
+
: worktree.path;
|
|
82
|
+
const upstream = health
|
|
83
|
+
? formatUpstreamHint(worktree.branch, health)
|
|
84
|
+
: null;
|
|
72
85
|
return {
|
|
73
86
|
value: worktree.path,
|
|
74
|
-
label: worktree.branch ??
|
|
87
|
+
label: worktree.branch ?? "(detached)",
|
|
75
88
|
hint: upstream ? `${upstream} · ${pathHint}` : pathHint,
|
|
76
89
|
};
|
|
77
90
|
}),
|
|
@@ -85,11 +98,11 @@ export function formatUpstreamHint(branch, health) {
|
|
|
85
98
|
if (branch === null)
|
|
86
99
|
return null;
|
|
87
100
|
if (!health.hasUpstream)
|
|
88
|
-
return
|
|
101
|
+
return "no upstream";
|
|
89
102
|
if (health.upstreamGone)
|
|
90
|
-
return
|
|
103
|
+
return "upstream gone";
|
|
91
104
|
if (health.ahead === 0 && health.behind === 0)
|
|
92
|
-
return
|
|
105
|
+
return "up to date";
|
|
93
106
|
if (health.ahead === 0)
|
|
94
107
|
return `behind ${health.behind}`;
|
|
95
108
|
if (health.behind === 0)
|
package/dist/headless.js
CHANGED
package/dist/history-command.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { formatHistoryList } from "./back.js";
|
|
2
|
+
import { loadHistory } from "./history.js";
|
|
3
3
|
export async function runHistoryCommand(options) {
|
|
4
4
|
const history = await loadHistory(options.home);
|
|
5
5
|
if (options.json) {
|
|
@@ -7,7 +7,7 @@ export async function runHistoryCommand(options) {
|
|
|
7
7
|
return 0;
|
|
8
8
|
}
|
|
9
9
|
if (history.length === 0) {
|
|
10
|
-
options.stdout(
|
|
10
|
+
options.stdout("No navigation history.\n");
|
|
11
11
|
return 0;
|
|
12
12
|
}
|
|
13
13
|
options.stdout(formatHistoryList(history, options.cwd));
|
package/dist/history.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from
|
|
2
|
-
import { homedir } from
|
|
3
|
-
import { dirname, join, resolve } from
|
|
4
|
-
import { GLOBAL_CONFIG_DIRECTORY } from
|
|
5
|
-
export const HISTORY_FILE_NAME =
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { GLOBAL_CONFIG_DIRECTORY } from "./config.js";
|
|
5
|
+
export const HISTORY_FILE_NAME = "history.json";
|
|
6
6
|
const MAX_HISTORY_ENTRIES = 50;
|
|
7
7
|
export function HISTORY_FILE_PATH(home = homedir()) {
|
|
8
8
|
const configDir = process.env.GJI_CONFIG_DIR;
|
|
@@ -14,7 +14,7 @@ export function HISTORY_FILE_PATH(home = homedir()) {
|
|
|
14
14
|
export async function loadHistory(home = homedir()) {
|
|
15
15
|
const path = HISTORY_FILE_PATH(home);
|
|
16
16
|
try {
|
|
17
|
-
const raw = await readFile(path,
|
|
17
|
+
const raw = await readFile(path, "utf8");
|
|
18
18
|
const parsed = JSON.parse(raw);
|
|
19
19
|
if (!Array.isArray(parsed))
|
|
20
20
|
return [];
|
|
@@ -34,13 +34,13 @@ export async function appendHistory(path, branch, home = homedir()) {
|
|
|
34
34
|
const entry = { branch, path, timestamp: Date.now() };
|
|
35
35
|
const next = [entry, ...existing].slice(0, MAX_HISTORY_ENTRIES);
|
|
36
36
|
await mkdir(dirname(historyPath), { recursive: true });
|
|
37
|
-
await writeFile(historyPath, `${JSON.stringify(next, null, 2)}\n`,
|
|
37
|
+
await writeFile(historyPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
38
38
|
}
|
|
39
39
|
function isHistoryEntry(value) {
|
|
40
|
-
return (typeof value ===
|
|
40
|
+
return (typeof value === "object" &&
|
|
41
41
|
value !== null &&
|
|
42
|
-
|
|
43
|
-
typeof value.path ===
|
|
44
|
-
|
|
45
|
-
typeof value.timestamp ===
|
|
42
|
+
"path" in value &&
|
|
43
|
+
typeof value.path === "string" &&
|
|
44
|
+
"timestamp" in value &&
|
|
45
|
+
typeof value.timestamp === "number");
|
|
46
46
|
}
|
package/dist/hooks.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn } from
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
export async function runHook(hookCmd, cwd, context, stderr) {
|
|
3
3
|
if (!hookCmd)
|
|
4
4
|
return;
|
|
@@ -11,26 +11,26 @@ export async function runHook(hookCmd, cwd, context, stderr) {
|
|
|
11
11
|
async function runArgvHook(hookCmd, cwd, context, stderr) {
|
|
12
12
|
const [command, ...args] = hookCmd.map((arg) => interpolate(arg, context));
|
|
13
13
|
if (!command) {
|
|
14
|
-
stderr(
|
|
14
|
+
stderr("gji: hook argv command must include a non-empty command\n");
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
await new Promise((resolve) => {
|
|
18
18
|
const child = spawn(command, args, {
|
|
19
19
|
cwd,
|
|
20
20
|
shell: false,
|
|
21
|
-
stdio: [
|
|
21
|
+
stdio: ["ignore", "inherit", "pipe"],
|
|
22
22
|
env: hookEnvironment(context),
|
|
23
23
|
});
|
|
24
|
-
child.stderr.on(
|
|
24
|
+
child.stderr.on("data", (chunk) => {
|
|
25
25
|
stderr(chunk.toString());
|
|
26
26
|
});
|
|
27
|
-
child.on(
|
|
27
|
+
child.on("close", (code) => {
|
|
28
28
|
if (code !== 0) {
|
|
29
29
|
stderr(`gji: hook exited with code ${code}: ${formatArgvHook(command, args)}\n`);
|
|
30
30
|
}
|
|
31
31
|
resolve();
|
|
32
32
|
});
|
|
33
|
-
child.on(
|
|
33
|
+
child.on("error", (err) => {
|
|
34
34
|
stderr(`gji: hook failed to start: ${err.message}\n`);
|
|
35
35
|
resolve();
|
|
36
36
|
});
|
|
@@ -42,19 +42,19 @@ async function runShellHook(hookCmd, cwd, context, stderr) {
|
|
|
42
42
|
const child = spawn(interpolated, {
|
|
43
43
|
cwd,
|
|
44
44
|
shell: true,
|
|
45
|
-
stdio: [
|
|
45
|
+
stdio: ["ignore", "inherit", "pipe"],
|
|
46
46
|
env: hookEnvironment(context),
|
|
47
47
|
});
|
|
48
|
-
child.stderr.on(
|
|
48
|
+
child.stderr.on("data", (chunk) => {
|
|
49
49
|
stderr(chunk.toString());
|
|
50
50
|
});
|
|
51
|
-
child.on(
|
|
51
|
+
child.on("close", (code) => {
|
|
52
52
|
if (code !== 0) {
|
|
53
53
|
stderr(`gji: hook exited with code ${code}: ${interpolated}\n`);
|
|
54
54
|
}
|
|
55
55
|
resolve();
|
|
56
56
|
});
|
|
57
|
-
child.on(
|
|
57
|
+
child.on("error", (err) => {
|
|
58
58
|
stderr(`gji: hook failed to start: ${err.message}\n`);
|
|
59
59
|
resolve();
|
|
60
60
|
});
|
|
@@ -63,7 +63,7 @@ async function runShellHook(hookCmd, cwd, context, stderr) {
|
|
|
63
63
|
function hookEnvironment(context) {
|
|
64
64
|
return {
|
|
65
65
|
...process.env,
|
|
66
|
-
GJI_BRANCH: context.branch ??
|
|
66
|
+
GJI_BRANCH: context.branch ?? "",
|
|
67
67
|
GJI_PATH: context.path,
|
|
68
68
|
GJI_REPO: context.repo,
|
|
69
69
|
};
|
|
@@ -73,13 +73,13 @@ function formatArgvHook(command, args) {
|
|
|
73
73
|
}
|
|
74
74
|
export function interpolate(template, context) {
|
|
75
75
|
return template
|
|
76
|
-
.replace(/\{\{branch\}\}/g, context.branch ??
|
|
76
|
+
.replace(/\{\{branch\}\}/g, context.branch ?? "")
|
|
77
77
|
.replace(/\{\{path\}\}/g, context.path)
|
|
78
78
|
.replace(/\{\{repo\}\}/g, context.repo);
|
|
79
79
|
}
|
|
80
80
|
export function extractHooks(config) {
|
|
81
81
|
const raw = config.hooks;
|
|
82
|
-
if (!raw || typeof raw !==
|
|
82
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
83
83
|
return {};
|
|
84
84
|
}
|
|
85
85
|
const hooks = raw;
|
|
@@ -90,12 +90,12 @@ export function extractHooks(config) {
|
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
function parseHookCommand(value) {
|
|
93
|
-
if (typeof value ===
|
|
93
|
+
if (typeof value === "string")
|
|
94
94
|
return value;
|
|
95
95
|
if (Array.isArray(value) &&
|
|
96
96
|
value.length > 0 &&
|
|
97
|
-
value[0] !==
|
|
98
|
-
value.every((item) => typeof item ===
|
|
97
|
+
value[0] !== "" &&
|
|
98
|
+
value.every((item) => typeof item === "string")) {
|
|
99
99
|
return value;
|
|
100
100
|
}
|
|
101
101
|
return undefined;
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { homedir } from
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { runCli } from "./cli.js";
|
|
4
|
+
import { loadGlobalConfig } from "./config.js";
|
|
5
5
|
async function main() {
|
|
6
6
|
try {
|
|
7
7
|
const argv = process.argv.slice(2);
|
|
8
8
|
// Warn once (until fixed) when shell integration hasn't been set up.
|
|
9
9
|
// Only shown in interactive terminals — suppressed in pipes and after gji init --write.
|
|
10
|
-
const isMetaArg = argv[0] ===
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const isMetaArg = argv[0] === "init" ||
|
|
11
|
+
argv[0] === "--version" ||
|
|
12
|
+
argv[0] === "-V" ||
|
|
13
|
+
argv[0] === "--help" ||
|
|
14
|
+
argv[0] === "-h";
|
|
13
15
|
if (process.stderr.isTTY === true && !isMetaArg) {
|
|
14
16
|
await warnIfMissingShellIntegration();
|
|
15
17
|
}
|
|
@@ -20,7 +22,7 @@ async function main() {
|
|
|
20
22
|
process.exitCode = result.exitCode;
|
|
21
23
|
}
|
|
22
24
|
catch (error) {
|
|
23
|
-
const message = error instanceof Error ? error.message :
|
|
25
|
+
const message = error instanceof Error ? error.message : "An unknown error occurred.";
|
|
24
26
|
process.stderr.write(`${message}\n`);
|
|
25
27
|
process.exitCode = 1;
|
|
26
28
|
}
|
|
@@ -29,8 +31,10 @@ async function warnIfMissingShellIntegration() {
|
|
|
29
31
|
try {
|
|
30
32
|
const { config } = await loadGlobalConfig(homedir());
|
|
31
33
|
if (!config.shellIntegration) {
|
|
32
|
-
const shellBin = (process.env.SHELL ??
|
|
33
|
-
const shellArg = shellBin && [
|
|
34
|
+
const shellBin = (process.env.SHELL ?? "").split("/").at(-1);
|
|
35
|
+
const shellArg = shellBin && ["bash", "zsh", "fish"].includes(shellBin)
|
|
36
|
+
? ` ${shellBin}`
|
|
37
|
+
: "";
|
|
34
38
|
process.stderr.write(`gji: shell integration not set up — run \`gji init${shellArg} --write\` to enable automatic cd.\n`);
|
|
35
39
|
}
|
|
36
40
|
}
|
package/dist/init.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type SupportedShell } from
|
|
2
|
-
export type InstallSaveTarget =
|
|
1
|
+
import { type SupportedShell } from "./shell.js";
|
|
2
|
+
export type InstallSaveTarget = "local" | "global";
|
|
3
3
|
export interface SetupWizardResult {
|
|
4
4
|
branchPrefix?: string;
|
|
5
5
|
hooks?: {
|