@akiojin/gwt 4.7.0 → 4.9.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.ja.md +1 -1
- package/README.md +1 -1
- package/dist/claude.js +1 -1
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.js +1 -1
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +8 -7
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/LogDatePickerScreen.js +1 -1
- package/dist/cli/ui/components/screens/LogDatePickerScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/LogDetailScreen.js +1 -1
- package/dist/cli/ui/components/screens/LogDetailScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/LogListScreen.js +1 -1
- package/dist/cli/ui/components/screens/LogListScreen.js.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.d.ts +5 -0
- package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.js +18 -5
- package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +0 -1
- package/dist/codex.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +3 -7
- package/dist/config/index.js.map +1 -1
- package/dist/config/profiles.d.ts +2 -2
- package/dist/config/profiles.d.ts.map +1 -1
- package/dist/config/profiles.js +4 -7
- package/dist/config/profiles.js.map +1 -1
- package/dist/config/tools.d.ts +1 -1
- package/dist/config/tools.d.ts.map +1 -1
- package/dist/config/tools.js +3 -43
- package/dist/config/tools.js.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +1 -2
- package/dist/gemini.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +106 -90
- package/dist/index.js.map +1 -1
- package/dist/utils/command.d.ts +11 -0
- package/dist/utils/command.d.ts.map +1 -1
- package/dist/utils/command.js +33 -0
- package/dist/utils/command.js.map +1 -1
- package/dist/web/client/src/pages/ConfigPage.js +1 -1
- package/dist/web/client/src/pages/ConfigPage.js.map +1 -1
- package/package.json +2 -2
- package/src/claude.ts +1 -1
- package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +1 -1
- package/src/cli/ui/__tests__/components/App.test.tsx +65 -3
- package/src/cli/ui/__tests__/components/screens/LogDetailScreen.test.tsx +1 -1
- package/src/cli/ui/__tests__/components/screens/LogListScreen.test.tsx +1 -1
- package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +83 -22
- package/src/cli/ui/__tests__/integration/navigation.test.tsx +57 -37
- package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +105 -0
- package/src/cli/ui/components/App.tsx +1 -1
- package/src/cli/ui/components/screens/BranchListScreen.tsx +9 -7
- package/src/cli/ui/components/screens/LogDatePickerScreen.tsx +1 -1
- package/src/cli/ui/components/screens/LogDetailScreen.tsx +1 -1
- package/src/cli/ui/components/screens/LogListScreen.tsx +1 -1
- package/src/cli/ui/utils/branchFormatter.ts +19 -5
- package/src/codex.ts +0 -1
- package/src/config/index.ts +3 -7
- package/src/config/profiles.ts +4 -7
- package/src/config/tools.ts +3 -56
- package/src/gemini.ts +1 -2
- package/src/index.ts +148 -133
- package/src/utils/command.ts +37 -0
- package/src/web/client/src/pages/ConfigPage.tsx +2 -2
- package/src/index.ts.backup +0 -1543
package/src/index.ts.backup
DELETED
|
@@ -1,1543 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { confirm } from "@inquirer/prompts";
|
|
5
|
-
import {
|
|
6
|
-
isGitRepository,
|
|
7
|
-
getAllBranches,
|
|
8
|
-
createBranch,
|
|
9
|
-
branchExists,
|
|
10
|
-
getRepositoryRoot,
|
|
11
|
-
deleteBranch,
|
|
12
|
-
deleteRemoteBranch,
|
|
13
|
-
hasUncommittedChanges,
|
|
14
|
-
showStatus,
|
|
15
|
-
stashChanges,
|
|
16
|
-
discardAllChanges,
|
|
17
|
-
commitChanges,
|
|
18
|
-
fetchAllRemotes,
|
|
19
|
-
pushBranchToRemote,
|
|
20
|
-
isInWorktree,
|
|
21
|
-
GitError,
|
|
22
|
-
getCurrentVersion,
|
|
23
|
-
calculateNewVersion,
|
|
24
|
-
executeNpmVersionInWorktree,
|
|
25
|
-
getCurrentBranchName,
|
|
26
|
-
} from "./git.js";
|
|
27
|
-
import {
|
|
28
|
-
listAdditionalWorktrees,
|
|
29
|
-
worktreeExists,
|
|
30
|
-
generateWorktreePath,
|
|
31
|
-
createWorktree,
|
|
32
|
-
removeWorktree,
|
|
33
|
-
getMergedPRWorktrees,
|
|
34
|
-
WorktreeError,
|
|
35
|
-
} from "./worktree.js";
|
|
36
|
-
import {
|
|
37
|
-
launchClaudeCode,
|
|
38
|
-
isClaudeCodeAvailable,
|
|
39
|
-
ClaudeError,
|
|
40
|
-
} from "./claude.js";
|
|
41
|
-
import { launchCodexCLI, isCodexAvailable, CodexError } from "./codex.js";
|
|
42
|
-
import {
|
|
43
|
-
selectFromTable,
|
|
44
|
-
selectBaseBranch,
|
|
45
|
-
confirmWorktreeCreation,
|
|
46
|
-
confirmSkipPermissions,
|
|
47
|
-
selectWorktreeForManagement,
|
|
48
|
-
selectWorktreeAction,
|
|
49
|
-
confirmWorktreeRemoval,
|
|
50
|
-
confirmBranchRemoval,
|
|
51
|
-
selectChangesAction,
|
|
52
|
-
inputCommitMessage,
|
|
53
|
-
confirmDiscardChanges,
|
|
54
|
-
confirmContinue,
|
|
55
|
-
selectCleanupTargets,
|
|
56
|
-
confirmCleanup,
|
|
57
|
-
confirmRemoteBranchDeletion,
|
|
58
|
-
confirmPushUnpushedCommits,
|
|
59
|
-
confirmProceedWithoutPush,
|
|
60
|
-
selectSession,
|
|
61
|
-
selectClaudeExecutionMode,
|
|
62
|
-
selectVersionBumpType,
|
|
63
|
-
selectReleaseAction,
|
|
64
|
-
} from "./ui/legacy/prompts.js";
|
|
65
|
-
import {
|
|
66
|
-
displayBranchTable,
|
|
67
|
-
printError,
|
|
68
|
-
printSuccess,
|
|
69
|
-
printInfo,
|
|
70
|
-
printWarning,
|
|
71
|
-
printExit,
|
|
72
|
-
printStatistics,
|
|
73
|
-
displayCleanupTargets,
|
|
74
|
-
displayCleanupResults,
|
|
75
|
-
} from "./ui/legacy/display.js";
|
|
76
|
-
import { createBranchTable } from "./ui/legacy/table.js";
|
|
77
|
-
import chalk from "chalk";
|
|
78
|
-
import { isGitHubCLIAvailable, checkGitHubAuth } from "./github.js";
|
|
79
|
-
import { CleanupTarget } from "./ui/types.js";
|
|
80
|
-
import { AppError, setupExitHandlers, handleUserCancel } from "./utils.js";
|
|
81
|
-
import { BranchInfo, WorktreeConfig } from "./ui/types.js";
|
|
82
|
-
import { WorktreeInfo } from "./worktree.js";
|
|
83
|
-
import {
|
|
84
|
-
loadSession,
|
|
85
|
-
saveSession,
|
|
86
|
-
SessionData,
|
|
87
|
-
getAllSessions,
|
|
88
|
-
} from "./config/index.js";
|
|
89
|
-
|
|
90
|
-
function showHelp(): void {
|
|
91
|
-
console.log(`
|
|
92
|
-
Worktree Manager
|
|
93
|
-
|
|
94
|
-
Usage: claude-worktree [options]
|
|
95
|
-
|
|
96
|
-
Options:
|
|
97
|
-
-c Continue from the last session (automatically open the last used worktree)
|
|
98
|
-
-r, --resume Resume a session - interactively select from available sessions
|
|
99
|
-
--tool <name> Select AI tool to launch in worktree (claude|codex)
|
|
100
|
-
-h, --help Show this help message
|
|
101
|
-
|
|
102
|
-
Description:
|
|
103
|
-
Interactive Git worktree manager with AI tool selection (Claude Code / Codex CLI) and graphical branch selection.
|
|
104
|
-
|
|
105
|
-
Without options: Opens the interactive menu to select branches and manage worktrees.
|
|
106
|
-
With -c option: Automatically continues from where you left off in the last session.
|
|
107
|
-
With -r option: Shows a list of recent sessions to choose from and resume.
|
|
108
|
-
|
|
109
|
-
Pass-through:
|
|
110
|
-
Use "--" to pass additional args directly to the selected tool.
|
|
111
|
-
Examples:
|
|
112
|
-
claude-worktree --tool claude -- -r
|
|
113
|
-
claude-worktree --tool codex -- resume --last
|
|
114
|
-
`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Main function for Ink.js UI (new UI)
|
|
119
|
-
*/
|
|
120
|
-
async function mainInkUI(): Promise<void> {
|
|
121
|
-
const { render } = await import('ink');
|
|
122
|
-
const React = await import('react');
|
|
123
|
-
const { App } = await import('./ui/components/App.js');
|
|
124
|
-
|
|
125
|
-
// Check if current directory is a Git repository
|
|
126
|
-
if (!(await isGitRepository())) {
|
|
127
|
-
printError("Current directory is not a Git repository.");
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
let selectedBranch: string | undefined;
|
|
132
|
-
|
|
133
|
-
const { unmount, waitUntilExit } = render(
|
|
134
|
-
React.createElement(App, {
|
|
135
|
-
onExit: (branch?: string) => {
|
|
136
|
-
selectedBranch = branch;
|
|
137
|
-
},
|
|
138
|
-
})
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
// Wait for user to exit
|
|
142
|
-
await waitUntilExit();
|
|
143
|
-
unmount();
|
|
144
|
-
|
|
145
|
-
// If a branch was selected, handle it
|
|
146
|
-
if (selectedBranch) {
|
|
147
|
-
printInfo(`Selected branch: ${selectedBranch}`);
|
|
148
|
-
// TODO: Implement branch handling logic
|
|
149
|
-
// For now, just exit
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export async function main(): Promise<void> {
|
|
154
|
-
try {
|
|
155
|
-
// Check for Ink UI feature flag (default: true, set USE_INK_UI=false to use legacy UI)
|
|
156
|
-
const useInkUI = process.env.USE_INK_UI !== 'false';
|
|
157
|
-
|
|
158
|
-
if (useInkUI) {
|
|
159
|
-
await mainInkUI();
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Legacy UI (original implementation)
|
|
164
|
-
// Parse command line arguments
|
|
165
|
-
const args = process.argv.slice(2);
|
|
166
|
-
const continueLastSession = args.includes("-c");
|
|
167
|
-
const resumeSession = args.includes("-r") || args.includes("--resume");
|
|
168
|
-
const showHelpFlag = args.includes("-h") || args.includes("--help");
|
|
169
|
-
// Parse --tool value (supports --tool codex or --tool=codex)
|
|
170
|
-
const toolIndex = args.findIndex(
|
|
171
|
-
(a) =>
|
|
172
|
-
a === "--tool" || (typeof a === "string" && a.startsWith("--tool=")),
|
|
173
|
-
);
|
|
174
|
-
let cliToolArg: "claude" | "codex" | undefined;
|
|
175
|
-
if (toolIndex !== -1) {
|
|
176
|
-
const token: string | undefined = args[toolIndex];
|
|
177
|
-
if (typeof token === "string" && token.includes("=")) {
|
|
178
|
-
const val = token.split("=")[1];
|
|
179
|
-
if (val === "claude" || val === "codex") cliToolArg = val;
|
|
180
|
-
else {
|
|
181
|
-
printError(`Unknown tool: ${val}. Use --tool claude|codex`);
|
|
182
|
-
return; // Exit early on invalid tool arg
|
|
183
|
-
}
|
|
184
|
-
} else if (
|
|
185
|
-
typeof args[toolIndex + 1] === "string" &&
|
|
186
|
-
(args[toolIndex + 1] === "claude" || args[toolIndex + 1] === "codex")
|
|
187
|
-
) {
|
|
188
|
-
cliToolArg = args[toolIndex + 1] as "claude" | "codex";
|
|
189
|
-
} else if (typeof args[toolIndex + 1] === "string") {
|
|
190
|
-
printError(
|
|
191
|
-
`Unknown tool: ${args[toolIndex + 1]}. Use --tool claude|codex`,
|
|
192
|
-
);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Collect pass-through args after "--"
|
|
198
|
-
const dashDashIndex = args.findIndex((a) => a === "--");
|
|
199
|
-
const passThroughArgs: string[] =
|
|
200
|
-
dashDashIndex !== -1 ? args.slice(dashDashIndex + 1) : [];
|
|
201
|
-
|
|
202
|
-
// Show help if requested
|
|
203
|
-
if (showHelpFlag) {
|
|
204
|
-
showHelp();
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Setup graceful exit handlers
|
|
209
|
-
setupExitHandlers();
|
|
210
|
-
|
|
211
|
-
// Check if current directory is a Git repository
|
|
212
|
-
if (!(await isGitRepository())) {
|
|
213
|
-
printError("Current directory is not a Git repository.");
|
|
214
|
-
process.exit(1);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Check if running from a worktree directory
|
|
218
|
-
if (await isInWorktree()) {
|
|
219
|
-
printWarning("Running from a worktree directory is not recommended.");
|
|
220
|
-
printInfo(
|
|
221
|
-
"Please run this command from the main repository root to avoid path issues.",
|
|
222
|
-
);
|
|
223
|
-
printInfo(
|
|
224
|
-
"You can continue, but some operations may not work correctly.",
|
|
225
|
-
);
|
|
226
|
-
const shouldContinue = await confirmContinue(
|
|
227
|
-
"Do you want to continue anyway?",
|
|
228
|
-
);
|
|
229
|
-
if (!shouldContinue) {
|
|
230
|
-
process.exit(0);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Check tool availability for selection later
|
|
235
|
-
const [claudeAvailable, codexAvailable] = await Promise.all([
|
|
236
|
-
isClaudeCodeAvailable().catch(() => false),
|
|
237
|
-
isCodexAvailable().catch(() => false),
|
|
238
|
-
]);
|
|
239
|
-
if (!claudeAvailable) {
|
|
240
|
-
printWarning(
|
|
241
|
-
"Claude Code CLI not found. Make sure it's installed and in your PATH.",
|
|
242
|
-
);
|
|
243
|
-
printInfo("You can install it from: https://claude.ai/code");
|
|
244
|
-
}
|
|
245
|
-
if (!codexAvailable) {
|
|
246
|
-
printInfo(
|
|
247
|
-
"Codex CLI not detected. You can still select it, but bunx may prompt to install.",
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Get repository root
|
|
252
|
-
const repoRoot = await getRepositoryRoot();
|
|
253
|
-
|
|
254
|
-
// Handle continue last session option
|
|
255
|
-
if (continueLastSession) {
|
|
256
|
-
const sessionData = await loadSession(repoRoot);
|
|
257
|
-
if (sessionData && sessionData.lastWorktreePath) {
|
|
258
|
-
printInfo(
|
|
259
|
-
`Continuing last session: ${sessionData.lastBranch} (${sessionData.lastWorktreePath})`,
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
// Check if worktree still exists
|
|
263
|
-
if (await worktreeExists(sessionData.lastBranch!)) {
|
|
264
|
-
// Select AI tool
|
|
265
|
-
const { selectAITool } = await import("./ui/legacy/prompts.js");
|
|
266
|
-
let selectedTool: "claude" | "codex" | null = null;
|
|
267
|
-
|
|
268
|
-
// Parse tool arg
|
|
269
|
-
const argv = process.argv.slice(2);
|
|
270
|
-
const idx = argv.findIndex(
|
|
271
|
-
(a) =>
|
|
272
|
-
a === "--tool" ||
|
|
273
|
-
(typeof a === "string" && a.startsWith("--tool=")),
|
|
274
|
-
);
|
|
275
|
-
let argTool: "claude" | "codex" | undefined;
|
|
276
|
-
if (idx !== -1) {
|
|
277
|
-
const tok: string | undefined = argv[idx];
|
|
278
|
-
if (typeof tok === "string" && tok.includes("=")) {
|
|
279
|
-
const val = tok.split("=")[1];
|
|
280
|
-
if (val === "claude" || val === "codex") argTool = val;
|
|
281
|
-
} else if (
|
|
282
|
-
typeof argv[idx + 1] === "string" &&
|
|
283
|
-
(argv[idx + 1] === "claude" || argv[idx + 1] === "codex")
|
|
284
|
-
) {
|
|
285
|
-
argTool = argv[idx + 1] as "claude" | "codex";
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const [localClaudeAvail, localCodexAvail] = await Promise.all([
|
|
290
|
-
isClaudeCodeAvailable().catch(() => false),
|
|
291
|
-
isCodexAvailable().catch(() => false),
|
|
292
|
-
]);
|
|
293
|
-
|
|
294
|
-
if (argTool) {
|
|
295
|
-
selectedTool = argTool;
|
|
296
|
-
if (selectedTool === "claude" && !localClaudeAvail) {
|
|
297
|
-
selectedTool = null;
|
|
298
|
-
printWarning("Requested tool is not available.");
|
|
299
|
-
} else if (selectedTool === "codex" && !localCodexAvail) {
|
|
300
|
-
selectedTool = null;
|
|
301
|
-
printWarning("Codex CLI was not detected.");
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (!selectedTool) {
|
|
306
|
-
selectedTool = await selectAITool({
|
|
307
|
-
claudeAvailable: localClaudeAvail,
|
|
308
|
-
codexAvailable: localCodexAvail,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (!selectedTool) {
|
|
313
|
-
printInfo("No AI tool selected.");
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const executionConfig = await selectClaudeExecutionMode(
|
|
318
|
-
selectedTool === "claude" ? "Claude Code" : "Codex CLI",
|
|
319
|
-
);
|
|
320
|
-
if (!executionConfig) return;
|
|
321
|
-
const { mode, skipPermissions } = executionConfig;
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
if (selectedTool === "claude") {
|
|
325
|
-
await launchClaudeCode(sessionData.lastWorktreePath, {
|
|
326
|
-
mode,
|
|
327
|
-
skipPermissions,
|
|
328
|
-
});
|
|
329
|
-
await handlePostClaudeChanges(sessionData.lastWorktreePath);
|
|
330
|
-
} else {
|
|
331
|
-
await launchCodexCLI(sessionData.lastWorktreePath, {
|
|
332
|
-
mode,
|
|
333
|
-
bypassApprovals: skipPermissions,
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
} catch (error) {
|
|
337
|
-
if (error instanceof ClaudeError || error instanceof CodexError) {
|
|
338
|
-
printError(error.message);
|
|
339
|
-
} else {
|
|
340
|
-
printError(
|
|
341
|
-
`Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
await confirmContinue("Press enter to continue...");
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return; // Exit after continuing session
|
|
348
|
-
} else {
|
|
349
|
-
printWarning(
|
|
350
|
-
`Last session worktree no longer exists: ${sessionData.lastWorktreePath}`,
|
|
351
|
-
);
|
|
352
|
-
printInfo("Falling back to normal flow...");
|
|
353
|
-
}
|
|
354
|
-
} else {
|
|
355
|
-
printInfo("No previous session found. Starting normally...");
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Handle resume session option
|
|
360
|
-
if (resumeSession) {
|
|
361
|
-
const allSessions = await getAllSessions();
|
|
362
|
-
if (allSessions.length === 0) {
|
|
363
|
-
printInfo("No previous sessions found. Starting normally...");
|
|
364
|
-
} else {
|
|
365
|
-
const selectedSession = await selectSession(allSessions);
|
|
366
|
-
if (selectedSession && selectedSession.lastWorktreePath) {
|
|
367
|
-
printInfo(
|
|
368
|
-
`Resuming session: ${selectedSession.lastBranch} (${selectedSession.lastWorktreePath})`,
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
// Check if worktree still exists
|
|
372
|
-
if (await worktreeExists(selectedSession.lastBranch!)) {
|
|
373
|
-
// Select AI tool
|
|
374
|
-
const { selectAITool } = await import("./ui/legacy/prompts.js");
|
|
375
|
-
let selectedTool: "claude" | "codex" | null = null;
|
|
376
|
-
|
|
377
|
-
// Parse tool arg
|
|
378
|
-
const argv = process.argv.slice(2);
|
|
379
|
-
const idx = argv.findIndex(
|
|
380
|
-
(a) =>
|
|
381
|
-
a === "--tool" ||
|
|
382
|
-
(typeof a === "string" && a.startsWith("--tool=")),
|
|
383
|
-
);
|
|
384
|
-
let argTool: "claude" | "codex" | undefined;
|
|
385
|
-
if (idx !== -1) {
|
|
386
|
-
const tok: string | undefined = argv[idx];
|
|
387
|
-
if (typeof tok === "string" && tok.includes("=")) {
|
|
388
|
-
const val = tok.split("=")[1];
|
|
389
|
-
if (val === "claude" || val === "codex") argTool = val;
|
|
390
|
-
} else if (
|
|
391
|
-
typeof argv[idx + 1] === "string" &&
|
|
392
|
-
(argv[idx + 1] === "claude" || argv[idx + 1] === "codex")
|
|
393
|
-
) {
|
|
394
|
-
argTool = argv[idx + 1] as "claude" | "codex";
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const [localClaudeAvail, localCodexAvail] = await Promise.all([
|
|
399
|
-
isClaudeCodeAvailable().catch(() => false),
|
|
400
|
-
isCodexAvailable().catch(() => false),
|
|
401
|
-
]);
|
|
402
|
-
|
|
403
|
-
if (argTool) {
|
|
404
|
-
selectedTool = argTool;
|
|
405
|
-
if (selectedTool === "claude" && !localClaudeAvail) {
|
|
406
|
-
selectedTool = null;
|
|
407
|
-
printWarning("Requested tool is not available.");
|
|
408
|
-
} else if (selectedTool === "codex" && !localCodexAvail) {
|
|
409
|
-
selectedTool = null;
|
|
410
|
-
printWarning("Codex CLI was not detected.");
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (!selectedTool) {
|
|
415
|
-
selectedTool = await selectAITool({
|
|
416
|
-
claudeAvailable: localClaudeAvail,
|
|
417
|
-
codexAvailable: localCodexAvail,
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
if (!selectedTool) {
|
|
422
|
-
printInfo("No AI tool selected.");
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const executionConfig = await selectClaudeExecutionMode(
|
|
427
|
-
selectedTool === "claude" ? "Claude Code" : "Codex CLI",
|
|
428
|
-
);
|
|
429
|
-
if (!executionConfig) return;
|
|
430
|
-
const { mode, skipPermissions } = executionConfig;
|
|
431
|
-
|
|
432
|
-
try {
|
|
433
|
-
if (selectedTool === "claude") {
|
|
434
|
-
await launchClaudeCode(selectedSession.lastWorktreePath, {
|
|
435
|
-
mode,
|
|
436
|
-
skipPermissions,
|
|
437
|
-
});
|
|
438
|
-
await handlePostClaudeChanges(selectedSession.lastWorktreePath);
|
|
439
|
-
} else {
|
|
440
|
-
await launchCodexCLI(selectedSession.lastWorktreePath, {
|
|
441
|
-
mode,
|
|
442
|
-
bypassApprovals: skipPermissions,
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
} catch (error) {
|
|
446
|
-
if (error instanceof ClaudeError || error instanceof CodexError) {
|
|
447
|
-
printError(error.message);
|
|
448
|
-
} else {
|
|
449
|
-
printError(
|
|
450
|
-
`Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
await confirmContinue("Press enter to continue...");
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
return; // Exit after resuming session
|
|
457
|
-
} else {
|
|
458
|
-
printWarning(
|
|
459
|
-
`Selected session worktree no longer exists: ${selectedSession.lastWorktreePath}`,
|
|
460
|
-
);
|
|
461
|
-
printInfo("Falling back to normal flow...");
|
|
462
|
-
}
|
|
463
|
-
} else {
|
|
464
|
-
printInfo("No session selected. Starting normally...");
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Main application loop
|
|
470
|
-
while (true) {
|
|
471
|
-
try {
|
|
472
|
-
// Get current repository state
|
|
473
|
-
const [branches, worktrees] = await Promise.all([
|
|
474
|
-
getAllBranches(),
|
|
475
|
-
listAdditionalWorktrees(),
|
|
476
|
-
]);
|
|
477
|
-
|
|
478
|
-
// Create and display table
|
|
479
|
-
const choices = await createBranchTable(branches, worktrees);
|
|
480
|
-
|
|
481
|
-
// Get user selection with statistics
|
|
482
|
-
const selection = await selectFromTable(choices, {
|
|
483
|
-
branches,
|
|
484
|
-
worktrees,
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
// Handle selection
|
|
488
|
-
const shouldContinue = await handleSelection(
|
|
489
|
-
selection,
|
|
490
|
-
branches,
|
|
491
|
-
worktrees,
|
|
492
|
-
repoRoot,
|
|
493
|
-
);
|
|
494
|
-
|
|
495
|
-
if (!shouldContinue) {
|
|
496
|
-
break;
|
|
497
|
-
}
|
|
498
|
-
} catch (error) {
|
|
499
|
-
handleUserCancel(error);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
} catch (error) {
|
|
503
|
-
if (error instanceof GitError) {
|
|
504
|
-
printError(`Git error: ${error.message}`);
|
|
505
|
-
} else if (error instanceof WorktreeError) {
|
|
506
|
-
printError(`Worktree error: ${error.message}`);
|
|
507
|
-
} else if (error instanceof ClaudeError) {
|
|
508
|
-
printError(`Claude Code error: ${error.message}`);
|
|
509
|
-
if (error.cause && error.cause instanceof Error) {
|
|
510
|
-
printError(`Cause: ${error.cause.message}`);
|
|
511
|
-
}
|
|
512
|
-
} else if (error instanceof AppError) {
|
|
513
|
-
printError(`Application error: ${error.message}`);
|
|
514
|
-
} else {
|
|
515
|
-
printError(
|
|
516
|
-
`Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
process.exit(1);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
async function handleSelection(
|
|
525
|
-
selection: string,
|
|
526
|
-
branches: BranchInfo[],
|
|
527
|
-
worktrees: WorktreeInfo[],
|
|
528
|
-
repoRoot: string,
|
|
529
|
-
): Promise<boolean> {
|
|
530
|
-
switch (selection) {
|
|
531
|
-
case "__exit__":
|
|
532
|
-
printExit();
|
|
533
|
-
return false;
|
|
534
|
-
|
|
535
|
-
case "__create_new__":
|
|
536
|
-
return await handleCreateNewBranch(branches, repoRoot);
|
|
537
|
-
|
|
538
|
-
case "__manage_worktrees__":
|
|
539
|
-
return await handleManageWorktrees(worktrees);
|
|
540
|
-
|
|
541
|
-
case "__cleanup_prs__":
|
|
542
|
-
return await handleCleanupMergedPRs();
|
|
543
|
-
|
|
544
|
-
default:
|
|
545
|
-
// Handle branch selection
|
|
546
|
-
return await handleBranchSelection(selection, repoRoot);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
async function handleBranchSelection(
|
|
551
|
-
branchName: string,
|
|
552
|
-
repoRoot: string,
|
|
553
|
-
): Promise<boolean> {
|
|
554
|
-
try {
|
|
555
|
-
// Collect pass-through args from process argv
|
|
556
|
-
const argvAll = process.argv.slice(2);
|
|
557
|
-
const ddIndex = argvAll.findIndex((a) => a === "--");
|
|
558
|
-
const passThroughArgs = ddIndex !== -1 ? argvAll.slice(ddIndex + 1) : [];
|
|
559
|
-
|
|
560
|
-
// Check if this is a remote branch
|
|
561
|
-
const isRemoteBranch = branchName.startsWith("origin/");
|
|
562
|
-
let localBranchName = branchName;
|
|
563
|
-
let targetBranch = branchName;
|
|
564
|
-
|
|
565
|
-
if (isRemoteBranch) {
|
|
566
|
-
// Extract local branch name from remote branch
|
|
567
|
-
localBranchName = branchName.replace(/^origin\//, "");
|
|
568
|
-
targetBranch = localBranchName;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// Check if worktree exists (using local branch name)
|
|
572
|
-
let worktreePath = await worktreeExists(targetBranch);
|
|
573
|
-
|
|
574
|
-
if (worktreePath) {
|
|
575
|
-
printInfo(`Opening existing worktree: ${worktreePath}`);
|
|
576
|
-
} else {
|
|
577
|
-
// Create new worktree
|
|
578
|
-
worktreePath = await generateWorktreePath(repoRoot, targetBranch);
|
|
579
|
-
|
|
580
|
-
// Check for path conflict
|
|
581
|
-
const {
|
|
582
|
-
checkWorktreePathConflict,
|
|
583
|
-
generateAlternativeWorktreePath,
|
|
584
|
-
removeWorktree: removeConflictingWorktree,
|
|
585
|
-
} = await import("./worktree.js");
|
|
586
|
-
const pathConflict = await checkWorktreePathConflict(worktreePath);
|
|
587
|
-
|
|
588
|
-
if (pathConflict && pathConflict.branch !== targetBranch) {
|
|
589
|
-
// Path conflict detected - let user choose how to proceed
|
|
590
|
-
const { selectWorktreePathConflictResolution } = await import(
|
|
591
|
-
"./ui/legacy/prompts.js"
|
|
592
|
-
);
|
|
593
|
-
const resolution = await selectWorktreePathConflictResolution(
|
|
594
|
-
targetBranch,
|
|
595
|
-
worktreePath,
|
|
596
|
-
pathConflict.branch,
|
|
597
|
-
);
|
|
598
|
-
|
|
599
|
-
if (resolution === "cancel") {
|
|
600
|
-
printInfo("Operation cancelled.");
|
|
601
|
-
return true;
|
|
602
|
-
} else if (resolution === "remove-and-create") {
|
|
603
|
-
printInfo(
|
|
604
|
-
`Removing existing worktree for "${pathConflict.branch}"...`,
|
|
605
|
-
);
|
|
606
|
-
await removeConflictingWorktree(worktreePath, true);
|
|
607
|
-
printSuccess("Existing worktree removed.");
|
|
608
|
-
} else if (resolution === "use-different-path") {
|
|
609
|
-
worktreePath = await generateAlternativeWorktreePath(worktreePath);
|
|
610
|
-
printInfo(`Using alternative path: ${worktreePath}`);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
if (!(await confirmWorktreeCreation(targetBranch, worktreePath))) {
|
|
615
|
-
printInfo("Operation cancelled.");
|
|
616
|
-
return true; // Continue to main menu
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
let isNewBranch = false;
|
|
620
|
-
let baseBranch = targetBranch;
|
|
621
|
-
|
|
622
|
-
if (isRemoteBranch) {
|
|
623
|
-
// Check if local branch exists
|
|
624
|
-
const localExists = await branchExists(localBranchName);
|
|
625
|
-
if (!localExists) {
|
|
626
|
-
// Need to create new local branch from remote
|
|
627
|
-
isNewBranch = true;
|
|
628
|
-
baseBranch = branchName; // Use full remote branch name as base
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
const worktreeConfig: WorktreeConfig = {
|
|
633
|
-
branchName: targetBranch,
|
|
634
|
-
worktreePath,
|
|
635
|
-
repoRoot,
|
|
636
|
-
isNewBranch,
|
|
637
|
-
baseBranch,
|
|
638
|
-
};
|
|
639
|
-
|
|
640
|
-
printInfo(`Creating worktree for "${targetBranch}"...`);
|
|
641
|
-
await createWorktree(worktreeConfig);
|
|
642
|
-
printSuccess(`Worktree created at: ${worktreePath}`);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// Select and launch AI tool
|
|
646
|
-
const { selectAITool } = await import("./ui/legacy/prompts.js");
|
|
647
|
-
let selectedTool: "claude" | "codex" | null = null;
|
|
648
|
-
// Re-parse tool arg for this flow as well
|
|
649
|
-
const argv = process.argv.slice(2);
|
|
650
|
-
const idx = argv.findIndex(
|
|
651
|
-
(a) =>
|
|
652
|
-
a === "--tool" || (typeof a === "string" && a.startsWith("--tool=")),
|
|
653
|
-
);
|
|
654
|
-
let argTool: "claude" | "codex" | undefined;
|
|
655
|
-
if (idx !== -1) {
|
|
656
|
-
const tok: string | undefined = argv[idx];
|
|
657
|
-
if (typeof tok === "string" && tok.includes("=")) {
|
|
658
|
-
const val = tok.split("=")[1];
|
|
659
|
-
if (val === "claude" || val === "codex") argTool = val;
|
|
660
|
-
} else if (
|
|
661
|
-
typeof argv[idx + 1] === "string" &&
|
|
662
|
-
(argv[idx + 1] === "claude" || argv[idx + 1] === "codex")
|
|
663
|
-
) {
|
|
664
|
-
argTool = argv[idx + 1] as "claude" | "codex";
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
const [localClaudeAvail, localCodexAvail] = await Promise.all([
|
|
668
|
-
isClaudeCodeAvailable().catch(() => false),
|
|
669
|
-
isCodexAvailable().catch(() => false),
|
|
670
|
-
]);
|
|
671
|
-
if (argTool) {
|
|
672
|
-
selectedTool = argTool;
|
|
673
|
-
if (selectedTool === "claude" && !localClaudeAvail) {
|
|
674
|
-
selectedTool = null;
|
|
675
|
-
printWarning("Requested tool is not available.");
|
|
676
|
-
} else if (selectedTool === "codex" && !localCodexAvail) {
|
|
677
|
-
selectedTool = null;
|
|
678
|
-
printWarning(
|
|
679
|
-
"Codex CLI was not detected. Install Bun and Codex before selecting it.",
|
|
680
|
-
);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
if (!selectedTool) {
|
|
684
|
-
selectedTool = await selectAITool({
|
|
685
|
-
claudeAvailable: localClaudeAvail,
|
|
686
|
-
codexAvailable: localCodexAvail,
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
if (!selectedTool) {
|
|
690
|
-
printInfo("No AI tool selected. Returning to menu.");
|
|
691
|
-
return true;
|
|
692
|
-
}
|
|
693
|
-
if (!localClaudeAvail && !localCodexAvail) {
|
|
694
|
-
printError(
|
|
695
|
-
"No AI tools are available in PATH (Claude Code or Codex CLI).",
|
|
696
|
-
);
|
|
697
|
-
await confirmContinue("Press enter to continue...");
|
|
698
|
-
return true;
|
|
699
|
-
}
|
|
700
|
-
if (selectedTool === "claude" && !localClaudeAvail) {
|
|
701
|
-
printError(
|
|
702
|
-
"Claude Code CLI not found in PATH. Install it before selecting this option.",
|
|
703
|
-
);
|
|
704
|
-
await confirmContinue("Press enter to continue...");
|
|
705
|
-
return true;
|
|
706
|
-
}
|
|
707
|
-
if (selectedTool === "codex" && !localCodexAvail) {
|
|
708
|
-
printError(
|
|
709
|
-
"Codex CLI not detected via bunx. Install Bun and the Codex CLI before selecting this option.",
|
|
710
|
-
);
|
|
711
|
-
await confirmContinue("Press enter to continue...");
|
|
712
|
-
return true;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
const executionConfig = await selectClaudeExecutionMode(
|
|
716
|
-
selectedTool === "claude" ? "Claude Code" : "Codex CLI",
|
|
717
|
-
);
|
|
718
|
-
if (!executionConfig) return true;
|
|
719
|
-
const { mode, skipPermissions } = executionConfig;
|
|
720
|
-
|
|
721
|
-
try {
|
|
722
|
-
// Save session data before launching
|
|
723
|
-
const sessionData: SessionData = {
|
|
724
|
-
lastWorktreePath: worktreePath,
|
|
725
|
-
lastBranch: targetBranch,
|
|
726
|
-
timestamp: Date.now(),
|
|
727
|
-
repositoryRoot: repoRoot,
|
|
728
|
-
};
|
|
729
|
-
await saveSession(sessionData);
|
|
730
|
-
|
|
731
|
-
if (selectedTool === "claude") {
|
|
732
|
-
await launchClaudeCode(worktreePath, {
|
|
733
|
-
mode,
|
|
734
|
-
skipPermissions,
|
|
735
|
-
extraArgs: passThroughArgs,
|
|
736
|
-
});
|
|
737
|
-
await handlePostClaudeChanges(worktreePath);
|
|
738
|
-
} else {
|
|
739
|
-
await launchCodexCLI(worktreePath, {
|
|
740
|
-
mode,
|
|
741
|
-
extraArgs: passThroughArgs,
|
|
742
|
-
bypassApprovals: skipPermissions,
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
} catch (error) {
|
|
746
|
-
if (error instanceof ClaudeError || error instanceof CodexError) {
|
|
747
|
-
printError(error.message);
|
|
748
|
-
} else {
|
|
749
|
-
printError(
|
|
750
|
-
`Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
|
|
751
|
-
);
|
|
752
|
-
}
|
|
753
|
-
await confirmContinue("Press enter to continue...");
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// After handling changes, return to main menu
|
|
757
|
-
return true;
|
|
758
|
-
} catch (error) {
|
|
759
|
-
printError(
|
|
760
|
-
`Failed to handle branch selection: ${error instanceof Error ? error.message : String(error)}`,
|
|
761
|
-
);
|
|
762
|
-
await confirmContinue("Press enter to continue...");
|
|
763
|
-
return true;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
async function handleCreateNewBranch(
|
|
768
|
-
branches: BranchInfo[],
|
|
769
|
-
repoRoot: string,
|
|
770
|
-
): Promise<boolean> {
|
|
771
|
-
try {
|
|
772
|
-
// まず、ブランチタイプのみを選択
|
|
773
|
-
const { selectBranchType } = await import("./ui/legacy/prompts.js");
|
|
774
|
-
const branchType = await selectBranchType();
|
|
775
|
-
|
|
776
|
-
let targetBranch: string;
|
|
777
|
-
let baseBranch: string;
|
|
778
|
-
|
|
779
|
-
// リリースブランチの場合は特別な処理
|
|
780
|
-
if (branchType === "release") {
|
|
781
|
-
// Git flowではリリースブランチはdevelopから分岐するが、
|
|
782
|
-
// developブランチがない場合はmainブランチから分岐
|
|
783
|
-
const developBranch = branches.find(
|
|
784
|
-
(b) => b.type === "local" && (b.name === "develop" || b.name === "dev"),
|
|
785
|
-
);
|
|
786
|
-
|
|
787
|
-
if (developBranch) {
|
|
788
|
-
baseBranch = developBranch.name;
|
|
789
|
-
printInfo(`Creating release branch from ${baseBranch} (Git Flow)`);
|
|
790
|
-
} else {
|
|
791
|
-
// developがない場合はmain/masterから分岐
|
|
792
|
-
const mainBranch = branches.find(
|
|
793
|
-
(b) =>
|
|
794
|
-
b.type === "local" && (b.name === "main" || b.name === "master"),
|
|
795
|
-
);
|
|
796
|
-
|
|
797
|
-
if (!mainBranch) {
|
|
798
|
-
printError("No develop, main, or master branch found.");
|
|
799
|
-
return true;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
baseBranch = mainBranch.name;
|
|
803
|
-
printWarning(
|
|
804
|
-
`No develop branch found. Creating release branch from ${baseBranch}`,
|
|
805
|
-
);
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
// 現在のバージョンを取得
|
|
809
|
-
const currentVersion = await getCurrentVersion(repoRoot);
|
|
810
|
-
|
|
811
|
-
// バージョンバンプタイプを選択
|
|
812
|
-
const versionBump = await selectVersionBumpType(currentVersion);
|
|
813
|
-
|
|
814
|
-
// 新しいバージョンを計算
|
|
815
|
-
const newVersion = calculateNewVersion(currentVersion, versionBump);
|
|
816
|
-
|
|
817
|
-
// リリースブランチ名を生成
|
|
818
|
-
targetBranch = `release/${newVersion}`;
|
|
819
|
-
printInfo(`Release branch will be: ${targetBranch}`);
|
|
820
|
-
} else {
|
|
821
|
-
// 通常のブランチの場合
|
|
822
|
-
const { inputBranchName } = await import("./ui/legacy/prompts.js");
|
|
823
|
-
const taskName = await inputBranchName(branchType);
|
|
824
|
-
targetBranch = `${branchType}/${taskName}`;
|
|
825
|
-
baseBranch = await selectBaseBranch(branches);
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
// Check if branch already exists
|
|
829
|
-
if (await branchExists(targetBranch)) {
|
|
830
|
-
printError(`Branch "${targetBranch}" already exists.`);
|
|
831
|
-
if (await confirmContinue("Return to main menu?")) {
|
|
832
|
-
return true;
|
|
833
|
-
}
|
|
834
|
-
return false;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
printInfo(`Creating new branch "${targetBranch}" from "${baseBranch}"`);
|
|
838
|
-
|
|
839
|
-
// Create worktree path
|
|
840
|
-
const worktreePath = await generateWorktreePath(repoRoot, targetBranch);
|
|
841
|
-
|
|
842
|
-
if (!(await confirmWorktreeCreation(targetBranch, worktreePath))) {
|
|
843
|
-
printInfo("Operation cancelled.");
|
|
844
|
-
return true;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
// Create worktree configuration
|
|
848
|
-
const worktreeConfig: WorktreeConfig = {
|
|
849
|
-
branchName: targetBranch,
|
|
850
|
-
worktreePath,
|
|
851
|
-
repoRoot,
|
|
852
|
-
isNewBranch: true,
|
|
853
|
-
baseBranch,
|
|
854
|
-
};
|
|
855
|
-
|
|
856
|
-
// Create worktree
|
|
857
|
-
printInfo(`Creating worktree for "${targetBranch}"...`);
|
|
858
|
-
await createWorktree(worktreeConfig);
|
|
859
|
-
printSuccess(`Worktree created at: ${worktreePath}`);
|
|
860
|
-
|
|
861
|
-
// リリースブランチの場合、worktree作成後にnpm versionを実行
|
|
862
|
-
if (branchType === "release") {
|
|
863
|
-
printInfo("Updating version in release branch...");
|
|
864
|
-
try {
|
|
865
|
-
const newVersion = targetBranch.replace("release/", "");
|
|
866
|
-
await executeNpmVersionInWorktree(worktreePath, newVersion);
|
|
867
|
-
printSuccess(`Version updated to ${newVersion} in release branch`);
|
|
868
|
-
} catch (error) {
|
|
869
|
-
printError(
|
|
870
|
-
`Failed to update version: ${error instanceof Error ? error.message : String(error)}`,
|
|
871
|
-
);
|
|
872
|
-
// エラーが発生してもworktreeは作成済みなので続行
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
// Select and launch AI tool
|
|
877
|
-
const { selectAITool } = await import("./ui/legacy/prompts.js");
|
|
878
|
-
let selectedTool: "claude" | "codex" | null = null;
|
|
879
|
-
|
|
880
|
-
// Parse tool arg
|
|
881
|
-
const argv = process.argv.slice(2);
|
|
882
|
-
const idx = argv.findIndex(
|
|
883
|
-
(a) =>
|
|
884
|
-
a === "--tool" || (typeof a === "string" && a.startsWith("--tool=")),
|
|
885
|
-
);
|
|
886
|
-
let argTool: "claude" | "codex" | undefined;
|
|
887
|
-
if (idx !== -1) {
|
|
888
|
-
const tok: string | undefined = argv[idx];
|
|
889
|
-
if (typeof tok === "string" && tok.includes("=")) {
|
|
890
|
-
const val = tok.split("=")[1];
|
|
891
|
-
if (val === "claude" || val === "codex") argTool = val;
|
|
892
|
-
} else if (
|
|
893
|
-
typeof argv[idx + 1] === "string" &&
|
|
894
|
-
(argv[idx + 1] === "claude" || argv[idx + 1] === "codex")
|
|
895
|
-
) {
|
|
896
|
-
argTool = argv[idx + 1] as "claude" | "codex";
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
const [localClaudeAvail, localCodexAvail] = await Promise.all([
|
|
901
|
-
isClaudeCodeAvailable().catch(() => false),
|
|
902
|
-
isCodexAvailable().catch(() => false),
|
|
903
|
-
]);
|
|
904
|
-
|
|
905
|
-
if (argTool) {
|
|
906
|
-
selectedTool = argTool;
|
|
907
|
-
if (selectedTool === "claude" && !localClaudeAvail) {
|
|
908
|
-
selectedTool = null;
|
|
909
|
-
printWarning("Requested tool is not available.");
|
|
910
|
-
} else if (selectedTool === "codex" && !localCodexAvail) {
|
|
911
|
-
selectedTool = null;
|
|
912
|
-
printWarning(
|
|
913
|
-
"Codex CLI was not detected. Install Bun and Codex before selecting it.",
|
|
914
|
-
);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
if (!selectedTool) {
|
|
919
|
-
selectedTool = await selectAITool({
|
|
920
|
-
claudeAvailable: localClaudeAvail,
|
|
921
|
-
codexAvailable: localCodexAvail,
|
|
922
|
-
});
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
if (!selectedTool) {
|
|
926
|
-
printInfo("No AI tool selected. Returning to menu.");
|
|
927
|
-
return true;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
if (!localClaudeAvail && !localCodexAvail) {
|
|
931
|
-
printError(
|
|
932
|
-
"No AI tools are available in PATH (Claude Code or Codex CLI).",
|
|
933
|
-
);
|
|
934
|
-
await confirmContinue("Press enter to continue...");
|
|
935
|
-
return true;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
if (selectedTool === "claude" && !localClaudeAvail) {
|
|
939
|
-
printError(
|
|
940
|
-
"Claude Code CLI not found in PATH. Install it before selecting this option.",
|
|
941
|
-
);
|
|
942
|
-
await confirmContinue("Press enter to continue...");
|
|
943
|
-
return true;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
if (selectedTool === "codex" && !localCodexAvail) {
|
|
947
|
-
printError(
|
|
948
|
-
"Codex CLI not detected via bunx. Install Bun and the Codex CLI before selecting this option.",
|
|
949
|
-
);
|
|
950
|
-
await confirmContinue("Press enter to continue...");
|
|
951
|
-
return true;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
const executionConfig = await selectClaudeExecutionMode(
|
|
955
|
-
selectedTool === "claude" ? "Claude Code" : "Codex CLI",
|
|
956
|
-
);
|
|
957
|
-
if (!executionConfig) return true;
|
|
958
|
-
const { mode, skipPermissions } = executionConfig;
|
|
959
|
-
|
|
960
|
-
try {
|
|
961
|
-
// Save session data before launching
|
|
962
|
-
const sessionData: SessionData = {
|
|
963
|
-
lastWorktreePath: worktreePath,
|
|
964
|
-
lastBranch: targetBranch,
|
|
965
|
-
timestamp: Date.now(),
|
|
966
|
-
repositoryRoot: repoRoot,
|
|
967
|
-
};
|
|
968
|
-
await saveSession(sessionData);
|
|
969
|
-
|
|
970
|
-
if (selectedTool === "claude") {
|
|
971
|
-
await launchClaudeCode(worktreePath, { mode, skipPermissions });
|
|
972
|
-
await handlePostClaudeChanges(worktreePath);
|
|
973
|
-
} else {
|
|
974
|
-
await launchCodexCLI(worktreePath, {
|
|
975
|
-
mode,
|
|
976
|
-
bypassApprovals: skipPermissions,
|
|
977
|
-
});
|
|
978
|
-
}
|
|
979
|
-
} catch (error) {
|
|
980
|
-
if (error instanceof ClaudeError || error instanceof CodexError) {
|
|
981
|
-
printError(error.message);
|
|
982
|
-
} else {
|
|
983
|
-
printError(
|
|
984
|
-
`Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
|
|
985
|
-
);
|
|
986
|
-
}
|
|
987
|
-
await confirmContinue("Press enter to continue...");
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
return true;
|
|
991
|
-
} catch (error) {
|
|
992
|
-
printError(
|
|
993
|
-
`Failed to create new branch: ${error instanceof Error ? error.message : String(error)}`,
|
|
994
|
-
);
|
|
995
|
-
await confirmContinue("Press enter to continue...");
|
|
996
|
-
return true;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
async function handleManageWorktrees(
|
|
1001
|
-
worktrees: WorktreeInfo[],
|
|
1002
|
-
): Promise<boolean> {
|
|
1003
|
-
try {
|
|
1004
|
-
if (worktrees.length === 0) {
|
|
1005
|
-
printInfo("No worktrees found.");
|
|
1006
|
-
if (await confirmContinue("Return to main menu?")) {
|
|
1007
|
-
return true;
|
|
1008
|
-
}
|
|
1009
|
-
return false;
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
while (true) {
|
|
1013
|
-
const worktreeChoices = worktrees.map((w) => ({
|
|
1014
|
-
branch: w.branch,
|
|
1015
|
-
path: w.path,
|
|
1016
|
-
}));
|
|
1017
|
-
const selectedWorktree =
|
|
1018
|
-
await selectWorktreeForManagement(worktreeChoices);
|
|
1019
|
-
|
|
1020
|
-
if (selectedWorktree === "back") {
|
|
1021
|
-
return true; // Return to main menu
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
const worktree = worktrees.find((w) => w.branch === selectedWorktree);
|
|
1025
|
-
if (!worktree) {
|
|
1026
|
-
printError("Worktree not found.");
|
|
1027
|
-
continue;
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
const action = await selectWorktreeAction();
|
|
1031
|
-
|
|
1032
|
-
switch (action) {
|
|
1033
|
-
case "open":
|
|
1034
|
-
// Check if worktree is accessible
|
|
1035
|
-
if (worktree.isAccessible === false) {
|
|
1036
|
-
printError("Cannot open inaccessible worktree");
|
|
1037
|
-
printInfo(`Path: ${worktree.path}`);
|
|
1038
|
-
printInfo(
|
|
1039
|
-
"This worktree was created in a different environment and is not accessible here.",
|
|
1040
|
-
);
|
|
1041
|
-
await confirmContinue("Press enter to continue...");
|
|
1042
|
-
break;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
// Select AI tool (reuse selection logic)
|
|
1046
|
-
const { selectAITool } = await import("./ui/legacy/prompts.js");
|
|
1047
|
-
const [localClaudeAvail, localCodexAvail] = await Promise.all([
|
|
1048
|
-
isClaudeCodeAvailable().catch(() => false),
|
|
1049
|
-
isCodexAvailable().catch(() => false),
|
|
1050
|
-
]);
|
|
1051
|
-
if (!localClaudeAvail && !localCodexAvail) {
|
|
1052
|
-
printError(
|
|
1053
|
-
"No AI tools are available in PATH (Claude Code or Codex CLI).",
|
|
1054
|
-
);
|
|
1055
|
-
await confirmContinue("Press enter to continue...");
|
|
1056
|
-
return true;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
const selectedTool = await selectAITool({
|
|
1060
|
-
claudeAvailable: localClaudeAvail,
|
|
1061
|
-
codexAvailable: localCodexAvail,
|
|
1062
|
-
});
|
|
1063
|
-
if (!selectedTool) {
|
|
1064
|
-
printInfo("No AI tool selected. Returning to menu.");
|
|
1065
|
-
return true;
|
|
1066
|
-
}
|
|
1067
|
-
if (selectedTool === "claude" && !localClaudeAvail) {
|
|
1068
|
-
printError(
|
|
1069
|
-
"Claude Code CLI not found in PATH. Install it before selecting this option.",
|
|
1070
|
-
);
|
|
1071
|
-
await confirmContinue("Press enter to continue...");
|
|
1072
|
-
return true;
|
|
1073
|
-
}
|
|
1074
|
-
if (selectedTool === "codex" && !localCodexAvail) {
|
|
1075
|
-
printError(
|
|
1076
|
-
"Codex CLI not detected via bunx. Install Bun and the Codex CLI before selecting this option.",
|
|
1077
|
-
);
|
|
1078
|
-
await confirmContinue("Press enter to continue...");
|
|
1079
|
-
return true;
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
// Ask execution mode
|
|
1083
|
-
const execCfg = await selectClaudeExecutionMode(
|
|
1084
|
-
selectedTool === "claude" ? "Claude Code" : "Codex CLI",
|
|
1085
|
-
);
|
|
1086
|
-
if (!execCfg) return true;
|
|
1087
|
-
const { mode, skipPermissions } = execCfg;
|
|
1088
|
-
|
|
1089
|
-
try {
|
|
1090
|
-
// Save session data before launching
|
|
1091
|
-
const sessionData: SessionData = {
|
|
1092
|
-
lastWorktreePath: worktree.path,
|
|
1093
|
-
lastBranch: worktree.branch,
|
|
1094
|
-
timestamp: Date.now(),
|
|
1095
|
-
repositoryRoot: await getRepositoryRoot(),
|
|
1096
|
-
};
|
|
1097
|
-
await saveSession(sessionData);
|
|
1098
|
-
|
|
1099
|
-
if (selectedTool === "claude") {
|
|
1100
|
-
await launchClaudeCode(worktree.path, { mode, skipPermissions });
|
|
1101
|
-
} else {
|
|
1102
|
-
await launchCodexCLI(worktree.path, {
|
|
1103
|
-
mode,
|
|
1104
|
-
bypassApprovals: skipPermissions,
|
|
1105
|
-
});
|
|
1106
|
-
}
|
|
1107
|
-
} catch (error) {
|
|
1108
|
-
if (error instanceof ClaudeError || error instanceof CodexError) {
|
|
1109
|
-
printError(error.message);
|
|
1110
|
-
} else {
|
|
1111
|
-
printError(
|
|
1112
|
-
`Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
|
|
1113
|
-
);
|
|
1114
|
-
}
|
|
1115
|
-
await confirmContinue("Press enter to continue...");
|
|
1116
|
-
}
|
|
1117
|
-
return true; // Return to main menu after opening
|
|
1118
|
-
|
|
1119
|
-
case "remove":
|
|
1120
|
-
if (worktree.isAccessible === false) {
|
|
1121
|
-
// Special handling for inaccessible worktrees
|
|
1122
|
-
const shouldRemove = await confirm({
|
|
1123
|
-
message:
|
|
1124
|
-
"This worktree is inaccessible. Do you want to remove it from Git's records?",
|
|
1125
|
-
default: false,
|
|
1126
|
-
});
|
|
1127
|
-
if (shouldRemove) {
|
|
1128
|
-
await removeWorktree(worktree.path, true); // Force removal
|
|
1129
|
-
printSuccess(`Worktree record removed: ${worktree.path}`);
|
|
1130
|
-
// Update worktrees list
|
|
1131
|
-
const index = worktrees.indexOf(worktree);
|
|
1132
|
-
worktrees.splice(index, 1);
|
|
1133
|
-
}
|
|
1134
|
-
} else {
|
|
1135
|
-
if (await confirmWorktreeRemoval(worktree.path)) {
|
|
1136
|
-
await removeWorktree(worktree.path);
|
|
1137
|
-
printSuccess(`Worktree removed: ${worktree.path}`);
|
|
1138
|
-
// Update worktrees list
|
|
1139
|
-
const index = worktrees.indexOf(worktree);
|
|
1140
|
-
worktrees.splice(index, 1);
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
break;
|
|
1144
|
-
|
|
1145
|
-
case "remove-branch":
|
|
1146
|
-
if (worktree.isAccessible === false) {
|
|
1147
|
-
// Special handling for inaccessible worktrees
|
|
1148
|
-
const shouldRemove = await confirm({
|
|
1149
|
-
message:
|
|
1150
|
-
"This worktree is inaccessible. Do you want to remove it from Git's records and delete the branch?",
|
|
1151
|
-
default: false,
|
|
1152
|
-
});
|
|
1153
|
-
if (shouldRemove) {
|
|
1154
|
-
await removeWorktree(worktree.path, true); // Force removal
|
|
1155
|
-
printSuccess(`Worktree record removed: ${worktree.path}`);
|
|
1156
|
-
|
|
1157
|
-
if (await confirmBranchRemoval(worktree.branch)) {
|
|
1158
|
-
await deleteBranch(worktree.branch, true); // Force delete
|
|
1159
|
-
printSuccess(`Branch deleted: ${worktree.branch}`);
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// Update worktrees list
|
|
1163
|
-
const index = worktrees.indexOf(worktree);
|
|
1164
|
-
worktrees.splice(index, 1);
|
|
1165
|
-
}
|
|
1166
|
-
} else {
|
|
1167
|
-
if (await confirmWorktreeRemoval(worktree.path)) {
|
|
1168
|
-
await removeWorktree(worktree.path);
|
|
1169
|
-
printSuccess(`Worktree removed: ${worktree.path}`);
|
|
1170
|
-
|
|
1171
|
-
if (await confirmBranchRemoval(worktree.branch)) {
|
|
1172
|
-
await deleteBranch(worktree.branch, true); // Force delete
|
|
1173
|
-
printSuccess(`Branch deleted: ${worktree.branch}`);
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
// Update worktrees list
|
|
1177
|
-
const index = worktrees.indexOf(worktree);
|
|
1178
|
-
worktrees.splice(index, 1);
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
break;
|
|
1182
|
-
|
|
1183
|
-
case "back":
|
|
1184
|
-
continue; // Continue worktree management loop
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
} catch (error) {
|
|
1188
|
-
printError(
|
|
1189
|
-
`Failed to manage worktrees: ${error instanceof Error ? error.message : String(error)}`,
|
|
1190
|
-
);
|
|
1191
|
-
await confirmContinue("Press enter to continue...");
|
|
1192
|
-
return true;
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
async function handleCleanupMergedPRs(): Promise<boolean> {
|
|
1197
|
-
try {
|
|
1198
|
-
// Check if GitHub CLI is available
|
|
1199
|
-
if (!(await isGitHubCLIAvailable())) {
|
|
1200
|
-
printError(
|
|
1201
|
-
"GitHub CLI is not installed. Please install it to use this feature.",
|
|
1202
|
-
);
|
|
1203
|
-
printInfo("Install GitHub CLI: https://cli.github.com/");
|
|
1204
|
-
return true;
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
// Check if authenticated
|
|
1208
|
-
if (!(await checkGitHubAuth())) {
|
|
1209
|
-
return true;
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
printInfo("Fetching latest changes from remote...");
|
|
1213
|
-
await fetchAllRemotes();
|
|
1214
|
-
|
|
1215
|
-
printInfo("Checking for merged pull requests...");
|
|
1216
|
-
const cleanupTargets = await getMergedPRWorktrees();
|
|
1217
|
-
|
|
1218
|
-
if (cleanupTargets.length === 0) {
|
|
1219
|
-
console.log(
|
|
1220
|
-
chalk.green(
|
|
1221
|
-
"✨ すべてクリーンです!クリーンアップが必要なworktreeはありません。",
|
|
1222
|
-
),
|
|
1223
|
-
);
|
|
1224
|
-
await confirmContinue("Press enter to continue...");
|
|
1225
|
-
return true;
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
// Display targets
|
|
1229
|
-
displayCleanupTargets(cleanupTargets);
|
|
1230
|
-
|
|
1231
|
-
// Select targets to clean up
|
|
1232
|
-
const selectedTargets = await selectCleanupTargets(cleanupTargets);
|
|
1233
|
-
|
|
1234
|
-
// Check if user pressed q to go back
|
|
1235
|
-
if (selectedTargets === null) {
|
|
1236
|
-
console.log(chalk.yellow("🔙 前の画面に戻ります。"));
|
|
1237
|
-
return true;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
if (selectedTargets.length === 0) {
|
|
1241
|
-
console.log(chalk.yellow("🚫 クリーンアップをキャンセルしました。"));
|
|
1242
|
-
return true;
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
// Confirm cleanup
|
|
1246
|
-
if (!(await confirmCleanup(selectedTargets))) {
|
|
1247
|
-
console.log(chalk.yellow("🚫 クリーンアップをキャンセルしました。"));
|
|
1248
|
-
return true;
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
// Check if there are branches with unpushed commits and ask about pushing
|
|
1252
|
-
const shouldPushUnpushed =
|
|
1253
|
-
await confirmPushUnpushedCommits(selectedTargets);
|
|
1254
|
-
|
|
1255
|
-
// Ask about remote branch deletion
|
|
1256
|
-
const deleteRemoteBranches =
|
|
1257
|
-
await confirmRemoteBranchDeletion(selectedTargets);
|
|
1258
|
-
|
|
1259
|
-
// Perform cleanup
|
|
1260
|
-
const results: Array<{
|
|
1261
|
-
target: CleanupTarget;
|
|
1262
|
-
success: boolean;
|
|
1263
|
-
error?: string;
|
|
1264
|
-
}> = [];
|
|
1265
|
-
|
|
1266
|
-
for (const target of selectedTargets) {
|
|
1267
|
-
try {
|
|
1268
|
-
// Push unpushed commits if requested and needed (only for worktree targets)
|
|
1269
|
-
if (
|
|
1270
|
-
shouldPushUnpushed &&
|
|
1271
|
-
target.hasUnpushedCommits &&
|
|
1272
|
-
target.cleanupType === "worktree-and-branch"
|
|
1273
|
-
) {
|
|
1274
|
-
printInfo(`Pushing unpushed commits in branch: ${target.branch}`);
|
|
1275
|
-
try {
|
|
1276
|
-
await pushBranchToRemote(target.worktreePath!, target.branch);
|
|
1277
|
-
printSuccess(
|
|
1278
|
-
`Successfully pushed changes for branch: ${target.branch}`,
|
|
1279
|
-
);
|
|
1280
|
-
} catch (error) {
|
|
1281
|
-
printWarning(
|
|
1282
|
-
`Failed to push branch ${target.branch}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1283
|
-
);
|
|
1284
|
-
|
|
1285
|
-
// Ask user if they want to proceed without pushing
|
|
1286
|
-
if (!(await confirmProceedWithoutPush(target.branch))) {
|
|
1287
|
-
printInfo(`Skipping deletion of branch: ${target.branch}`);
|
|
1288
|
-
continue; // Skip this target
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
// Handle different cleanup types
|
|
1294
|
-
if (target.cleanupType === "worktree-and-branch") {
|
|
1295
|
-
printInfo(`Removing worktree: ${target.worktreePath}`);
|
|
1296
|
-
await removeWorktree(target.worktreePath!, true); // Force remove
|
|
1297
|
-
|
|
1298
|
-
printInfo(`Deleting local branch: ${target.branch}`);
|
|
1299
|
-
await deleteBranch(target.branch, true); // Force delete
|
|
1300
|
-
} else if (target.cleanupType === "branch-only") {
|
|
1301
|
-
printInfo(`Deleting local branch: ${target.branch}`);
|
|
1302
|
-
await deleteBranch(target.branch, true); // Force delete
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
if (deleteRemoteBranches && target.hasRemoteBranch) {
|
|
1306
|
-
printInfo(`Deleting remote branch: origin/${target.branch}`);
|
|
1307
|
-
try {
|
|
1308
|
-
await deleteRemoteBranch(target.branch);
|
|
1309
|
-
printSuccess(
|
|
1310
|
-
`Successfully deleted remote branch: origin/${target.branch}`,
|
|
1311
|
-
);
|
|
1312
|
-
} catch (error) {
|
|
1313
|
-
// リモートブランチの削除に失敗してもローカルの削除は成功として扱う
|
|
1314
|
-
printWarning(
|
|
1315
|
-
`Failed to delete remote branch: ${error instanceof Error ? error.message : String(error)}`,
|
|
1316
|
-
);
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
results.push({ target, success: true });
|
|
1321
|
-
} catch (error) {
|
|
1322
|
-
results.push({
|
|
1323
|
-
target,
|
|
1324
|
-
success: false,
|
|
1325
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1326
|
-
});
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
// Display results
|
|
1331
|
-
displayCleanupResults(results);
|
|
1332
|
-
|
|
1333
|
-
return true;
|
|
1334
|
-
} catch (error) {
|
|
1335
|
-
printError(
|
|
1336
|
-
`Failed to cleanup merged PRs: ${error instanceof Error ? error.message : String(error)}`,
|
|
1337
|
-
);
|
|
1338
|
-
await confirmContinue("Press enter to continue...");
|
|
1339
|
-
return true;
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
async function handlePostClaudeChanges(worktreePath: string): Promise<void> {
|
|
1344
|
-
try {
|
|
1345
|
-
// 正確なブランチ名を取得
|
|
1346
|
-
const branchName = await getCurrentBranchName(worktreePath);
|
|
1347
|
-
const isReleaseBranch = branchName.startsWith("release/");
|
|
1348
|
-
|
|
1349
|
-
// リリースブランチの場合は特別な処理
|
|
1350
|
-
if (isReleaseBranch) {
|
|
1351
|
-
// 変更がある場合は自動的にコミット
|
|
1352
|
-
if (await hasUncommittedChanges(worktreePath)) {
|
|
1353
|
-
const version = branchName.replace("release/", "");
|
|
1354
|
-
const commitMessage = `chore: prepare release ${version}`;
|
|
1355
|
-
printInfo(`Committing release changes: ${commitMessage}`);
|
|
1356
|
-
await commitChanges(worktreePath, commitMessage);
|
|
1357
|
-
printSuccess("Release changes committed successfully!");
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
// リリースアクションを選択
|
|
1361
|
-
const action = await selectReleaseAction();
|
|
1362
|
-
|
|
1363
|
-
switch (action) {
|
|
1364
|
-
case "complete":
|
|
1365
|
-
try {
|
|
1366
|
-
await pushBranchToRemote(worktreePath, branchName);
|
|
1367
|
-
printSuccess(`Pushed release branch: ${branchName}`);
|
|
1368
|
-
|
|
1369
|
-
// GitHub CLIが利用可能か確認
|
|
1370
|
-
if (await isGitHubCLIAvailable()) {
|
|
1371
|
-
const version = branchName.replace("release/", "");
|
|
1372
|
-
printInfo("\nCreating pull request...");
|
|
1373
|
-
|
|
1374
|
-
try {
|
|
1375
|
-
const { execa } = await import("execa");
|
|
1376
|
-
const prTitle = `Release v${version}`;
|
|
1377
|
-
const prBody = `## Release v${version}\n\nThis PR contains the release preparation for version ${version}.\n\n### Release Checklist\n- [ ] Review changes\n- [ ] Update changelog if needed\n- [ ] Merge to main\n- [ ] Create tag v${version}\n- [ ] Merge back to develop`;
|
|
1378
|
-
|
|
1379
|
-
const { stdout } = await execa(
|
|
1380
|
-
"gh",
|
|
1381
|
-
[
|
|
1382
|
-
"pr",
|
|
1383
|
-
"create",
|
|
1384
|
-
"--base",
|
|
1385
|
-
"main",
|
|
1386
|
-
"--head",
|
|
1387
|
-
branchName,
|
|
1388
|
-
"--title",
|
|
1389
|
-
prTitle,
|
|
1390
|
-
"--body",
|
|
1391
|
-
prBody,
|
|
1392
|
-
],
|
|
1393
|
-
{ cwd: worktreePath },
|
|
1394
|
-
);
|
|
1395
|
-
|
|
1396
|
-
printSuccess("Pull request created successfully!");
|
|
1397
|
-
printInfo(stdout);
|
|
1398
|
-
|
|
1399
|
-
// リリースブランチのworktreeとローカルブランチを削除
|
|
1400
|
-
printInfo("\nCleaning up release worktree and local branch...");
|
|
1401
|
-
try {
|
|
1402
|
-
await removeWorktree(worktreePath, true);
|
|
1403
|
-
printSuccess("Release worktree removed successfully.");
|
|
1404
|
-
|
|
1405
|
-
// ローカルブランチも削除(リモートブランチは残す)
|
|
1406
|
-
await deleteBranch(branchName, true);
|
|
1407
|
-
printSuccess(
|
|
1408
|
-
`Local branch ${branchName} deleted successfully.`,
|
|
1409
|
-
);
|
|
1410
|
-
|
|
1411
|
-
printInfo(
|
|
1412
|
-
"\nRelease process initiated. The PR is ready for review.",
|
|
1413
|
-
);
|
|
1414
|
-
printInfo("Remote branch is preserved for the PR.");
|
|
1415
|
-
} catch (error) {
|
|
1416
|
-
printWarning(
|
|
1417
|
-
"Failed to clean up. Please remove worktree/branch manually.",
|
|
1418
|
-
);
|
|
1419
|
-
}
|
|
1420
|
-
} catch (error) {
|
|
1421
|
-
printWarning(
|
|
1422
|
-
"Failed to create PR automatically. Please create it manually.",
|
|
1423
|
-
);
|
|
1424
|
-
printInfo("\nGit Flow Release Process:");
|
|
1425
|
-
printInfo("1. Create a PR to main branch");
|
|
1426
|
-
printInfo("2. After merge, create a tag on main branch");
|
|
1427
|
-
printInfo("3. Merge back to develop branch");
|
|
1428
|
-
}
|
|
1429
|
-
} else {
|
|
1430
|
-
printInfo(
|
|
1431
|
-
"\nGitHub CLI not found. Please create the PR manually:",
|
|
1432
|
-
);
|
|
1433
|
-
printInfo("1. Create a PR to main branch");
|
|
1434
|
-
printInfo("2. After merge, create a tag on main branch");
|
|
1435
|
-
printInfo("3. Merge back to develop branch");
|
|
1436
|
-
}
|
|
1437
|
-
} catch (error) {
|
|
1438
|
-
printError(
|
|
1439
|
-
`Failed to push: ${error instanceof Error ? error.message : String(error)}`,
|
|
1440
|
-
);
|
|
1441
|
-
}
|
|
1442
|
-
break;
|
|
1443
|
-
|
|
1444
|
-
case "continue":
|
|
1445
|
-
printInfo(
|
|
1446
|
-
"Release branch saved. You can continue working on it later.",
|
|
1447
|
-
);
|
|
1448
|
-
break;
|
|
1449
|
-
|
|
1450
|
-
case "nothing":
|
|
1451
|
-
// Just exit
|
|
1452
|
-
break;
|
|
1453
|
-
}
|
|
1454
|
-
return;
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
// 通常のブランチの場合は従来の処理
|
|
1458
|
-
// Check if there are uncommitted changes
|
|
1459
|
-
if (!(await hasUncommittedChanges(worktreePath))) {
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
while (true) {
|
|
1464
|
-
const action = await selectChangesAction();
|
|
1465
|
-
|
|
1466
|
-
switch (action) {
|
|
1467
|
-
case "status":
|
|
1468
|
-
const status = await showStatus(worktreePath);
|
|
1469
|
-
console.log("\n" + status + "\n");
|
|
1470
|
-
await confirmContinue("Press enter to continue...");
|
|
1471
|
-
break;
|
|
1472
|
-
|
|
1473
|
-
case "commit":
|
|
1474
|
-
const commitMessage = await inputCommitMessage();
|
|
1475
|
-
await commitChanges(worktreePath, commitMessage);
|
|
1476
|
-
printSuccess("Changes committed successfully!");
|
|
1477
|
-
|
|
1478
|
-
// リリースブランチの場合は、リリースアクションを選択
|
|
1479
|
-
if (isReleaseBranch) {
|
|
1480
|
-
const action = await selectReleaseAction();
|
|
1481
|
-
|
|
1482
|
-
switch (action) {
|
|
1483
|
-
case "complete":
|
|
1484
|
-
try {
|
|
1485
|
-
await pushBranchToRemote(worktreePath, branchName);
|
|
1486
|
-
printSuccess(`Pushed release branch: ${branchName}`);
|
|
1487
|
-
printInfo("\nGit Flow Release Process:");
|
|
1488
|
-
printInfo("1. Create a PR to main branch");
|
|
1489
|
-
printInfo("2. After merge, create a tag on main branch");
|
|
1490
|
-
printInfo("3. Merge back to develop branch");
|
|
1491
|
-
printInfo("\nUse GitHub/GitLab to create the PR.");
|
|
1492
|
-
} catch (error) {
|
|
1493
|
-
printError(
|
|
1494
|
-
`Failed to push: ${error instanceof Error ? error.message : String(error)}`,
|
|
1495
|
-
);
|
|
1496
|
-
}
|
|
1497
|
-
break;
|
|
1498
|
-
|
|
1499
|
-
case "continue":
|
|
1500
|
-
printInfo(
|
|
1501
|
-
"Release branch saved with your commits. You can continue working on it later.",
|
|
1502
|
-
);
|
|
1503
|
-
break;
|
|
1504
|
-
|
|
1505
|
-
case "nothing":
|
|
1506
|
-
// Just exit
|
|
1507
|
-
break;
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
return;
|
|
1511
|
-
|
|
1512
|
-
case "stash":
|
|
1513
|
-
await stashChanges(worktreePath, "Stashed by Worktree Manager");
|
|
1514
|
-
printSuccess("Changes stashed successfully!");
|
|
1515
|
-
return;
|
|
1516
|
-
|
|
1517
|
-
case "discard":
|
|
1518
|
-
if (await confirmDiscardChanges()) {
|
|
1519
|
-
await discardAllChanges(worktreePath);
|
|
1520
|
-
printSuccess("All changes discarded.");
|
|
1521
|
-
return;
|
|
1522
|
-
}
|
|
1523
|
-
break;
|
|
1524
|
-
|
|
1525
|
-
case "continue":
|
|
1526
|
-
return;
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
} catch (error) {
|
|
1530
|
-
printError(
|
|
1531
|
-
`Failed to handle changes: ${error instanceof Error ? error.message : String(error)}`,
|
|
1532
|
-
);
|
|
1533
|
-
await confirmContinue("Press enter to continue...");
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
// Run the application if this module is executed directly
|
|
1538
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
1539
|
-
main().catch((error) => {
|
|
1540
|
-
console.error("Fatal error:", error);
|
|
1541
|
-
process.exit(1);
|
|
1542
|
-
});
|
|
1543
|
-
}
|