@solaqua/gji 0.15.0 → 0.16.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 +63 -120
- package/dist/cli/commands/back.d.ts +1 -1
- package/dist/cli/commands/back.js +1 -0
- package/dist/cli/commands/clean.d.ts +1 -1
- package/dist/cli/commands/clean.js +11 -2
- package/dist/cli/commands/done.js +3 -2
- package/dist/cli/commands/go.js +18 -5
- package/dist/cli/commands/init.js +0 -14
- package/dist/cli/commands/new.d.ts +2 -1
- package/dist/cli/commands/new.js +71 -63
- package/dist/cli/commands/open.js +1 -1
- package/dist/cli/commands/pr-open.js +2 -2
- package/dist/cli/commands/pr.js +17 -6
- package/dist/cli/commands/run-hook.js +4 -0
- package/dist/cli/commands/undo.js +10 -74
- package/dist/cli/dependencies.d.ts +5 -1
- package/dist/cli/dependencies.js +7 -2
- package/dist/cli/program.js +1 -56
- package/dist/cli/runtime/headless.d.ts +3 -3
- package/dist/cli/runtime/headless.js +6 -4
- package/dist/gji-bundle.mjs +500 -842
- package/dist/infrastructure/filesystem/locked-file.d.ts +4 -0
- package/dist/infrastructure/filesystem/locked-file.js +53 -0
- package/dist/infrastructure/git/runner.d.ts +7 -0
- package/dist/infrastructure/git/runner.js +35 -1
- package/dist/infrastructure/persistence/history.js +12 -10
- package/dist/infrastructure/persistence/slots.js +24 -21
- package/dist/infrastructure/process/hooks.js +2 -0
- package/dist/infrastructure/repository/registry.js +11 -41
- package/dist/infrastructure/worktree/lifecycle.d.ts +25 -0
- package/dist/infrastructure/worktree/lifecycle.js +120 -19
- package/dist/ports/git.d.ts +1 -0
- package/dist/ports/hooks.d.ts +7 -0
- package/dist/presentation/shell/completion.js +7 -30
- package/dist/presentation/terminal/context-card.js +1 -1
- 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 +1 -1
- package/man/man1/gji-done.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-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +3 -3
- 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-task.1 +1 -1
- package/man/man1/gji-undo.1 +1 -1
- package/man/man1/gji.1 +2 -12
- package/package.json +3 -2
- package/dist/cli/commands/remove.d.ts +0 -21
- package/dist/cli/commands/remove.js +0 -189
- package/dist/cli/commands/warp.d.ts +0 -28
- package/dist/cli/commands/warp.js +0 -123
- package/man/man1/gji-remove.1 +0 -19
- package/man/man1/gji-warp.1 +0 -16
|
@@ -23,7 +23,7 @@ export function createPrOpenCommand(dependencies = {}) {
|
|
|
23
23
|
return 1;
|
|
24
24
|
}
|
|
25
25
|
if (options.select && isHeadless()) {
|
|
26
|
-
options.stderr("gji pr open --select: selector is unavailable in non-interactive mode
|
|
26
|
+
options.stderr("gji pr open --select: selector is unavailable in non-interactive mode; pass a branch or PR number instead\n");
|
|
27
27
|
return 1;
|
|
28
28
|
}
|
|
29
29
|
const repository = await detectRepository(options.cwd).catch((error) => {
|
|
@@ -236,7 +236,7 @@ async function openPullRequests(pullRequests, options, promptForPullRequest, ope
|
|
|
236
236
|
return 1;
|
|
237
237
|
}
|
|
238
238
|
if (pullRequests.length > 1 && isHeadless()) {
|
|
239
|
-
options.stderr("gji pr open: multiple open PRs found; pass an explicit PR number in non-interactive mode
|
|
239
|
+
options.stderr("gji pr open: multiple open PRs found; pass an explicit PR number in non-interactive mode\n");
|
|
240
240
|
return 1;
|
|
241
241
|
}
|
|
242
242
|
const selected = pullRequests.length === 1
|
package/dist/cli/commands/pr.js
CHANGED
|
@@ -54,7 +54,7 @@ export function createPrCommand(dependencies = {}) {
|
|
|
54
54
|
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
55
55
|
}
|
|
56
56
|
else {
|
|
57
|
-
options.stderr(`gji pr: ${message} in non-interactive mode
|
|
57
|
+
options.stderr(`gji pr: ${message} in non-interactive mode\n`);
|
|
58
58
|
options.stderr(`Hint: Use 'gji done pr/${prNumber}' or 'gji clean' to remove the existing worktree\n`);
|
|
59
59
|
}
|
|
60
60
|
return 1;
|
|
@@ -108,10 +108,21 @@ export function createPrCommand(dependencies = {}) {
|
|
|
108
108
|
}
|
|
109
109
|
await mkdir(dirname(worktreePath), { recursive: true });
|
|
110
110
|
const branchAlreadyExists = await localBranchExists(repository.repoRoot, branchName, runtime);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
try {
|
|
112
|
+
await runtime.git.addWorktree(repository.repoRoot, worktreePath, branchAlreadyExists
|
|
113
|
+
? { branch: branchName }
|
|
114
|
+
: { branch: branchName, create: { startPoint: remoteRef } });
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
const message = `failed to create worktree: ${error instanceof Error ? error.message : String(error)}`;
|
|
118
|
+
if (options.json) {
|
|
119
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
options.stderr(`gji pr: ${message}\n`);
|
|
123
|
+
}
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
115
126
|
const bootstrap = await runtime.bootstrap.bootstrapWorktree({
|
|
116
127
|
branch: branchName,
|
|
117
128
|
config,
|
|
@@ -173,7 +184,7 @@ export const runPrCommand = createPrCommand();
|
|
|
173
184
|
async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef, runtime) {
|
|
174
185
|
for (const sourceRef of listPullRequestSourceRefs(input, prNumber)) {
|
|
175
186
|
try {
|
|
176
|
-
await runtime.git.
|
|
187
|
+
await runtime.git.runGitRetryingContention(repoRoot, [
|
|
177
188
|
"fetch",
|
|
178
189
|
"origin",
|
|
179
190
|
`${sourceRef}:${remoteRef}`,
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { defaultCliDependencies } from "../dependencies.js";
|
|
2
2
|
const VALID_HOOKS = [
|
|
3
|
+
"after-branch-create",
|
|
3
4
|
"after-create",
|
|
4
5
|
"after-enter",
|
|
6
|
+
"after-switch",
|
|
5
7
|
"before-remove",
|
|
6
8
|
];
|
|
7
9
|
const CAMEL_ALIASES = {
|
|
10
|
+
afterBranchCreate: "after-branch-create",
|
|
8
11
|
afterCreate: "after-create",
|
|
9
12
|
afterEnter: "after-enter",
|
|
13
|
+
afterSwitch: "after-switch",
|
|
10
14
|
beforeRemove: "before-remove",
|
|
11
15
|
};
|
|
12
16
|
function isValidHook(hook) {
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import { access,
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import {
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
4
|
import { confirm, isCancel } from "@clack/prompts";
|
|
5
|
+
import { withFileLock, writeJsonAtomic, } from "../../infrastructure/filesystem/locked-file.js";
|
|
5
6
|
import { defaultCliDependencies, } from "../dependencies.js";
|
|
6
7
|
import { isHeadless } from "../runtime/headless.js";
|
|
7
8
|
const MAX_UNDO_RECORDS = 20;
|
|
8
|
-
const UNDO_LOCK_STALE_AFTER_MS = 60_000;
|
|
9
|
-
const UNDO_LOCK_RETRY_DELAY_MS = 5;
|
|
10
|
-
const UNDO_LOCK_WAIT_TIMEOUT_MS = UNDO_LOCK_STALE_AFTER_MS + UNDO_LOCK_RETRY_DELAY_MS;
|
|
11
|
-
const undoLogQueues = new Map();
|
|
12
9
|
export function undoLogPath(home = homedir(), globalConfigDirectory = defaultCliDependencies.configStore
|
|
13
10
|
.GLOBAL_CONFIG_DIRECTORY) {
|
|
14
11
|
const configured = process.env.GJI_CONFIG_DIR;
|
|
@@ -65,16 +62,16 @@ export async function recordUndoOperation(op, repoRoot, worktrees, home = homedi
|
|
|
65
62
|
timestamp,
|
|
66
63
|
entries,
|
|
67
64
|
};
|
|
68
|
-
return
|
|
65
|
+
return withFileLock(undoLogPath(home, globalConfigDirectory), async () => {
|
|
69
66
|
const existing = await loadUndoRecords(home, globalConfigDirectory);
|
|
70
67
|
await writeUndoRecords(home, [record, ...existing].slice(0, MAX_UNDO_RECORDS), globalConfigDirectory);
|
|
71
68
|
return record;
|
|
72
|
-
}
|
|
69
|
+
});
|
|
73
70
|
}
|
|
74
71
|
export async function finalizeUndoOperation(record, removedWorktrees, home = homedir(), globalConfigDirectory = defaultCliDependencies.configStore
|
|
75
72
|
.GLOBAL_CONFIG_DIRECTORY) {
|
|
76
73
|
const removedPaths = new Set(removedWorktrees.map((worktree) => worktree.path));
|
|
77
|
-
await
|
|
74
|
+
await withFileLock(undoLogPath(home, globalConfigDirectory), async () => {
|
|
78
75
|
const records = await loadUndoRecords(home, globalConfigDirectory);
|
|
79
76
|
const remaining = records.flatMap((candidate) => {
|
|
80
77
|
if (candidate.id !== record.id)
|
|
@@ -83,7 +80,7 @@ export async function finalizeUndoOperation(record, removedWorktrees, home = hom
|
|
|
83
80
|
return entries.length === 0 ? [] : [{ ...candidate, entries }];
|
|
84
81
|
});
|
|
85
82
|
await writeUndoRecords(home, remaining, globalConfigDirectory);
|
|
86
|
-
}
|
|
83
|
+
});
|
|
87
84
|
}
|
|
88
85
|
export async function restoreUndoRecord(record, home = homedir(), runtime = defaultCliDependencies) {
|
|
89
86
|
const globalConfigDirectory = runtime.configStore.GLOBAL_CONFIG_DIRECTORY;
|
|
@@ -104,7 +101,7 @@ export async function restoreUndoRecord(record, home = homedir(), runtime = defa
|
|
|
104
101
|
unresolved.push(entry);
|
|
105
102
|
}
|
|
106
103
|
}
|
|
107
|
-
await
|
|
104
|
+
await withFileLock(undoLogPath(home, globalConfigDirectory), async () => {
|
|
108
105
|
const records = await loadUndoRecords(home, globalConfigDirectory);
|
|
109
106
|
const remaining = records.flatMap((candidate) => {
|
|
110
107
|
if (candidate.id !== record.id)
|
|
@@ -114,7 +111,7 @@ export async function restoreUndoRecord(record, home = homedir(), runtime = defa
|
|
|
114
111
|
return [{ ...candidate, entries: unresolved }];
|
|
115
112
|
});
|
|
116
113
|
await writeUndoRecords(home, remaining, globalConfigDirectory);
|
|
117
|
-
}
|
|
114
|
+
});
|
|
118
115
|
return { restored, failed };
|
|
119
116
|
}
|
|
120
117
|
export async function runUndoCommand(options) {
|
|
@@ -292,70 +289,9 @@ async function assertCommitExists(repoRoot, headSha, runGit) {
|
|
|
292
289
|
throw new Error("commit no longer exists (gc'd) — cannot restore");
|
|
293
290
|
}
|
|
294
291
|
}
|
|
295
|
-
async function enqueueUndoLogWrite(home, operation, globalConfigDirectory = defaultCliDependencies.configStore
|
|
296
|
-
.GLOBAL_CONFIG_DIRECTORY) {
|
|
297
|
-
const path = undoLogPath(home, globalConfigDirectory);
|
|
298
|
-
await mkdir(dirname(path), { recursive: true });
|
|
299
|
-
const previous = undoLogQueues.get(path) ?? Promise.resolve();
|
|
300
|
-
const runLocked = async () => {
|
|
301
|
-
const release = await acquireUndoLogLock(path);
|
|
302
|
-
try {
|
|
303
|
-
return await operation();
|
|
304
|
-
}
|
|
305
|
-
finally {
|
|
306
|
-
await release();
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
|
-
const current = previous.then(runLocked, runLocked);
|
|
310
|
-
const settled = current.then(() => undefined, () => undefined);
|
|
311
|
-
undoLogQueues.set(path, settled);
|
|
312
|
-
try {
|
|
313
|
-
return await current;
|
|
314
|
-
}
|
|
315
|
-
finally {
|
|
316
|
-
if (undoLogQueues.get(path) === settled)
|
|
317
|
-
undoLogQueues.delete(path);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
async function acquireUndoLogLock(path) {
|
|
321
|
-
const lockPath = `${path}.lock`;
|
|
322
|
-
const deadline = Date.now() + UNDO_LOCK_WAIT_TIMEOUT_MS;
|
|
323
|
-
while (Date.now() < deadline) {
|
|
324
|
-
try {
|
|
325
|
-
const handle = await open(lockPath, "wx");
|
|
326
|
-
return async () => {
|
|
327
|
-
await handle.close();
|
|
328
|
-
await unlink(lockPath).catch(() => undefined);
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
catch (error) {
|
|
332
|
-
if (!(error instanceof Error && "code" in error && error.code === "EEXIST"))
|
|
333
|
-
throw error;
|
|
334
|
-
const lockStat = await stat(lockPath).catch(() => null);
|
|
335
|
-
if (lockStat && Date.now() - lockStat.mtimeMs > UNDO_LOCK_STALE_AFTER_MS)
|
|
336
|
-
await unlink(lockPath).catch(() => undefined);
|
|
337
|
-
const remainingMs = deadline - Date.now();
|
|
338
|
-
if (remainingMs > 0)
|
|
339
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(UNDO_LOCK_RETRY_DELAY_MS, remainingMs)));
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
throw new Error("timed out waiting for the undo journal lock");
|
|
343
|
-
}
|
|
344
292
|
async function writeUndoRecords(home, records, globalConfigDirectory = defaultCliDependencies.configStore
|
|
345
293
|
.GLOBAL_CONFIG_DIRECTORY) {
|
|
346
|
-
|
|
347
|
-
await mkdir(dirname(path), { recursive: true });
|
|
348
|
-
const temporaryPath = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
349
|
-
try {
|
|
350
|
-
await writeFile(temporaryPath, `${JSON.stringify(records, null, 2)}\n`, {
|
|
351
|
-
encoding: "utf8",
|
|
352
|
-
flag: "wx",
|
|
353
|
-
});
|
|
354
|
-
await rename(temporaryPath, path);
|
|
355
|
-
}
|
|
356
|
-
finally {
|
|
357
|
-
await unlink(temporaryPath).catch(() => undefined);
|
|
358
|
-
}
|
|
294
|
+
await writeJsonAtomic(undoLogPath(home, globalConfigDirectory), records);
|
|
359
295
|
}
|
|
360
296
|
function isUndoRecord(value) {
|
|
361
297
|
if (!value || typeof value !== "object")
|
|
@@ -12,7 +12,7 @@ import { clearTask, readTask, writeTask } from "../infrastructure/persistence/ta
|
|
|
12
12
|
import { extractHooks, runHook } from "../infrastructure/process/hooks.js";
|
|
13
13
|
import { loadRegistry, REGISTRY_FILE_PATH, registerRepo, removeMissingRegistryEntries } from "../infrastructure/repository/registry.js";
|
|
14
14
|
import { readWorktreeInfo, readWorktreeInfos, serializeWorktreeInfo } from "../infrastructure/worktree/info.js";
|
|
15
|
-
import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isSubmoduleWorktreeRemovalError, isWorktreeDeletionError, isWorktreeForceRemovalError, removeWorktree } from "../infrastructure/worktree/lifecycle.js";
|
|
15
|
+
import { addWorktree, createBranch, deleteBranch, deleteCreatedBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isSubmoduleWorktreeRemovalError, isWorktreeDeletionError, isWorktreeForceRemovalError, removeCreatedWorktree, removeWorktree } from "../infrastructure/worktree/lifecycle.js";
|
|
16
16
|
import type { DependencyBootstrapPort, WorktreeBootstrapPort } from "../ports/bootstrap.js";
|
|
17
17
|
import type { ConfigPort } from "../ports/config.js";
|
|
18
18
|
import type { GitCommandPort } from "../ports/git.js";
|
|
@@ -42,6 +42,10 @@ export interface CliDependencies {
|
|
|
42
42
|
runHook: typeof runHook;
|
|
43
43
|
};
|
|
44
44
|
git: GitCommandPort & {
|
|
45
|
+
addWorktree: typeof addWorktree;
|
|
46
|
+
createBranch: typeof createBranch;
|
|
47
|
+
deleteCreatedBranch: typeof deleteCreatedBranch;
|
|
48
|
+
removeCreatedWorktree: typeof removeCreatedWorktree;
|
|
45
49
|
isDirtyWorktree: typeof isDirtyWorktree;
|
|
46
50
|
readWorktreeHealth: typeof readWorktreeHealth;
|
|
47
51
|
isBranchMergedInto: typeof isBranchMergedInto;
|
package/dist/cli/dependencies.js
CHANGED
|
@@ -2,7 +2,7 @@ import { prepareDependencyBootstrap, previewDependencyBootstrap, resolveDependen
|
|
|
2
2
|
import { validateSyncFilePattern } from "../infrastructure/filesystem/file-sync.js";
|
|
3
3
|
import { isDirtyWorktree, readWorktreeHealth, } from "../infrastructure/git/health.js";
|
|
4
4
|
import { isBranchMergedInto, readBranchLastCommitTimestamp, resolveRemoteBase, resolveRemoteDefaultBranch, } from "../infrastructure/git/refs.js";
|
|
5
|
-
import { runGit, runGitRaw } from "../infrastructure/git/runner.js";
|
|
5
|
+
import { runGit, runGitRaw, runGitRetryingContention, } from "../infrastructure/git/runner.js";
|
|
6
6
|
import { openBrowser } from "../infrastructure/integrations/browser.js";
|
|
7
7
|
import { defaultSpawnEditor, EDITORS, } from "../infrastructure/integrations/editor.js";
|
|
8
8
|
import { createPullRequestQuery } from "../infrastructure/integrations/pull-requests.js";
|
|
@@ -15,7 +15,7 @@ import { configPort, repositoryContextPort, repositoryRefPort, repositoryRegistr
|
|
|
15
15
|
import { loadRegistry, REGISTRY_FILE_PATH, registerRepo, removeMissingRegistryEntries, } from "../infrastructure/repository/registry.js";
|
|
16
16
|
import { bootstrapWorktree } from "../infrastructure/worktree/bootstrap.js";
|
|
17
17
|
import { readWorktreeInfo, readWorktreeInfos, serializeWorktreeInfo, } from "../infrastructure/worktree/info.js";
|
|
18
|
-
import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isSubmoduleWorktreeRemovalError, isWorktreeDeletionError, isWorktreeForceRemovalError, removeWorktree, } from "../infrastructure/worktree/lifecycle.js";
|
|
18
|
+
import { addWorktree, createBranch, deleteBranch, deleteCreatedBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isSubmoduleWorktreeRemovalError, isWorktreeDeletionError, isWorktreeForceRemovalError, removeCreatedWorktree, removeWorktree, } from "../infrastructure/worktree/lifecycle.js";
|
|
19
19
|
import { formatLastCommit, formatRelativeAge, formatUpstreamState, } from "../presentation/worktree/format.js";
|
|
20
20
|
export function createCliDependencies() {
|
|
21
21
|
const pullRequests = createPullRequestQuery();
|
|
@@ -49,6 +49,11 @@ export function createCliDependencies() {
|
|
|
49
49
|
git: {
|
|
50
50
|
runGit,
|
|
51
51
|
runGitRaw,
|
|
52
|
+
runGitRetryingContention,
|
|
53
|
+
addWorktree,
|
|
54
|
+
createBranch,
|
|
55
|
+
deleteCreatedBranch,
|
|
56
|
+
removeCreatedWorktree,
|
|
52
57
|
isDirtyWorktree,
|
|
53
58
|
readWorktreeHealth,
|
|
54
59
|
isBranchMergedInto,
|
package/dist/cli/program.js
CHANGED
|
@@ -16,7 +16,6 @@ import { runNewCommand } from "./commands/new.js";
|
|
|
16
16
|
import { runOpenCommand } from "./commands/open.js";
|
|
17
17
|
import { runPrCommand } from "./commands/pr.js";
|
|
18
18
|
import { createPrOpenCommand, } from "./commands/pr-open.js";
|
|
19
|
-
import { runRemoveCommand } from "./commands/remove.js";
|
|
20
19
|
import { runRootCommand } from "./commands/root.js";
|
|
21
20
|
import { runHookCommand } from "./commands/run-hook.js";
|
|
22
21
|
import { runStatusCommand } from "./commands/status.js";
|
|
@@ -24,7 +23,6 @@ import { runSyncCommand } from "./commands/sync.js";
|
|
|
24
23
|
import { runSyncFilesCommand } from "./commands/sync-files-command.js";
|
|
25
24
|
import { runTaskCommand } from "./commands/task-command.js";
|
|
26
25
|
import { runUndoCommand } from "./commands/undo.js";
|
|
27
|
-
import { runWarpCommand } from "./commands/warp.js";
|
|
28
26
|
import { createCliDependencies } from "./dependencies.js";
|
|
29
27
|
import { isHeadless } from "./runtime/headless.js";
|
|
30
28
|
export function createProgram() {
|
|
@@ -299,27 +297,11 @@ function registerCommands(program) {
|
|
|
299
297
|
.option("--dry-run", "show what would be deleted without removing anything")
|
|
300
298
|
.option("--json", "emit JSON on success or error instead of human-readable output")
|
|
301
299
|
.action(notImplemented("clean"));
|
|
302
|
-
program
|
|
303
|
-
.command("remove [branch]")
|
|
304
|
-
.alias("rm")
|
|
305
|
-
.description("deprecated: use gji done for one worktree or gji clean for bulk cleanup")
|
|
306
|
-
.option("-f, --force", "bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch")
|
|
307
|
-
.option("--dry-run", "show what would be deleted without removing anything")
|
|
308
|
-
.option("--json", "emit JSON on success or error instead of human-readable output")
|
|
309
|
-
.action(notImplemented("remove"));
|
|
310
300
|
program
|
|
311
301
|
.command("run-hook <hook>")
|
|
312
302
|
.alias("trigger-hook")
|
|
313
|
-
.description("run a named hook (after-create, after-enter, before-remove) in the current worktree")
|
|
303
|
+
.description("run a named hook (after-branch-create, after-create, after-enter, after-switch, before-remove) in the current worktree")
|
|
314
304
|
.action(notImplemented("run-hook"));
|
|
315
|
-
program
|
|
316
|
-
.command("warp [branch]")
|
|
317
|
-
.description("deprecated: use gji go (press Tab for all known repos)")
|
|
318
|
-
// --print is the shell-wrapper bypass signal (see SHELL_WRAPPED_COMMANDS in init.ts).
|
|
319
|
-
// The shell omits GJI_WARP_OUTPUT_FILE, so writeShellOutput falls through to stdout.
|
|
320
|
-
.option("--print", "print the resolved worktree path without changing directory")
|
|
321
|
-
.option("--json", "emit JSON on success or error instead of human-readable output")
|
|
322
|
-
.action(notImplemented("warp"));
|
|
323
305
|
const configCommand = program
|
|
324
306
|
.command("config")
|
|
325
307
|
.description("manage global config defaults")
|
|
@@ -649,27 +631,6 @@ function attachCommandActions(program, options) {
|
|
|
649
631
|
throw commanderExit(exitCode);
|
|
650
632
|
}
|
|
651
633
|
});
|
|
652
|
-
const runRemovalCommand = async (branch, commandOptions = {}) => {
|
|
653
|
-
const exitCode = await runRemoveCommand({
|
|
654
|
-
branch,
|
|
655
|
-
cwd: options.cwd,
|
|
656
|
-
dryRun: commandOptions.dryRun,
|
|
657
|
-
force: commandOptions.force,
|
|
658
|
-
json: commandOptions.json,
|
|
659
|
-
runtime: options.dependencies,
|
|
660
|
-
stderr: options.stderr,
|
|
661
|
-
stdout: options.stdout,
|
|
662
|
-
});
|
|
663
|
-
if (exitCode !== 0) {
|
|
664
|
-
throw commanderExit(exitCode);
|
|
665
|
-
}
|
|
666
|
-
};
|
|
667
|
-
requireCommand(program, "remove").action(async (branch, commandOptions) => {
|
|
668
|
-
if (!commandOptions.json) {
|
|
669
|
-
options.stderr("gji remove is deprecated; use 'gji done' for one worktree or 'gji clean' for bulk cleanup.\n");
|
|
670
|
-
}
|
|
671
|
-
return runRemovalCommand(branch, commandOptions);
|
|
672
|
-
});
|
|
673
634
|
requireCommand(program, "run-hook").action(async (hook) => {
|
|
674
635
|
const exitCode = await runHookCommand({
|
|
675
636
|
cwd: options.cwd,
|
|
@@ -681,22 +642,6 @@ function attachCommandActions(program, options) {
|
|
|
681
642
|
throw commanderExit(exitCode);
|
|
682
643
|
}
|
|
683
644
|
});
|
|
684
|
-
requireCommand(program, "warp").action(async (branch, commandOptions) => {
|
|
685
|
-
if (!commandOptions.json) {
|
|
686
|
-
options.stderr("gji warp is deprecated; use 'gji go' (press Tab for all repositories).\n");
|
|
687
|
-
}
|
|
688
|
-
const exitCode = await runWarpCommand({
|
|
689
|
-
branch,
|
|
690
|
-
cwd: options.cwd,
|
|
691
|
-
json: commandOptions.json,
|
|
692
|
-
runtime: options.dependencies,
|
|
693
|
-
stderr: options.stderr,
|
|
694
|
-
stdout: options.stdout,
|
|
695
|
-
});
|
|
696
|
-
if (exitCode !== 0) {
|
|
697
|
-
throw commanderExit(exitCode);
|
|
698
|
-
}
|
|
699
|
-
});
|
|
700
645
|
const configCommand = requireCommand(program, "config");
|
|
701
646
|
configCommand.action(async () => {
|
|
702
647
|
const exitCode = await runConfigCommand({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Returns true when
|
|
3
|
-
*
|
|
4
|
-
* Commands that would otherwise
|
|
2
|
+
* Returns true when no one can answer a prompt: GJI_NO_TUI=1 is set, or stdin
|
|
3
|
+
* or stdout is not a terminal.
|
|
4
|
+
* Commands that would otherwise wait for input must fail fast instead.
|
|
5
5
|
*/
|
|
6
6
|
export declare function isHeadless(): boolean;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Returns true when
|
|
3
|
-
*
|
|
4
|
-
* Commands that would otherwise
|
|
2
|
+
* Returns true when no one can answer a prompt: GJI_NO_TUI=1 is set, or stdin
|
|
3
|
+
* or stdout is not a terminal.
|
|
4
|
+
* Commands that would otherwise wait for input must fail fast instead.
|
|
5
5
|
*/
|
|
6
6
|
export function isHeadless() {
|
|
7
|
-
return process.env.GJI_NO_TUI === "1"
|
|
7
|
+
return (process.env.GJI_NO_TUI === "1" ||
|
|
8
|
+
process.stdin.isTTY !== true ||
|
|
9
|
+
process.stdout.isTTY !== true);
|
|
8
10
|
}
|