@solaqua/gji 0.6.2 → 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 +1624 -819
- 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 +14 -14
- 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 +37 -33
- 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/README.md
CHANGED
|
@@ -249,6 +249,7 @@ path=$(gji root --print)
|
|
|
249
249
|
| `gji status [--json]` | repo overview, worktree health, ahead/behind |
|
|
250
250
|
| `gji ls [--compact] [--json]` | list active worktrees |
|
|
251
251
|
| `gji sync [--all]` | fetch and rebase worktrees onto default branch |
|
|
252
|
+
| `gji sync-files [list\|add\|remove] [paths...]` | manage local files copied into new worktrees |
|
|
252
253
|
| `gji clean [--stale] [--force] [--json]` | interactively prune linked worktrees |
|
|
253
254
|
| `gji remove [branch] [--force] [--json]` | remove a worktree and its branch |
|
|
254
255
|
| `gji trigger-hook <hook>` | run a hook in the current worktree |
|
|
@@ -272,7 +273,7 @@ No setup required. Optional config lives in:
|
|
|
272
273
|
| `worktreePath` | base directory for new worktrees (absolute or `~/…`); overrides the default `../worktrees/<repo>/` layout |
|
|
273
274
|
| `syncRemote` | remote for `gji sync` (default: `origin`) |
|
|
274
275
|
| `syncDefaultBranch` | branch to rebase onto (default: remote `HEAD`) |
|
|
275
|
-
| `syncFiles` | files to copy from main worktree into each new worktree |
|
|
276
|
+
| `syncFiles` | files to copy from main worktree into each new worktree; use global per-repo config for private files |
|
|
276
277
|
| `skipInstallPrompt` | `true` to disable the auto-install prompt permanently |
|
|
277
278
|
| `installSaveTarget` | `"local"` or `"global"` — where **Always**/**Never** choices are persisted (default: `"local"`); set once during `gji init --write` |
|
|
278
279
|
| `hooks` | lifecycle scripts (see [Hooks](#hooks)) |
|
|
@@ -287,6 +288,30 @@ No setup required. Optional config lives in:
|
|
|
287
288
|
}
|
|
288
289
|
```
|
|
289
290
|
|
|
291
|
+
### Syncing local files
|
|
292
|
+
|
|
293
|
+
Use `syncFiles` for private, gitignored, or machine-local files that every new worktree needs, such as `.env.local` or `.npmrc`. `gji new` copies these files from the main worktree before install hooks run, skips missing source files, and does not overwrite files that already exist in the target worktree.
|
|
294
|
+
|
|
295
|
+
For private files, prefer the `sync-files` command. It writes to your global per-repo config so secret filenames do not need to be committed to `.gji.json`:
|
|
296
|
+
|
|
297
|
+
```sh
|
|
298
|
+
gji sync-files add .env.local .npmrc
|
|
299
|
+
gji sync-files list
|
|
300
|
+
gji sync-files remove .npmrc
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
This stores:
|
|
304
|
+
|
|
305
|
+
```json
|
|
306
|
+
{
|
|
307
|
+
"repos": {
|
|
308
|
+
"/home/me/code/my-repo": {
|
|
309
|
+
"syncFiles": [".env.local"]
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
290
315
|
### Per-repo overrides in global config
|
|
291
316
|
|
|
292
317
|
If you work across many repositories, you can scope config to a specific repo inside `~/.config/gji/config.json` without adding a `.gji.json` to that repo:
|
package/dist/back.d.ts
CHANGED
package/dist/back.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { access } from
|
|
2
|
-
import { basename } from
|
|
3
|
-
import { loadEffectiveConfig } from
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { detectRepository } from
|
|
7
|
-
import { writeShellOutput } from
|
|
8
|
-
export const BACK_OUTPUT_FILE_ENV =
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { loadEffectiveConfig } from "./config.js";
|
|
4
|
+
import { appendHistory, loadHistory } from "./history.js";
|
|
5
|
+
import { extractHooks, runHook } from "./hooks.js";
|
|
6
|
+
import { detectRepository } from "./repo.js";
|
|
7
|
+
import { writeShellOutput } from "./shell-handoff.js";
|
|
8
|
+
export const BACK_OUTPUT_FILE_ENV = "GJI_BACK_OUTPUT_FILE";
|
|
9
9
|
export async function runBackCommand(options) {
|
|
10
10
|
const history = await loadHistory(options.home);
|
|
11
11
|
const steps = options.n ?? 1;
|
|
12
12
|
if (steps < 1) {
|
|
13
|
-
options.stderr(
|
|
13
|
+
options.stderr("gji back: step count must be at least 1\n");
|
|
14
14
|
return 1;
|
|
15
15
|
}
|
|
16
16
|
let found = 0;
|
|
@@ -31,7 +31,7 @@ export async function runBackCommand(options) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
if (!target) {
|
|
34
|
-
options.stderr(
|
|
34
|
+
options.stderr("gji back: no previous worktree in history\n");
|
|
35
35
|
options.stderr("Hint: Use 'gji go', 'gji new', or 'gji pr' to navigate between worktrees\n");
|
|
36
36
|
return 1;
|
|
37
37
|
}
|
|
@@ -39,7 +39,11 @@ 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.afterEnter, target.path, {
|
|
42
|
+
await runHook(hooks.afterEnter, target.path, {
|
|
43
|
+
branch: target.branch ?? undefined,
|
|
44
|
+
path: target.path,
|
|
45
|
+
repo: basename(repository.repoRoot),
|
|
46
|
+
}, options.stderr);
|
|
43
47
|
}
|
|
44
48
|
catch {
|
|
45
49
|
// Not in a git repo or hooks unavailable — proceed without hook
|
|
@@ -49,20 +53,22 @@ export async function runBackCommand(options) {
|
|
|
49
53
|
return 0;
|
|
50
54
|
}
|
|
51
55
|
export function formatHistoryList(history, cwd) {
|
|
52
|
-
const branchWidth = Math.max(
|
|
53
|
-
const lines = [
|
|
56
|
+
const branchWidth = Math.max("BRANCH".length, ...history.map((e) => (e.branch ?? "(detached)").length));
|
|
57
|
+
const lines = [
|
|
58
|
+
" " + "BRANCH".padEnd(branchWidth) + " WHEN PATH",
|
|
59
|
+
];
|
|
54
60
|
for (const entry of history) {
|
|
55
61
|
const isCurrent = entry.path === cwd;
|
|
56
|
-
const branch = (entry.branch ??
|
|
62
|
+
const branch = (entry.branch ?? "(detached)").padEnd(branchWidth);
|
|
57
63
|
const when = formatAge(entry.timestamp).padEnd(10);
|
|
58
|
-
lines.push(`${isCurrent ?
|
|
64
|
+
lines.push(`${isCurrent ? "*" : " "} ${branch} ${when} ${entry.path}`);
|
|
59
65
|
}
|
|
60
|
-
return lines.join(
|
|
66
|
+
return lines.join("\n") + "\n";
|
|
61
67
|
}
|
|
62
68
|
export function formatAge(timestamp) {
|
|
63
69
|
const seconds = Math.floor((Date.now() - timestamp) / 1000);
|
|
64
70
|
if (seconds < 60)
|
|
65
|
-
return
|
|
71
|
+
return "just now";
|
|
66
72
|
const minutes = Math.floor(seconds / 60);
|
|
67
73
|
if (minutes < 60)
|
|
68
74
|
return `${minutes}m ago`;
|
package/dist/clean.d.ts
CHANGED
package/dist/clean.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { confirm, isCancel, multiselect } from
|
|
2
|
-
import { loadEffectiveConfig } from
|
|
3
|
-
import { isBranchMergedInto, readWorktreeHealth, resolveRemoteDefaultBranch, runGit } from
|
|
4
|
-
import { isHeadless } from
|
|
5
|
-
import { formatLastCommit, formatUpstreamState, formatWorktreeHint, readWorktreeInfos, serializeWorktreeInfo, } from
|
|
6
|
-
import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isWorktreeDirtyError, loadLinkedWorktrees, removeWorktree, } from
|
|
7
|
-
import { defaultConfirmForceDeleteBranch, defaultConfirmForceRemoveWorktree } from
|
|
1
|
+
import { confirm, isCancel, multiselect } from "@clack/prompts";
|
|
2
|
+
import { loadEffectiveConfig } from "./config.js";
|
|
3
|
+
import { isBranchMergedInto, readWorktreeHealth, resolveRemoteDefaultBranch, runGit, } from "./git.js";
|
|
4
|
+
import { isHeadless } from "./headless.js";
|
|
5
|
+
import { formatLastCommit, formatUpstreamState, formatWorktreeHint, readWorktreeInfos, serializeWorktreeInfo, } from "./worktree-info.js";
|
|
6
|
+
import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isWorktreeDirtyError, loadLinkedWorktrees, removeWorktree, } from "./worktree-management.js";
|
|
7
|
+
import { defaultConfirmForceDeleteBranch, defaultConfirmForceRemoveWorktree, } from "./worktree-prompts.js";
|
|
8
8
|
export function createCleanCommand(dependencies = {}) {
|
|
9
9
|
const promptForWorktrees = dependencies.promptForWorktrees ?? defaultPromptForWorktrees;
|
|
10
10
|
const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval;
|
|
11
|
-
const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ??
|
|
11
|
+
const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ??
|
|
12
|
+
defaultConfirmForceRemoveWorktree;
|
|
12
13
|
const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
|
|
13
14
|
return async function runCleanCommand(options) {
|
|
14
15
|
const { linkedWorktrees, repository } = await loadLinkedWorktrees(options.cwd);
|
|
@@ -24,11 +25,11 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
24
25
|
emitNoStaleCandidates(options);
|
|
25
26
|
return 0;
|
|
26
27
|
}
|
|
27
|
-
emitError(options,
|
|
28
|
+
emitError(options, "No linked worktrees to clean");
|
|
28
29
|
return 1;
|
|
29
30
|
}
|
|
30
31
|
if (!options.dryRun && !options.force && (options.json || isHeadless())) {
|
|
31
|
-
const message =
|
|
32
|
+
const message = "--force is required";
|
|
32
33
|
if (options.json) {
|
|
33
34
|
emitError(options, message);
|
|
34
35
|
}
|
|
@@ -38,23 +39,26 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
38
39
|
return 1;
|
|
39
40
|
}
|
|
40
41
|
// With --force, or non-interactive dry-runs, skip selection prompt and target all candidates.
|
|
41
|
-
const shouldSelectAll = options.force ||
|
|
42
|
+
const shouldSelectAll = options.force ||
|
|
43
|
+
(options.dryRun && (options.stale || options.json || isHeadless()));
|
|
42
44
|
const selections = shouldSelectAll
|
|
43
45
|
? cleanupCandidates.map((w) => w.path)
|
|
44
46
|
: await promptForWorktrees(cleanupCandidates);
|
|
45
47
|
if (!selections || selections.length === 0) {
|
|
46
|
-
options.stderr(
|
|
48
|
+
options.stderr("Aborted\n");
|
|
47
49
|
return 1;
|
|
48
50
|
}
|
|
49
51
|
const selectedWorktrees = resolveSelectedWorktrees(cleanupCandidates, selections);
|
|
50
52
|
if (selectedWorktrees.length !== selections.length) {
|
|
51
|
-
options.stderr(
|
|
53
|
+
options.stderr("Selected worktree no longer exists\n");
|
|
52
54
|
return 1;
|
|
53
55
|
}
|
|
54
56
|
const selectedWorktreeInfos = await readWorktreeInfos(selectedWorktrees);
|
|
55
57
|
const selectedInfoByPath = new Map(selectedWorktreeInfos.map((info) => [info.path, info]));
|
|
56
|
-
if (!options.dryRun &&
|
|
57
|
-
options.
|
|
58
|
+
if (!options.dryRun &&
|
|
59
|
+
!options.force &&
|
|
60
|
+
!(await confirmRemoval(selectedWorktrees))) {
|
|
61
|
+
options.stderr("Aborted\n");
|
|
58
62
|
return 1;
|
|
59
63
|
}
|
|
60
64
|
if (options.dryRun) {
|
|
@@ -72,7 +76,8 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
72
76
|
const removedPaths = [];
|
|
73
77
|
const removedWorktrees = [];
|
|
74
78
|
for (const worktree of selectedWorktrees) {
|
|
75
|
-
if (options.stale &&
|
|
79
|
+
if (options.stale &&
|
|
80
|
+
!(await isStaleCleanupCandidate(repository.repoRoot, worktree, staleBaseRef))) {
|
|
76
81
|
options.stderr(`Skipped ${worktree.path}: no longer a safe stale cleanup candidate\n`);
|
|
77
82
|
continue;
|
|
78
83
|
}
|
|
@@ -87,9 +92,10 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
87
92
|
options.stderr(`Skipped ${worktree.path}: no longer a safe stale cleanup candidate\n`);
|
|
88
93
|
continue;
|
|
89
94
|
}
|
|
90
|
-
if (!options.force &&
|
|
95
|
+
if (!options.force &&
|
|
96
|
+
!(await confirmForceRemoveWorktree(worktree.path))) {
|
|
91
97
|
reportRemovedPaths(removedPaths, options.stderr);
|
|
92
|
-
options.stderr(
|
|
98
|
+
options.stderr("Aborted\n");
|
|
93
99
|
return 1;
|
|
94
100
|
}
|
|
95
101
|
try {
|
|
@@ -113,7 +119,8 @@ export function createCleanCommand(dependencies = {}) {
|
|
|
113
119
|
if (!isBranchUnmergedError(error)) {
|
|
114
120
|
throw error;
|
|
115
121
|
}
|
|
116
|
-
if (options.force ||
|
|
122
|
+
if (options.force ||
|
|
123
|
+
(await confirmForceDeleteBranch(worktree.branch))) {
|
|
117
124
|
try {
|
|
118
125
|
await forceDeleteBranch(repository.repoRoot, worktree.branch);
|
|
119
126
|
}
|
|
@@ -152,7 +159,7 @@ async function filterStaleCleanupCandidates(repoRoot, worktrees, baseBranch) {
|
|
|
152
159
|
}
|
|
153
160
|
async function resolveStaleBaseRef(repoRoot, stderr) {
|
|
154
161
|
const config = await loadEffectiveConfig(repoRoot, undefined, stderr);
|
|
155
|
-
const remote = resolveConfiguredString(config.syncRemote) ??
|
|
162
|
+
const remote = resolveConfiguredString(config.syncRemote) ?? "origin";
|
|
156
163
|
const configuredDefaultBranch = resolveConfiguredString(config.syncDefaultBranch);
|
|
157
164
|
if (configuredDefaultBranch) {
|
|
158
165
|
return await resolveFetchedRemoteRef(repoRoot, remote, configuredDefaultBranch);
|
|
@@ -169,7 +176,7 @@ async function resolveStaleBaseRef(repoRoot, stderr) {
|
|
|
169
176
|
}
|
|
170
177
|
async function resolveFetchedRemoteRef(repoRoot, remote, branch) {
|
|
171
178
|
try {
|
|
172
|
-
await runGit(repoRoot, [
|
|
179
|
+
await runGit(repoRoot, ["fetch", "--prune", remote]);
|
|
173
180
|
return `${remote}/${branch}`;
|
|
174
181
|
}
|
|
175
182
|
catch {
|
|
@@ -177,7 +184,7 @@ async function resolveFetchedRemoteRef(repoRoot, remote, branch) {
|
|
|
177
184
|
}
|
|
178
185
|
}
|
|
179
186
|
function resolveConfiguredString(value) {
|
|
180
|
-
return typeof value ===
|
|
187
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
181
188
|
}
|
|
182
189
|
async function isStaleCleanupCandidate(repoRoot, worktree, baseBranch) {
|
|
183
190
|
if (baseBranch === null) {
|
|
@@ -187,7 +194,7 @@ async function isStaleCleanupCandidate(repoRoot, worktree, baseBranch) {
|
|
|
187
194
|
return false;
|
|
188
195
|
}
|
|
189
196
|
const health = await readWorktreeHealth(worktree.path);
|
|
190
|
-
if (health.status !==
|
|
197
|
+
if (health.status !== "clean" || !health.upstreamGone) {
|
|
191
198
|
return false;
|
|
192
199
|
}
|
|
193
200
|
return isBranchMergedInto(repoRoot, worktree.branch, baseBranch);
|
|
@@ -207,15 +214,15 @@ function resolveSelectedWorktrees(worktrees, selections) {
|
|
|
207
214
|
}
|
|
208
215
|
function reportRemovedPaths(paths, stderr) {
|
|
209
216
|
if (paths.length > 0) {
|
|
210
|
-
stderr(`Already removed: ${paths.join(
|
|
217
|
+
stderr(`Already removed: ${paths.join(", ")}\n`);
|
|
211
218
|
}
|
|
212
219
|
}
|
|
213
220
|
function formatCleanInfo(info) {
|
|
214
|
-
const branch = info.branch === null ?
|
|
221
|
+
const branch = info.branch === null ? "detached" : `branch: ${info.branch}`;
|
|
215
222
|
const status = `status: ${info.status}`;
|
|
216
223
|
const upstream = `upstream: ${formatUpstreamState(info.upstream)}`;
|
|
217
224
|
const last = `last: ${formatLastCommit(info.lastCommitTimestamp)}`;
|
|
218
|
-
return [branch, status, upstream, last].join(
|
|
225
|
+
return [branch, status, upstream, last].join(", ");
|
|
219
226
|
}
|
|
220
227
|
function emitError(options, message) {
|
|
221
228
|
if (options.json) {
|
|
@@ -233,7 +240,7 @@ function emitNoStaleCandidates(options) {
|
|
|
233
240
|
options.stdout(`${JSON.stringify(payload, null, 2)}\n`);
|
|
234
241
|
return;
|
|
235
242
|
}
|
|
236
|
-
options.stdout(
|
|
243
|
+
options.stdout("No stale linked worktrees to clean\n");
|
|
237
244
|
}
|
|
238
245
|
function toMessage(error) {
|
|
239
246
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -241,11 +248,11 @@ function toMessage(error) {
|
|
|
241
248
|
async function defaultPromptForWorktrees(worktrees) {
|
|
242
249
|
const infos = await readWorktreeInfos(worktrees);
|
|
243
250
|
const choice = await multiselect({
|
|
244
|
-
message:
|
|
251
|
+
message: "Choose worktrees to clean",
|
|
245
252
|
options: worktrees.map((worktree, i) => {
|
|
246
253
|
return {
|
|
247
254
|
hint: formatWorktreeHint(infos[i]),
|
|
248
|
-
label: worktree.branch ??
|
|
255
|
+
label: worktree.branch ?? "(detached)",
|
|
249
256
|
value: worktree.path,
|
|
250
257
|
};
|
|
251
258
|
}),
|
|
@@ -256,18 +263,20 @@ async function defaultPromptForWorktrees(worktrees) {
|
|
|
256
263
|
async function defaultConfirmRemoval(worktrees) {
|
|
257
264
|
const branchCount = worktrees.filter((worktree) => worktree.branch !== null).length;
|
|
258
265
|
const detachedCount = worktrees.length - branchCount;
|
|
259
|
-
const messageParts = [
|
|
266
|
+
const messageParts = [
|
|
267
|
+
`Remove ${worktrees.length} linked worktree${worktrees.length === 1 ? "" : "s"}`,
|
|
268
|
+
];
|
|
260
269
|
if (branchCount > 0) {
|
|
261
|
-
messageParts.push(`delete ${branchCount} branch${branchCount === 1 ?
|
|
270
|
+
messageParts.push(`delete ${branchCount} branch${branchCount === 1 ? "" : "es"}`);
|
|
262
271
|
}
|
|
263
272
|
if (detachedCount > 0) {
|
|
264
|
-
messageParts.push(`remove ${detachedCount} detached worktree${detachedCount === 1 ?
|
|
273
|
+
messageParts.push(`remove ${detachedCount} detached worktree${detachedCount === 1 ? "" : "s"}`);
|
|
265
274
|
}
|
|
266
275
|
const choice = await confirm({
|
|
267
|
-
active:
|
|
268
|
-
inactive:
|
|
276
|
+
active: "Yes",
|
|
277
|
+
inactive: "No",
|
|
269
278
|
initialValue: true,
|
|
270
|
-
message: `${messageParts.join(
|
|
279
|
+
message: `${messageParts.join(", ")}?`,
|
|
271
280
|
});
|
|
272
281
|
return !isCancel(choice) && choice;
|
|
273
282
|
}
|
package/dist/cli.d.ts
CHANGED