@solaqua/gji 0.12.5 → 0.14.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 +12 -0
- package/dist/application/worktree/catalog.d.ts +31 -0
- package/dist/application/worktree/catalog.js +88 -0
- package/dist/application/worktree/context-card.d.ts +14 -0
- package/dist/application/worktree/context-card.js +12 -0
- package/dist/{go-resolver.d.ts → application/worktree/go-resolution.d.ts} +12 -4
- package/dist/{go-resolver.js → application/worktree/go-resolution.js} +50 -46
- package/dist/application/worktree/read-models.d.ts +31 -0
- package/dist/application/worktree/sources.d.ts +16 -0
- package/dist/application/worktree/sources.js +86 -0
- package/dist/{back.d.ts → cli/commands/back.d.ts} +3 -1
- package/dist/{back.js → cli/commands/back.js} +8 -6
- package/dist/{clean.d.ts → cli/commands/clean.d.ts} +6 -3
- package/dist/cli/commands/clean.js +555 -0
- package/dist/{completion.js → cli/commands/completion.js} +2 -2
- package/dist/{config-command.d.ts → cli/commands/config-command.d.ts} +2 -0
- package/dist/{config-command.js → cli/commands/config-command.js} +7 -5
- package/dist/{doctor.d.ts → cli/commands/doctor.d.ts} +2 -0
- package/dist/{doctor.js → cli/commands/doctor.js} +23 -25
- package/dist/{done.d.ts → cli/commands/done.d.ts} +2 -0
- package/dist/{done.js → cli/commands/done.js} +26 -23
- package/dist/{go.d.ts → cli/commands/go.d.ts} +8 -2
- package/dist/{go.js → cli/commands/go.js} +68 -48
- package/dist/{history-command.d.ts → cli/commands/history-command.d.ts} +2 -0
- package/dist/{history-command.js → cli/commands/history-command.js} +3 -1
- package/dist/{hub.d.ts → cli/commands/hub.d.ts} +7 -3
- package/dist/{hub.js → cli/commands/hub.js} +26 -33
- package/dist/{init.d.ts → cli/commands/init.d.ts} +3 -1
- package/dist/{init.js → cli/commands/init.js} +37 -34
- package/dist/{ls.d.ts → cli/commands/ls.d.ts} +4 -2
- package/dist/{ls.js → cli/commands/ls.js} +8 -5
- package/dist/{new.d.ts → cli/commands/new.d.ts} +6 -2
- package/dist/{new.js → cli/commands/new.js} +237 -109
- package/dist/{open.d.ts → cli/commands/open.d.ts} +4 -2
- package/dist/{open.js → cli/commands/open.js} +19 -12
- package/dist/{pr-open.d.ts → cli/commands/pr-open.d.ts} +4 -2
- package/dist/{pr-open.js → cli/commands/pr-open.js} +20 -18
- package/dist/{pr.d.ts → cli/commands/pr.d.ts} +5 -3
- package/dist/{pr.js → cli/commands/pr.js} +34 -41
- package/dist/{remove.d.ts → cli/commands/remove.d.ts} +4 -2
- package/dist/{remove.js → cli/commands/remove.js} +23 -17
- package/dist/{root.d.ts → cli/commands/root.d.ts} +2 -0
- package/dist/{root.js → cli/commands/root.js} +4 -2
- package/dist/{run-hook.d.ts → cli/commands/run-hook.d.ts} +2 -0
- package/dist/{run-hook.js → cli/commands/run-hook.js} +7 -4
- package/dist/{status.d.ts → cli/commands/status.d.ts} +2 -0
- package/dist/{status.js → cli/commands/status.js} +10 -7
- package/dist/{sync-files-command.d.ts → cli/commands/sync-files-command.d.ts} +2 -0
- package/dist/{sync-files-command.js → cli/commands/sync-files-command.js} +11 -9
- package/dist/{sync.d.ts → cli/commands/sync.d.ts} +2 -0
- package/dist/{sync.js → cli/commands/sync.js} +7 -4
- package/dist/{task-command.d.ts → cli/commands/task-command.d.ts} +2 -0
- package/dist/{task-command.js → cli/commands/task-command.js} +4 -2
- package/dist/{undo.d.ts → cli/commands/undo.d.ts} +8 -6
- package/dist/{undo.js → cli/commands/undo.js} +44 -34
- package/dist/{warp.d.ts → cli/commands/warp.d.ts} +5 -2
- package/dist/{warp.js → cli/commands/warp.js} +25 -13
- package/dist/cli/dependencies.d.ts +114 -0
- package/dist/cli/dependencies.js +118 -0
- package/dist/{cli.d.ts → cli/program.d.ts} +4 -2
- package/dist/{cli.js → cli/program.js} +108 -124
- package/dist/domain/repository/context.d.ts +6 -0
- package/dist/domain/repository/context.js +1 -0
- package/dist/domain/repository/registry.d.ts +5 -0
- package/dist/domain/repository/registry.js +1 -0
- package/dist/domain/shared/concurrency.d.ts +1 -0
- package/dist/domain/shared/concurrency.js +20 -0
- package/dist/domain/worktree/matching.d.ts +4 -0
- package/dist/domain/worktree/matching.js +79 -0
- package/dist/domain/worktree/policy.d.ts +2 -0
- package/dist/domain/worktree/policy.js +58 -0
- package/dist/domain/worktree/pr-reference.d.ts +1 -0
- package/dist/domain/worktree/pr-reference.js +9 -0
- package/dist/{worktree-source.d.ts → domain/worktree/source.d.ts} +1 -1
- package/dist/domain/worktree/source.js +1 -0
- package/dist/domain/worktree/types.d.ts +5 -0
- package/dist/domain/worktree/types.js +1 -0
- package/dist/gji-bundle.mjs +8756 -7666
- package/dist/index.js +3 -3
- package/dist/{dependency-bootstrap.d.ts → infrastructure/bootstrap/dependency-bootstrap.d.ts} +3 -30
- package/dist/{dependency-bootstrap.js → infrastructure/bootstrap/dependency-bootstrap.js} +4 -4
- package/dist/infrastructure/git/health.d.ts +9 -0
- package/dist/infrastructure/git/health.js +44 -0
- package/dist/infrastructure/git/refs.d.ts +6 -0
- package/dist/{git.js → infrastructure/git/refs.js} +4 -62
- package/dist/infrastructure/git/runner.d.ts +2 -0
- package/dist/infrastructure/git/runner.js +16 -0
- package/dist/{editor.d.ts → infrastructure/integrations/editor.d.ts} +2 -6
- package/dist/{pull-requests.d.ts → infrastructure/integrations/pull-requests.d.ts} +3 -11
- package/dist/{config.d.ts → infrastructure/persistence/config.d.ts} +3 -17
- package/dist/{config.js → infrastructure/persistence/config.js} +2 -17
- package/dist/{history.d.ts → infrastructure/persistence/history.d.ts} +2 -5
- package/dist/{task.js → infrastructure/persistence/task.js} +1 -1
- package/dist/infrastructure/process/command-runner.d.ts +3 -0
- package/dist/{hooks.d.ts → infrastructure/process/hooks.d.ts} +2 -12
- package/dist/infrastructure/repository/adapters.d.ts +7 -0
- package/dist/infrastructure/repository/adapters.js +23 -0
- package/dist/infrastructure/repository/context.d.ts +3 -0
- package/dist/infrastructure/repository/context.js +20 -0
- package/dist/infrastructure/repository/refs.d.ts +3 -0
- package/dist/infrastructure/repository/refs.js +37 -0
- package/dist/{repo-registry.d.ts → infrastructure/repository/registry.d.ts} +1 -5
- package/dist/{repo-registry.js → infrastructure/repository/registry.js} +1 -1
- package/dist/infrastructure/repository/worktrees.d.ts +2 -0
- package/dist/infrastructure/repository/worktrees.js +48 -0
- package/dist/{uv-validation.d.ts → infrastructure/validation/uv.d.ts} +1 -1
- package/dist/{uv-validation.js → infrastructure/validation/uv.js} +1 -1
- package/dist/infrastructure/worktree/bootstrap.d.ts +3 -0
- package/dist/{worktree-bootstrap.js → infrastructure/worktree/bootstrap.js} +7 -10
- package/dist/infrastructure/worktree/info.d.ts +6 -0
- package/dist/infrastructure/worktree/info.js +61 -0
- package/dist/{worktree-management.d.ts → infrastructure/worktree/lifecycle.d.ts} +0 -6
- package/dist/{worktree-management.js → infrastructure/worktree/lifecycle.js} +1 -9
- package/dist/ports/bootstrap.d.ts +61 -0
- package/dist/ports/bootstrap.js +1 -0
- package/dist/ports/config.d.ts +21 -0
- package/dist/ports/config.js +17 -0
- package/dist/ports/editor.d.ts +6 -0
- package/dist/ports/editor.js +1 -0
- package/dist/ports/git.d.ts +8 -0
- package/dist/ports/git.js +1 -0
- package/dist/ports/history.d.ts +5 -0
- package/dist/ports/history.js +1 -0
- package/dist/ports/hooks.d.ts +12 -0
- package/dist/ports/hooks.js +1 -0
- package/dist/{command-runner.d.ts → ports/process.d.ts} +0 -1
- package/dist/ports/process.js +1 -0
- package/dist/ports/pull-requests.d.ts +11 -0
- package/dist/ports/pull-requests.js +1 -0
- package/dist/ports/repository.d.ts +17 -0
- package/dist/ports/repository.js +1 -0
- package/dist/{bootstrap-output.d.ts → presentation/bootstrap/output.d.ts} +1 -1
- package/dist/presentation/bootstrap/preview.d.ts +2 -0
- package/dist/{bootstrap-preview.js → presentation/bootstrap/preview.js} +0 -4
- package/dist/{shell-completion.js → presentation/shell/completion.js} +5 -4
- package/dist/presentation/terminal/context-card.d.ts +2 -0
- package/dist/{context-card.js → presentation/terminal/context-card.js} +3 -12
- package/dist/presentation/worktree/format.d.ts +4 -0
- package/dist/presentation/worktree/format.js +41 -0
- package/dist/{worktree-picker.d.ts → presentation/worktree/picker.d.ts} +16 -17
- package/dist/{worktree-picker.js → presentation/worktree/picker.js} +30 -184
- 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 +6 -3
- 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 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-task.1 +1 -1
- package/man/man1/gji-undo.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +2 -2
- package/package.json +1 -1
- package/dist/bootstrap-preview.d.ts +0 -9
- package/dist/clean.js +0 -313
- package/dist/context-card.d.ts +0 -1
- package/dist/format-bytes.d.ts +0 -1
- package/dist/format-bytes.js +0 -14
- package/dist/git.d.ts +0 -18
- package/dist/repo.d.ts +0 -20
- package/dist/repo.js +0 -161
- package/dist/worktree-bootstrap.d.ts +0 -24
- package/dist/worktree-info.d.ts +0 -37
- package/dist/worktree-info.js +0 -123
- package/dist/worktree-sources.d.ts +0 -4
- package/dist/worktree-sources.js +0 -77
- /package/dist/{worktree-source.js → application/worktree/read-models.js} +0 -0
- /package/dist/{completion.d.ts → cli/commands/completion.d.ts} +0 -0
- /package/dist/{headless.d.ts → cli/runtime/headless.d.ts} +0 -0
- /package/dist/{headless.js → cli/runtime/headless.js} +0 -0
- /package/dist/{paths.d.ts → domain/shared/paths.d.ts} +0 -0
- /package/dist/{paths.js → domain/shared/paths.js} +0 -0
- /package/dist/{file-sync.d.ts → infrastructure/filesystem/file-sync.d.ts} +0 -0
- /package/dist/{file-sync.js → infrastructure/filesystem/file-sync.js} +0 -0
- /package/dist/{fs-utils.d.ts → infrastructure/filesystem/fs-utils.d.ts} +0 -0
- /package/dist/{fs-utils.js → infrastructure/filesystem/fs-utils.js} +0 -0
- /package/dist/{safe-destination.d.ts → infrastructure/filesystem/safe-destination.d.ts} +0 -0
- /package/dist/{safe-destination.js → infrastructure/filesystem/safe-destination.js} +0 -0
- /package/dist/{browser.d.ts → infrastructure/integrations/browser.d.ts} +0 -0
- /package/dist/{browser.js → infrastructure/integrations/browser.js} +0 -0
- /package/dist/{editor.js → infrastructure/integrations/editor.js} +0 -0
- /package/dist/{pull-requests.js → infrastructure/integrations/pull-requests.js} +0 -0
- /package/dist/{history.js → infrastructure/persistence/history.js} +0 -0
- /package/dist/{slots.d.ts → infrastructure/persistence/slots.d.ts} +0 -0
- /package/dist/{slots.js → infrastructure/persistence/slots.js} +0 -0
- /package/dist/{task.d.ts → infrastructure/persistence/task.d.ts} +0 -0
- /package/dist/{command-runner.js → infrastructure/process/command-runner.js} +0 -0
- /package/dist/{hooks.js → infrastructure/process/hooks.js} +0 -0
- /package/dist/{bootstrap-output.js → presentation/bootstrap/output.js} +0 -0
- /package/dist/{conflict.d.ts → presentation/prompts/path-conflict.d.ts} +0 -0
- /package/dist/{conflict.js → presentation/prompts/path-conflict.js} +0 -0
- /package/dist/{shell-completion.d.ts → presentation/shell/completion.d.ts} +0 -0
- /package/dist/{shell-handoff.d.ts → presentation/shell/handoff.d.ts} +0 -0
- /package/dist/{shell-handoff.js → presentation/shell/handoff.js} +0 -0
- /package/dist/{shell-setup.d.ts → presentation/shell/setup.d.ts} +0 -0
- /package/dist/{shell-setup.js → presentation/shell/setup.js} +0 -0
- /package/dist/{shell.d.ts → presentation/shell/shell.d.ts} +0 -0
- /package/dist/{shell.js → presentation/shell/shell.js} +0 -0
- /package/dist/{navigation-output.d.ts → presentation/terminal/navigation.d.ts} +0 -0
- /package/dist/{navigation-output.js → presentation/terminal/navigation.js} +0 -0
- /package/dist/{terminal-text.d.ts → presentation/terminal/text.d.ts} +0 -0
- /package/dist/{terminal-text.js → presentation/terminal/text.js} +0 -0
- /package/dist/{worktree-prompts.d.ts → presentation/worktree/prompts.d.ts} +0 -0
- /package/dist/{worktree-prompts.js → presentation/worktree/prompts.js} +0 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { confirm, isCancel } from "@clack/prompts";
|
|
2
|
+
import { loadLinkedWorktrees, } from "../../application/worktree/catalog.js";
|
|
3
|
+
import { deduplicateWorktreeSources, listRegisteredWorktreeSources, } from "../../application/worktree/sources.js";
|
|
4
|
+
import { mapWithConcurrency } from "../../domain/shared/concurrency.js";
|
|
5
|
+
import { buildWorktreePromptEntries, promptForMultipleWorktrees, } from "../../presentation/worktree/picker.js";
|
|
6
|
+
import { defaultConfirmForceDeleteBranch, defaultConfirmForceRemoveWorktree, } from "../../presentation/worktree/prompts.js";
|
|
7
|
+
import { defaultCliDependencies, } from "../dependencies.js";
|
|
8
|
+
import { isHeadless } from "../runtime/headless.js";
|
|
9
|
+
import { finalizeUndoOperation, recordUndoOperation, } from "./undo.js";
|
|
10
|
+
function toPromptSource(candidate) {
|
|
11
|
+
return {
|
|
12
|
+
repoName: candidate.repoName,
|
|
13
|
+
repoRoot: candidate.repoRoot,
|
|
14
|
+
worktree: candidate.worktree,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const MAX_CLEAN_REPOSITORY_CONCURRENCY = 4;
|
|
18
|
+
const MAX_CLEAN_WORKTREE_CONCURRENCY = 8;
|
|
19
|
+
export function createCleanCommand(dependencies = {}) {
|
|
20
|
+
const promptForWorktrees = dependencies.promptForWorktrees ?? defaultPromptForWorktrees;
|
|
21
|
+
const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval;
|
|
22
|
+
const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ??
|
|
23
|
+
defaultConfirmForceRemoveWorktree;
|
|
24
|
+
const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
|
|
25
|
+
return async function runCleanCommand(options) {
|
|
26
|
+
const runtime = options.runtime ?? defaultCliDependencies;
|
|
27
|
+
const { readWorktreeHealth, isBranchMergedInto, resolveRemoteBase, runGit, } = runtime.git;
|
|
28
|
+
const { loadEffectiveConfig } = runtime.config;
|
|
29
|
+
const { releaseWorktreeSlot } = runtime.slots;
|
|
30
|
+
const sourceDependencies = {
|
|
31
|
+
...runtime.repositoryContext,
|
|
32
|
+
...runtime.repositoryRegistry,
|
|
33
|
+
...runtime.worktrees,
|
|
34
|
+
};
|
|
35
|
+
const { formatLastCommit, formatUpstreamState, readWorktreeInfos, serializeWorktreeInfo, } = runtime.worktreeInfo;
|
|
36
|
+
const { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isSubmoduleWorktreeRemovalError, isWorktreeDeletionError, isWorktreeForceRemovalError, removeWorktree, } = runtime.worktreeLifecycle;
|
|
37
|
+
const { linkedWorktrees, repository } = await loadLinkedWorktrees(options.cwd, sourceDependencies);
|
|
38
|
+
const currentSources = linkedWorktrees
|
|
39
|
+
.filter((worktree) => worktree.path !== repository.currentRoot)
|
|
40
|
+
.map((worktree) => ({
|
|
41
|
+
repoName: repository.repoName,
|
|
42
|
+
repoRoot: repository.repoRoot,
|
|
43
|
+
worktree,
|
|
44
|
+
}));
|
|
45
|
+
const cleanupDependencies = {
|
|
46
|
+
isBranchMergedInto,
|
|
47
|
+
loadEffectiveConfig,
|
|
48
|
+
readWorktreeHealth,
|
|
49
|
+
resolveRemoteBase,
|
|
50
|
+
runGit,
|
|
51
|
+
};
|
|
52
|
+
const currentCandidates = await resolveCleanupCandidates({
|
|
53
|
+
sources: currentSources,
|
|
54
|
+
stale: options.stale,
|
|
55
|
+
stderr: options.stderr,
|
|
56
|
+
}, cleanupDependencies);
|
|
57
|
+
let activeCandidates = currentCandidates;
|
|
58
|
+
let allCandidates = null;
|
|
59
|
+
let currentScope = true;
|
|
60
|
+
const interactiveSelection = !options.force && !options.json && !isHeadless();
|
|
61
|
+
const skippedRepositories = [];
|
|
62
|
+
const loadAllCandidates = async (signal) => {
|
|
63
|
+
throwIfAborted(signal);
|
|
64
|
+
if (allCandidates !== null)
|
|
65
|
+
return allCandidates;
|
|
66
|
+
const registered = await listRegisteredWorktreeSources({
|
|
67
|
+
cwd: options.cwd,
|
|
68
|
+
repositoryPort: sourceDependencies,
|
|
69
|
+
signal,
|
|
70
|
+
});
|
|
71
|
+
skippedRepositories.push(...registered.skipped);
|
|
72
|
+
if (registered.skipped.length > 0) {
|
|
73
|
+
reportSkippedRepositories(skippedRepositories, options.stderr);
|
|
74
|
+
}
|
|
75
|
+
const sources = deduplicateWorktreeSources([
|
|
76
|
+
...currentSources,
|
|
77
|
+
...registered.sources,
|
|
78
|
+
]).filter((source) => source.repoRoot !== undefined &&
|
|
79
|
+
source.worktree.path !== source.repoRoot &&
|
|
80
|
+
source.worktree.path !== repository.currentRoot);
|
|
81
|
+
const otherRepositorySources = sources.filter((source) => source.repoRoot !== repository.repoRoot);
|
|
82
|
+
const otherRepositoryCandidates = await resolveCleanupCandidates({
|
|
83
|
+
sources: otherRepositorySources,
|
|
84
|
+
stale: options.stale,
|
|
85
|
+
stderr: options.stderr,
|
|
86
|
+
signal,
|
|
87
|
+
}, cleanupDependencies);
|
|
88
|
+
allCandidates = [...currentCandidates, ...otherRepositoryCandidates];
|
|
89
|
+
throwIfAborted(signal);
|
|
90
|
+
return allCandidates;
|
|
91
|
+
};
|
|
92
|
+
const scope = {
|
|
93
|
+
label: "current repository",
|
|
94
|
+
toggleLabel: "all repositories",
|
|
95
|
+
toggle: async (signal) => {
|
|
96
|
+
const nextCurrentScope = !currentScope;
|
|
97
|
+
const nextCandidates = nextCurrentScope
|
|
98
|
+
? currentCandidates
|
|
99
|
+
: await loadAllCandidates(signal);
|
|
100
|
+
throwIfAborted(signal);
|
|
101
|
+
currentScope = nextCurrentScope;
|
|
102
|
+
activeCandidates = nextCandidates;
|
|
103
|
+
return {
|
|
104
|
+
sources: nextCandidates.map(toPromptSource),
|
|
105
|
+
metadata: nextCurrentScope ? "full" : "fast",
|
|
106
|
+
label: nextCurrentScope ? "current repository" : "all repositories",
|
|
107
|
+
toggleLabel: nextCurrentScope
|
|
108
|
+
? "all repositories"
|
|
109
|
+
: "current repository",
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
if (currentCandidates.length === 0 && interactiveSelection) {
|
|
114
|
+
const loadedAllCandidates = await loadAllCandidates();
|
|
115
|
+
if (loadedAllCandidates.length === 0) {
|
|
116
|
+
if (options.stale) {
|
|
117
|
+
emitNoStaleCandidates(options);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
emitError(options, "No linked worktrees to clean");
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
currentScope = false;
|
|
124
|
+
activeCandidates = loadedAllCandidates;
|
|
125
|
+
scope.label = "all repositories";
|
|
126
|
+
scope.toggleLabel = "current repository";
|
|
127
|
+
}
|
|
128
|
+
if (currentCandidates.length === 0 && !interactiveSelection) {
|
|
129
|
+
if (options.stale) {
|
|
130
|
+
emitNoStaleCandidates(options);
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
emitError(options, "No linked worktrees to clean");
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
if (!options.dryRun && !options.force && (options.json || isHeadless())) {
|
|
137
|
+
const message = "--force is required";
|
|
138
|
+
if (options.json) {
|
|
139
|
+
emitError(options, message);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
options.stderr(`gji clean: ${message} in non-interactive mode (GJI_NO_TUI=1)\n`);
|
|
143
|
+
}
|
|
144
|
+
return 1;
|
|
145
|
+
}
|
|
146
|
+
// With --force, or non-interactive dry-runs, skip selection prompt and target all candidates.
|
|
147
|
+
const shouldSelectAll = options.force ||
|
|
148
|
+
(options.dryRun && (options.stale || options.json || isHeadless()));
|
|
149
|
+
const selections = shouldSelectAll
|
|
150
|
+
? activeCandidates.map(({ worktree }) => worktree.path)
|
|
151
|
+
: await promptForWorktrees(await buildWorktreePromptEntries(activeCandidates, {
|
|
152
|
+
metadata: currentScope ? "full" : "fast",
|
|
153
|
+
catalog: runtime.worktreeCatalog,
|
|
154
|
+
}), scope, runtime.worktreeCatalog);
|
|
155
|
+
if (!selections || selections.length === 0) {
|
|
156
|
+
options.stderr("Aborted\n");
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
const selectedCandidates = resolveSelectedCandidates(activeCandidates, selections);
|
|
160
|
+
if (selectedCandidates.length !== selections.length) {
|
|
161
|
+
options.stderr("Selected worktree no longer exists\n");
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
const selectedWorktrees = selectedCandidates.map(({ worktree }) => worktree);
|
|
165
|
+
const selectedWorktreeInfos = await readWorktreeInfos(selectedWorktrees);
|
|
166
|
+
const selectedInfoByPath = new Map(selectedWorktreeInfos.map((info) => [info.path, info]));
|
|
167
|
+
if (!options.dryRun &&
|
|
168
|
+
!options.force &&
|
|
169
|
+
!(await confirmRemoval(selectedWorktrees))) {
|
|
170
|
+
options.stderr("Aborted\n");
|
|
171
|
+
return 1;
|
|
172
|
+
}
|
|
173
|
+
if (options.dryRun) {
|
|
174
|
+
if (options.json) {
|
|
175
|
+
const removed = selectedWorktreeInfos.map((info) => serializeWorktreeInfo(info));
|
|
176
|
+
options.stdout(`${JSON.stringify({ removed, dryRun: true }, null, 2)}\n`);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
for (const info of selectedWorktreeInfos) {
|
|
180
|
+
options.stdout(`Would remove worktree at ${info.path} (${formatCleanInfo(info, formatLastCommit, formatUpstreamState)})\n`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
const candidatesByRepository = groupCandidatesByRepository(selectedCandidates);
|
|
186
|
+
const journals = new Map();
|
|
187
|
+
const failures = [];
|
|
188
|
+
try {
|
|
189
|
+
for (const [repoRoot, candidates] of candidatesByRepository) {
|
|
190
|
+
const journal = await recordUndoOperation("clean", repoRoot, candidates.map(({ worktree }) => worktree), undefined, runtime);
|
|
191
|
+
if (!journal) {
|
|
192
|
+
await discardUndoRecords(journals.values(), runtime.configStore.GLOBAL_CONFIG_DIRECTORY);
|
|
193
|
+
emitError(options, "could not capture undo state; no worktrees were removed");
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
journals.set(repoRoot, journal);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
await discardUndoRecords(journals.values(), runtime.configStore.GLOBAL_CONFIG_DIRECTORY);
|
|
201
|
+
emitError(options, `could not write undo journal; no worktrees were removed: ${toMessage(error)}`);
|
|
202
|
+
return 1;
|
|
203
|
+
}
|
|
204
|
+
const removedCandidates = [];
|
|
205
|
+
for (const candidate of selectedCandidates) {
|
|
206
|
+
const { repoRoot, staleBaseRef, worktree } = candidate;
|
|
207
|
+
if (options.stale &&
|
|
208
|
+
!(await isStaleCleanupCandidate({ repoRoot, worktree, baseBranch: staleBaseRef }, cleanupDependencies))) {
|
|
209
|
+
options.stderr(`Skipped ${worktree.path}: no longer a safe stale cleanup candidate\n`);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
await removeWorktree(repoRoot, worktree.path);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
if (!isWorktreeForceRemovalError(error)) {
|
|
217
|
+
failures.push({
|
|
218
|
+
branch: worktree.branch,
|
|
219
|
+
message: toMessage(error),
|
|
220
|
+
path: worktree.path,
|
|
221
|
+
});
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (options.stale &&
|
|
225
|
+
!isWorktreeDeletionError(error) &&
|
|
226
|
+
!isSubmoduleWorktreeRemovalError(error)) {
|
|
227
|
+
options.stderr(`Skipped ${worktree.path}: no longer a safe stale cleanup candidate\n`);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (!options.force &&
|
|
231
|
+
!(await confirmForceRemoveWorktree(worktree.path))) {
|
|
232
|
+
failures.push({
|
|
233
|
+
branch: worktree.branch,
|
|
234
|
+
message: "force removal declined",
|
|
235
|
+
path: worktree.path,
|
|
236
|
+
});
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
await forceRemoveWorktree(repoRoot, worktree.path);
|
|
241
|
+
}
|
|
242
|
+
catch (forceError) {
|
|
243
|
+
failures.push({
|
|
244
|
+
branch: worktree.branch,
|
|
245
|
+
message: toMessage(forceError),
|
|
246
|
+
path: worktree.path,
|
|
247
|
+
});
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
removedCandidates.push(candidate);
|
|
252
|
+
if (worktree.branch) {
|
|
253
|
+
try {
|
|
254
|
+
await deleteBranch(repoRoot, worktree.branch);
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
if (!isBranchUnmergedError(error)) {
|
|
258
|
+
failures.push({
|
|
259
|
+
branch: worktree.branch,
|
|
260
|
+
message: toMessage(error),
|
|
261
|
+
path: worktree.path,
|
|
262
|
+
});
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (options.force ||
|
|
266
|
+
(await confirmForceDeleteBranch(worktree.branch))) {
|
|
267
|
+
try {
|
|
268
|
+
await forceDeleteBranch(repoRoot, worktree.branch);
|
|
269
|
+
}
|
|
270
|
+
catch (forceError) {
|
|
271
|
+
options.stderr(`Failed to delete branch ${worktree.branch}: ${toMessage(forceError)}\n`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
options.stderr(`Branch ${worktree.branch} was not deleted (has unmerged commits)\n`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
for (const [repoRoot, journal] of journals) {
|
|
281
|
+
await finalizeUndoOperation(journal, removedCandidates
|
|
282
|
+
.filter((candidate) => candidate.repoRoot === repoRoot)
|
|
283
|
+
.map(({ worktree }) => worktree), undefined, runtime.configStore.GLOBAL_CONFIG_DIRECTORY);
|
|
284
|
+
}
|
|
285
|
+
const undoRecords = remainingUndoRecords(journals.values(), removedCandidates);
|
|
286
|
+
await Promise.all(removedCandidates.map(({ worktree }) => releaseWorktreeSlot(worktree.path)));
|
|
287
|
+
if (options.json) {
|
|
288
|
+
const removed = removedCandidates.map(({ worktree }) => {
|
|
289
|
+
const info = selectedInfoByPath.get(worktree.path);
|
|
290
|
+
return info === undefined
|
|
291
|
+
? { branch: worktree.branch, path: worktree.path }
|
|
292
|
+
: serializeWorktreeInfo(info);
|
|
293
|
+
});
|
|
294
|
+
const payload = {
|
|
295
|
+
removed,
|
|
296
|
+
...(failures.length === 0 ? {} : { failed: failures }),
|
|
297
|
+
...(needsExplicitUndoIds(undoRecords, repository.repoRoot)
|
|
298
|
+
? {
|
|
299
|
+
undo: undoRecords.map(({ id, repoRoot }) => ({ id, repoRoot })),
|
|
300
|
+
}
|
|
301
|
+
: {}),
|
|
302
|
+
};
|
|
303
|
+
options.stdout(`${JSON.stringify(payload, null, 2)}\n`);
|
|
304
|
+
}
|
|
305
|
+
else if (failures.length > 0) {
|
|
306
|
+
reportCleanFailures(failures, options.stderr);
|
|
307
|
+
emitUndoHints(undoRecords, repository.repoRoot, options.stderr);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
emitUndoHints(undoRecords, repository.repoRoot, options.stderr);
|
|
311
|
+
options.stdout(`${[...new Set(selectedCandidates.map(({ repoRoot }) => repoRoot))].join("\n")}\n`);
|
|
312
|
+
}
|
|
313
|
+
return failures.length === 0 ? 0 : 1;
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
export const runCleanCommand = createCleanCommand();
|
|
317
|
+
async function resolveCleanupCandidates(options, dependencies) {
|
|
318
|
+
const { sources, stale, stderr, signal } = options;
|
|
319
|
+
const grouped = new Map();
|
|
320
|
+
for (const source of sources) {
|
|
321
|
+
if (source.repoRoot === undefined)
|
|
322
|
+
continue;
|
|
323
|
+
const existing = grouped.get(source.repoRoot);
|
|
324
|
+
if (existing === undefined) {
|
|
325
|
+
grouped.set(source.repoRoot, {
|
|
326
|
+
repoName: source.repoName,
|
|
327
|
+
worktrees: [source.worktree],
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
existing.worktrees.push(source.worktree);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const results = await mapWithConcurrency([...grouped.entries()], MAX_CLEAN_REPOSITORY_CONCURRENCY, async ([repoRoot, group]) => {
|
|
335
|
+
throwIfAborted(signal);
|
|
336
|
+
const staleBaseRef = stale
|
|
337
|
+
? await resolveStaleBaseRef({ repoRoot, stderr }, dependencies)
|
|
338
|
+
: null;
|
|
339
|
+
const worktrees = stale
|
|
340
|
+
? await filterStaleCleanupCandidates({
|
|
341
|
+
repoRoot,
|
|
342
|
+
worktrees: group.worktrees,
|
|
343
|
+
baseBranch: staleBaseRef,
|
|
344
|
+
signal,
|
|
345
|
+
}, dependencies)
|
|
346
|
+
: group.worktrees;
|
|
347
|
+
return worktrees.map((worktree) => ({
|
|
348
|
+
repoName: group.repoName,
|
|
349
|
+
repoRoot,
|
|
350
|
+
staleBaseRef,
|
|
351
|
+
worktree,
|
|
352
|
+
}));
|
|
353
|
+
}, signal);
|
|
354
|
+
return results.flat();
|
|
355
|
+
}
|
|
356
|
+
function resolveSelectedCandidates(candidates, selections) {
|
|
357
|
+
const selected = [];
|
|
358
|
+
const seenPaths = new Set();
|
|
359
|
+
for (const selection of selections) {
|
|
360
|
+
const candidate = candidates.find((entry) => entry.worktree.path === selection ||
|
|
361
|
+
entry.worktree.branch === selection);
|
|
362
|
+
if (candidate === undefined || seenPaths.has(candidate.worktree.path)) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
selected.push(candidate);
|
|
366
|
+
seenPaths.add(candidate.worktree.path);
|
|
367
|
+
}
|
|
368
|
+
return selected;
|
|
369
|
+
}
|
|
370
|
+
function groupCandidatesByRepository(candidates) {
|
|
371
|
+
const grouped = new Map();
|
|
372
|
+
for (const candidate of candidates) {
|
|
373
|
+
const existing = grouped.get(candidate.repoRoot);
|
|
374
|
+
if (existing === undefined)
|
|
375
|
+
grouped.set(candidate.repoRoot, [candidate]);
|
|
376
|
+
else
|
|
377
|
+
existing.push(candidate);
|
|
378
|
+
}
|
|
379
|
+
return grouped;
|
|
380
|
+
}
|
|
381
|
+
function remainingUndoRecords(records, removedCandidates) {
|
|
382
|
+
return [...records].filter((record) => removedCandidates.some((candidate) => candidate.repoRoot === record.repoRoot &&
|
|
383
|
+
record.entries.some((entry) => entry.path === candidate.worktree.path)));
|
|
384
|
+
}
|
|
385
|
+
async function discardUndoRecords(records, globalConfigDirectory) {
|
|
386
|
+
await Promise.all([...records].map((record) => finalizeUndoOperation(record, [], undefined, globalConfigDirectory)));
|
|
387
|
+
}
|
|
388
|
+
async function filterStaleCleanupCandidates(options, dependencies) {
|
|
389
|
+
const { repoRoot, worktrees, baseBranch, signal } = options;
|
|
390
|
+
throwIfAborted(signal);
|
|
391
|
+
if (baseBranch === null) {
|
|
392
|
+
return [];
|
|
393
|
+
}
|
|
394
|
+
const results = await mapWithConcurrency(worktrees, MAX_CLEAN_WORKTREE_CONCURRENCY, (worktree) => isStaleCleanupCandidate({ repoRoot, worktree, baseBranch }, dependencies), signal);
|
|
395
|
+
throwIfAborted(signal);
|
|
396
|
+
return worktrees.filter((_, index) => results[index]);
|
|
397
|
+
}
|
|
398
|
+
async function resolveStaleBaseRef(options, dependencies) {
|
|
399
|
+
const { repoRoot, stderr } = options;
|
|
400
|
+
const config = await dependencies.loadEffectiveConfig(repoRoot, undefined, stderr);
|
|
401
|
+
const remote = resolveConfiguredString(config.syncRemote) ?? "origin";
|
|
402
|
+
const configuredDefaultBranch = resolveConfiguredString(config.syncDefaultBranch);
|
|
403
|
+
try {
|
|
404
|
+
const remoteBase = await dependencies.resolveRemoteBase(repoRoot, remote, configuredDefaultBranch ?? undefined);
|
|
405
|
+
return remoteBase === null
|
|
406
|
+
? null
|
|
407
|
+
: resolveFetchedRemoteRef({ branch: remoteBase.branch, remote, repoRoot }, dependencies.runGit);
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function resolveFetchedRemoteRef(options, runGit) {
|
|
414
|
+
const { repoRoot, remote, branch } = options;
|
|
415
|
+
try {
|
|
416
|
+
await runGit(repoRoot, ["fetch", "--prune", remote]);
|
|
417
|
+
await runGit(repoRoot, [
|
|
418
|
+
"rev-parse",
|
|
419
|
+
"--verify",
|
|
420
|
+
"--quiet",
|
|
421
|
+
`${remote}/${branch}`,
|
|
422
|
+
]);
|
|
423
|
+
return `${remote}/${branch}`;
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function resolveConfiguredString(value) {
|
|
430
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
431
|
+
}
|
|
432
|
+
async function isStaleCleanupCandidate(options, dependencies) {
|
|
433
|
+
const { repoRoot, worktree, baseBranch } = options;
|
|
434
|
+
if (baseBranch === null) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
if (worktree.branch === null) {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
const health = await dependencies.readWorktreeHealth(worktree.path);
|
|
441
|
+
if (health.status !== "clean" || !health.upstreamGone) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
if (await dependencies.isBranchMergedInto(repoRoot, worktree.branch, baseBranch)) {
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
return isBranchPatchEquivalentInto({ baseBranch, repoRoot, worktreeBranch: worktree.branch }, dependencies.runGit);
|
|
448
|
+
}
|
|
449
|
+
async function isBranchPatchEquivalentInto(options, runGit) {
|
|
450
|
+
const { repoRoot, worktreeBranch, baseBranch } = options;
|
|
451
|
+
try {
|
|
452
|
+
const output = await runGit(repoRoot, [
|
|
453
|
+
"cherry",
|
|
454
|
+
baseBranch,
|
|
455
|
+
worktreeBranch,
|
|
456
|
+
]);
|
|
457
|
+
const lines = output.split("\n").filter(Boolean);
|
|
458
|
+
return lines.length > 0 && lines.every((line) => line.startsWith("-"));
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function reportCleanFailures(failures, stderr) {
|
|
465
|
+
const noun = failures.length === 1 ? "worktree" : "worktrees";
|
|
466
|
+
stderr(`Failed to clean ${failures.length} ${noun}:\n`);
|
|
467
|
+
for (const failure of failures) {
|
|
468
|
+
const branch = failure.branch === null ? "detached" : failure.branch;
|
|
469
|
+
stderr(`- ${failure.path} (${branch}): ${failure.message}\n`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function emitUndoHints(records, currentRepositoryRoot, stderr) {
|
|
473
|
+
if (records.length === 0)
|
|
474
|
+
return;
|
|
475
|
+
if (!needsExplicitUndoIds(records, currentRepositoryRoot)) {
|
|
476
|
+
stderr("undo: gji undo\n");
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const message = records.length === 1
|
|
480
|
+
? "undo: restore with:"
|
|
481
|
+
: "undo: restore each repository with:";
|
|
482
|
+
stderr(`${message}\n`);
|
|
483
|
+
for (const record of records) {
|
|
484
|
+
stderr(` gji undo --id ${record.id} (${record.repoRoot})\n`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function needsExplicitUndoIds(records, currentRepositoryRoot) {
|
|
488
|
+
return (records.length > 1 ||
|
|
489
|
+
records.some((record) => record.repoRoot !== currentRepositoryRoot));
|
|
490
|
+
}
|
|
491
|
+
function reportSkippedRepositories(repositories, stderr) {
|
|
492
|
+
const noun = repositories.length === 1 ? "repository" : "repositories";
|
|
493
|
+
stderr(`Skipped ${repositories.length} registered ${noun} because worktrees could not be listed:\n`);
|
|
494
|
+
for (const repository of repositories) {
|
|
495
|
+
stderr(`- ${repository.name} (${repository.path})\n`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
function throwIfAborted(signal) {
|
|
499
|
+
if (signal?.aborted)
|
|
500
|
+
throw new Error("Operation cancelled");
|
|
501
|
+
}
|
|
502
|
+
function formatCleanInfo(info, formatLastCommit, formatUpstreamState) {
|
|
503
|
+
const branch = info.branch === null ? "detached" : `branch: ${info.branch}`;
|
|
504
|
+
const status = `status: ${info.status}`;
|
|
505
|
+
const upstream = `upstream: ${formatUpstreamState(info.upstream)}`;
|
|
506
|
+
const last = `last: ${formatLastCommit(info.lastCommitTimestamp)}`;
|
|
507
|
+
return [branch, status, upstream, last].join(", ");
|
|
508
|
+
}
|
|
509
|
+
function emitError(options, message) {
|
|
510
|
+
if (options.json) {
|
|
511
|
+
options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
options.stderr(`${message}\n`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function emitNoStaleCandidates(options) {
|
|
518
|
+
if (options.json) {
|
|
519
|
+
const payload = options.dryRun
|
|
520
|
+
? { removed: [], dryRun: true }
|
|
521
|
+
: { removed: [] };
|
|
522
|
+
options.stdout(`${JSON.stringify(payload, null, 2)}\n`);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
options.stdout("No stale linked worktrees to clean\n");
|
|
526
|
+
}
|
|
527
|
+
function toMessage(error) {
|
|
528
|
+
return error instanceof Error ? error.message : String(error);
|
|
529
|
+
}
|
|
530
|
+
async function defaultPromptForWorktrees(worktrees, scope, catalog) {
|
|
531
|
+
return promptForMultipleWorktrees("Choose worktrees to clean", worktrees, {
|
|
532
|
+
catalog,
|
|
533
|
+
scope,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
async function defaultConfirmRemoval(worktrees) {
|
|
537
|
+
const branchCount = worktrees.filter((worktree) => worktree.branch !== null).length;
|
|
538
|
+
const detachedCount = worktrees.length - branchCount;
|
|
539
|
+
const messageParts = [
|
|
540
|
+
`Remove ${worktrees.length} linked worktree${worktrees.length === 1 ? "" : "s"}`,
|
|
541
|
+
];
|
|
542
|
+
if (branchCount > 0) {
|
|
543
|
+
messageParts.push(`delete ${branchCount} branch${branchCount === 1 ? "" : "es"}`);
|
|
544
|
+
}
|
|
545
|
+
if (detachedCount > 0) {
|
|
546
|
+
messageParts.push(`remove ${detachedCount} detached worktree${detachedCount === 1 ? "" : "s"}`);
|
|
547
|
+
}
|
|
548
|
+
const choice = await confirm({
|
|
549
|
+
active: "Yes",
|
|
550
|
+
inactive: "No",
|
|
551
|
+
initialValue: true,
|
|
552
|
+
message: `${messageParts.join(", ")}?`,
|
|
553
|
+
});
|
|
554
|
+
return !isCancel(choice) && choice;
|
|
555
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { renderShellCompletion } from "../../presentation/shell/completion.js";
|
|
2
|
+
import { resolveSupportedShell } from "../../presentation/shell/shell.js";
|
|
3
3
|
export async function runCompletionCommand(options) {
|
|
4
4
|
const shell = options.shell
|
|
5
5
|
? resolveSupportedShell(options.shell, undefined)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CliRuntime } from "../dependencies.js";
|
|
1
2
|
export interface ConfigCommandOptions {
|
|
2
3
|
action?: string;
|
|
3
4
|
cwd: string;
|
|
@@ -5,5 +6,6 @@ export interface ConfigCommandOptions {
|
|
|
5
6
|
stderr?: (chunk: string) => void;
|
|
6
7
|
stdout: (chunk: string) => void;
|
|
7
8
|
value?: string;
|
|
9
|
+
runtime?: CliRuntime<"configStore">;
|
|
8
10
|
}
|
|
9
11
|
export declare function runConfigCommand(options: ConfigCommandOptions): Promise<number>;
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defaultCliDependencies } from "../dependencies.js";
|
|
2
2
|
export async function runConfigCommand(options) {
|
|
3
|
+
const runtime = options.runtime ?? defaultCliDependencies;
|
|
4
|
+
const config = runtime.configStore;
|
|
3
5
|
switch (options.action) {
|
|
4
6
|
case undefined: {
|
|
5
|
-
const loaded = await loadGlobalConfig();
|
|
7
|
+
const loaded = await config.loadGlobalConfig();
|
|
6
8
|
writeJson(options.stdout, loaded.config);
|
|
7
9
|
return 0;
|
|
8
10
|
}
|
|
9
11
|
case "get": {
|
|
10
|
-
const loaded = await loadGlobalConfig();
|
|
12
|
+
const loaded = await config.loadGlobalConfig();
|
|
11
13
|
writeJson(options.stdout, options.key ? loaded.config[options.key] : loaded.config);
|
|
12
14
|
return 0;
|
|
13
15
|
}
|
|
14
16
|
case "set":
|
|
15
17
|
if (options.key && options.value !== undefined) {
|
|
16
18
|
try {
|
|
17
|
-
await updateGlobalConfigKey(options.key, parseConfigValue(options.value));
|
|
19
|
+
await config.updateGlobalConfigKey(options.key, config.parseConfigValue(options.value));
|
|
18
20
|
}
|
|
19
21
|
catch (error) {
|
|
20
22
|
options.stderr?.(`gji config: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -25,7 +27,7 @@ export async function runConfigCommand(options) {
|
|
|
25
27
|
break;
|
|
26
28
|
case "unset":
|
|
27
29
|
if (options.key) {
|
|
28
|
-
await unsetGlobalConfigKey(options.key);
|
|
30
|
+
await config.unsetGlobalConfigKey(options.key);
|
|
29
31
|
return 0;
|
|
30
32
|
}
|
|
31
33
|
break;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CliRuntime } from "../dependencies.js";
|
|
1
2
|
export type DoctorCheckStatus = "fail" | "ok" | "skip";
|
|
2
3
|
export interface DoctorCheck {
|
|
3
4
|
hint?: string;
|
|
@@ -22,6 +23,7 @@ export interface DoctorCommandOptions {
|
|
|
22
23
|
confirmFixes?: (fixes: DoctorFix[]) => Promise<boolean>;
|
|
23
24
|
shell?: string;
|
|
24
25
|
yes?: boolean;
|
|
26
|
+
runtime?: CliRuntime<"integrations" | "slots" | "repositoryContext" | "configStore" | "registry">;
|
|
25
27
|
stderr?: (chunk: string) => void;
|
|
26
28
|
stdout: (chunk: string) => void;
|
|
27
29
|
}
|