@akiojin/claude-worktree
Version:
Interactive Git worktree manager for Claude Code with graphical branch selection
817 lines (813 loc) • 41.2 kB
JavaScript
import { confirm } from '@inquirer/prompts';
import { isGitRepository, getAllBranches, branchExists, getRepositoryRoot, deleteBranch, deleteRemoteBranch, hasUncommittedChanges, showStatus, stashChanges, discardAllChanges, commitChanges, fetchAllRemotes, pushBranchToRemote, isInWorktree, GitError, getCurrentVersion, calculateNewVersion, executeNpmVersionInWorktree, getCurrentBranchName } from './git.js';
import { listAdditionalWorktrees, worktreeExists, generateWorktreePath, createWorktree, removeWorktree, getMergedPRWorktrees, WorktreeError } from './worktree.js';
import { launchClaudeCode, isClaudeCodeAvailable, ClaudeError } from './claude.js';
import { selectFromTable, selectBaseBranch, confirmWorktreeCreation, confirmSkipPermissions, selectWorktreeForManagement, selectWorktreeAction, confirmWorktreeRemoval, confirmBranchRemoval, selectChangesAction, inputCommitMessage, confirmDiscardChanges, confirmContinue, selectCleanupTargets, confirmCleanup, confirmRemoteBranchDeletion, confirmPushUnpushedCommits, confirmProceedWithoutPush, selectSession, selectClaudeExecutionMode, selectVersionBumpType, selectReleaseAction } from './ui/prompts.js';
import { printError, printSuccess, printInfo, printWarning, printExit, displayCleanupTargets, displayCleanupResults } from './ui/display.js';
import { createBranchTable } from './ui/table.js';
import chalk from 'chalk';
import { isGitHubCLIAvailable, checkGitHubAuth } from './github.js';
import { AppError, setupExitHandlers, handleUserCancel } from './utils.js';
import { loadSession, saveSession, getAllSessions } from './config/index.js';
function showHelp() {
console.log(`
Claude Worktree Manager
Usage: claude-worktree [options]
Options:
-c Continue from the last session (automatically open the last used worktree)
-r, --resume Resume a session - interactively select from available sessions
-h, --help Show this help message
Description:
Interactive Git worktree manager for Claude Code with graphical branch selection.
Without options: Opens the interactive menu to select branches and manage worktrees.
With -c option: Automatically continues from where you left off in the last session.
With -r option: Shows a list of recent sessions to choose from and resume.
`);
}
export async function main() {
try {
// Parse command line arguments
const args = process.argv.slice(2);
const continueLastSession = args.includes('-c');
const resumeSession = args.includes('-r') || args.includes('--resume');
const showHelpFlag = args.includes('-h') || args.includes('--help');
// Show help if requested
if (showHelpFlag) {
showHelp();
return;
}
// Setup graceful exit handlers
setupExitHandlers();
// Check if current directory is a Git repository
if (!(await isGitRepository())) {
printError('Current directory is not a Git repository.');
process.exit(1);
}
// Check if running from a worktree directory
if (await isInWorktree()) {
printWarning('Running from a worktree directory is not recommended.');
printInfo('Please run this command from the main repository root to avoid path issues.');
printInfo('You can continue, but some operations may not work correctly.');
const shouldContinue = await confirmContinue('Do you want to continue anyway?');
if (!shouldContinue) {
process.exit(0);
}
}
// Check if Claude Code is available
if (!(await isClaudeCodeAvailable())) {
printWarning('Claude Code CLI not found. Make sure it\'s installed and in your PATH.');
printInfo('You can install it from: https://claude.ai/code');
}
// Get repository root
const repoRoot = await getRepositoryRoot();
// Handle continue last session option
if (continueLastSession) {
const sessionData = await loadSession(repoRoot);
if (sessionData && sessionData.lastWorktreePath) {
printInfo(`Continuing last session: ${sessionData.lastBranch} (${sessionData.lastWorktreePath})`);
// Check if worktree still exists
if (await worktreeExists(sessionData.lastBranch)) {
const skipPermissions = await confirmSkipPermissions();
try {
await launchClaudeCode(sessionData.lastWorktreePath, { skipPermissions });
await handlePostClaudeChanges(sessionData.lastWorktreePath);
}
catch (error) {
if (error instanceof ClaudeError) {
printError(`Failed to launch Claude Code: ${error.message}`);
}
else {
printError(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
}
await confirmContinue('Press enter to continue...');
}
return; // Exit after continuing session
}
else {
printWarning(`Last session worktree no longer exists: ${sessionData.lastWorktreePath}`);
printInfo('Falling back to normal flow...');
}
}
else {
printInfo('No previous session found. Starting normally...');
}
}
// Handle resume session option
if (resumeSession) {
const allSessions = await getAllSessions();
if (allSessions.length === 0) {
printInfo('No previous sessions found. Starting normally...');
}
else {
const selectedSession = await selectSession(allSessions);
if (selectedSession && selectedSession.lastWorktreePath) {
printInfo(`Resuming session: ${selectedSession.lastBranch} (${selectedSession.lastWorktreePath})`);
// Check if worktree still exists
if (await worktreeExists(selectedSession.lastBranch)) {
const skipPermissions = await confirmSkipPermissions();
try {
await launchClaudeCode(selectedSession.lastWorktreePath, { skipPermissions });
await handlePostClaudeChanges(selectedSession.lastWorktreePath);
}
catch (error) {
if (error instanceof ClaudeError) {
printError(`Failed to launch Claude Code: ${error.message}`);
}
else {
printError(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
}
await confirmContinue('Press enter to continue...');
}
return; // Exit after resuming session
}
else {
printWarning(`Selected session worktree no longer exists: ${selectedSession.lastWorktreePath}`);
printInfo('Falling back to normal flow...');
}
}
else {
printInfo('No session selected. Starting normally...');
}
}
}
// Main application loop
while (true) {
try {
// Get current repository state
const [branches, worktrees] = await Promise.all([
getAllBranches(),
listAdditionalWorktrees()
]);
// Create and display table
const choices = await createBranchTable(branches, worktrees);
// Get user selection with statistics
const selection = await selectFromTable(choices, { branches, worktrees });
// Handle selection
const shouldContinue = await handleSelection(selection, branches, worktrees, repoRoot);
if (!shouldContinue) {
break;
}
}
catch (error) {
handleUserCancel(error);
}
}
}
catch (error) {
if (error instanceof GitError) {
printError(`Git error: ${error.message}`);
}
else if (error instanceof WorktreeError) {
printError(`Worktree error: ${error.message}`);
}
else if (error instanceof ClaudeError) {
printError(`Claude Code error: ${error.message}`);
if (error.cause && error.cause instanceof Error) {
printError(`Cause: ${error.cause.message}`);
}
}
else if (error instanceof AppError) {
printError(`Application error: ${error.message}`);
}
else {
printError(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
}
process.exit(1);
}
}
async function handleSelection(selection, branches, worktrees, repoRoot) {
switch (selection) {
case '__exit__':
printExit();
return false;
case '__create_new__':
return await handleCreateNewBranch(branches, repoRoot);
case '__manage_worktrees__':
return await handleManageWorktrees(worktrees);
case '__cleanup_prs__':
return await handleCleanupMergedPRs();
default:
// Handle branch selection
return await handleBranchSelection(selection, repoRoot);
}
}
async function handleBranchSelection(branchName, repoRoot) {
try {
// Check if this is a remote branch
const isRemoteBranch = branchName.startsWith('origin/');
let localBranchName = branchName;
let targetBranch = branchName;
if (isRemoteBranch) {
// Extract local branch name from remote branch
localBranchName = branchName.replace(/^origin\//, '');
targetBranch = localBranchName;
}
// Check if worktree exists (using local branch name)
let worktreePath = await worktreeExists(targetBranch);
if (worktreePath) {
printInfo(`Opening existing worktree: ${worktreePath}`);
}
else {
// Create new worktree
worktreePath = await generateWorktreePath(repoRoot, targetBranch);
if (!(await confirmWorktreeCreation(targetBranch, worktreePath))) {
printInfo('Operation cancelled.');
return true; // Continue to main menu
}
let isNewBranch = false;
let baseBranch = targetBranch;
if (isRemoteBranch) {
// Check if local branch exists
const localExists = await branchExists(localBranchName);
if (!localExists) {
// Need to create new local branch from remote
isNewBranch = true;
baseBranch = branchName; // Use full remote branch name as base
}
}
const worktreeConfig = {
branchName: targetBranch,
worktreePath,
repoRoot,
isNewBranch,
baseBranch
};
printInfo(`Creating worktree for "${targetBranch}"...`);
await createWorktree(worktreeConfig);
printSuccess(`Worktree created at: ${worktreePath}`);
}
// Check if Claude Code is available before launching
if (await isClaudeCodeAvailable()) {
// Ask about execution mode
const executionConfig = await selectClaudeExecutionMode();
if (!executionConfig) {
// User cancelled, return to main menu
return true;
}
const { mode, skipPermissions } = executionConfig;
try {
// Save session data before launching Claude Code
const sessionData = {
lastWorktreePath: worktreePath,
lastBranch: targetBranch,
timestamp: Date.now(),
repositoryRoot: repoRoot
};
await saveSession(sessionData);
await launchClaudeCode(worktreePath, { mode, skipPermissions });
// Check for changes after Claude Code exits
await handlePostClaudeChanges(worktreePath);
}
catch (error) {
if (error instanceof ClaudeError) {
printError(`Failed to launch Claude Code: ${error.message}`);
if (error.message.includes('command not found')) {
printInfo('Install with: pnpm add -g @anthropic-ai/claude-code');
}
}
else {
printError(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
}
await confirmContinue('Press enter to continue...');
}
}
else {
printError('Claude Code is not available. Please install it first.');
printInfo('Install with: pnpm add -g @anthropic-ai/claude-code');
await confirmContinue('Press enter to continue...');
}
// After handling changes, return to main menu
return true;
}
catch (error) {
printError(`Failed to handle branch selection: ${error instanceof Error ? error.message : String(error)}`);
await confirmContinue('Press enter to continue...');
return true;
}
}
async function handleCreateNewBranch(branches, repoRoot) {
try {
// まず、ブランチタイプのみを選択
const { selectBranchType } = await import('./ui/prompts.js');
const branchType = await selectBranchType();
let targetBranch;
let baseBranch;
// リリースブランチの場合は特別な処理
if (branchType === 'release') {
// Git flowではリリースブランチはdevelopから分岐するが、
// developブランチがない場合はmainブランチから分岐
const developBranch = branches.find(b => b.type === 'local' && (b.name === 'develop' || b.name === 'dev'));
if (developBranch) {
baseBranch = developBranch.name;
printInfo(`Creating release branch from ${baseBranch} (Git Flow)`);
}
else {
// developがない場合はmain/masterから分岐
const mainBranch = branches.find(b => b.type === 'local' && (b.name === 'main' || b.name === 'master'));
if (!mainBranch) {
printError('No develop, main, or master branch found.');
return true;
}
baseBranch = mainBranch.name;
printWarning(`No develop branch found. Creating release branch from ${baseBranch}`);
}
// 現在のバージョンを取得
const currentVersion = await getCurrentVersion(repoRoot);
// バージョンバンプタイプを選択
const versionBump = await selectVersionBumpType(currentVersion);
// 新しいバージョンを計算
const newVersion = calculateNewVersion(currentVersion, versionBump);
// リリースブランチ名を生成
targetBranch = `release/${newVersion}`;
printInfo(`Release branch will be: ${targetBranch}`);
}
else {
// 通常のブランチの場合
const { inputBranchName } = await import('./ui/prompts.js');
const taskName = await inputBranchName(branchType);
targetBranch = `${branchType}/${taskName}`;
baseBranch = await selectBaseBranch(branches);
}
// Check if branch already exists
if (await branchExists(targetBranch)) {
printError(`Branch "${targetBranch}" already exists.`);
if (await confirmContinue('Return to main menu?')) {
return true;
}
return false;
}
printInfo(`Creating new branch "${targetBranch}" from "${baseBranch}"`);
// Create worktree path
const worktreePath = await generateWorktreePath(repoRoot, targetBranch);
if (!(await confirmWorktreeCreation(targetBranch, worktreePath))) {
printInfo('Operation cancelled.');
return true;
}
// Create worktree configuration
const worktreeConfig = {
branchName: targetBranch,
worktreePath,
repoRoot,
isNewBranch: true,
baseBranch
};
// Create worktree
printInfo(`Creating worktree for "${targetBranch}"...`);
await createWorktree(worktreeConfig);
printSuccess(`Worktree created at: ${worktreePath}`);
// リリースブランチの場合、worktree作成後にnpm versionを実行
if (branchType === 'release') {
printInfo('Updating version in release branch...');
try {
const newVersion = targetBranch.replace('release/', '');
await executeNpmVersionInWorktree(worktreePath, newVersion);
printSuccess(`Version updated to ${newVersion} in release branch`);
}
catch (error) {
printError(`Failed to update version: ${error instanceof Error ? error.message : String(error)}`);
// エラーが発生してもworktreeは作成済みなので続行
}
}
// Check if Claude Code is available before launching
if (await isClaudeCodeAvailable()) {
// Ask about execution mode
const executionConfig = await selectClaudeExecutionMode();
if (!executionConfig) {
// User cancelled, return to main menu
return true;
}
const { mode, skipPermissions } = executionConfig;
try {
// Save session data before launching Claude Code
const sessionData = {
lastWorktreePath: worktreePath,
lastBranch: targetBranch,
timestamp: Date.now(),
repositoryRoot: repoRoot
};
await saveSession(sessionData);
await launchClaudeCode(worktreePath, { mode, skipPermissions });
// Check for changes after Claude Code exits
await handlePostClaudeChanges(worktreePath);
}
catch (error) {
if (error instanceof ClaudeError) {
printError(`Failed to launch Claude Code: ${error.message}`);
if (error.message.includes('command not found')) {
printInfo('Install with: pnpm add -g @anthropic-ai/claude-code');
}
}
else {
printError(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
}
await confirmContinue('Press enter to continue...');
}
}
else {
printError('Claude Code is not available. Please install it first.');
printInfo('Install with: pnpm add -g @anthropic-ai/claude-code');
await confirmContinue('Press enter to continue...');
}
return true;
}
catch (error) {
printError(`Failed to create new branch: ${error instanceof Error ? error.message : String(error)}`);
await confirmContinue('Press enter to continue...');
return true;
}
}
async function handleManageWorktrees(worktrees) {
try {
if (worktrees.length === 0) {
printInfo('No worktrees found.');
if (await confirmContinue('Return to main menu?')) {
return true;
}
return false;
}
while (true) {
const worktreeChoices = worktrees.map(w => ({ branch: w.branch, path: w.path }));
const selectedWorktree = await selectWorktreeForManagement(worktreeChoices);
if (selectedWorktree === 'back') {
return true; // Return to main menu
}
const worktree = worktrees.find(w => w.branch === selectedWorktree);
if (!worktree) {
printError('Worktree not found.');
continue;
}
const action = await selectWorktreeAction();
switch (action) {
case 'open':
// Check if worktree is accessible
if (worktree.isAccessible === false) {
printError('Cannot open inaccessible worktree in Claude Code');
printInfo(`Path: ${worktree.path}`);
printInfo('This worktree was created in a different environment and is not accessible here.');
await confirmContinue('Press enter to continue...');
break;
}
// Check if Claude Code is available before launching
if (await isClaudeCodeAvailable()) {
// Ask about execution mode
const executionConfig = await selectClaudeExecutionMode();
if (!executionConfig) {
// User cancelled, return to main menu
return true;
}
const { mode, skipPermissions } = executionConfig;
try {
// Save session data before launching Claude Code
const sessionData = {
lastWorktreePath: worktree.path,
lastBranch: worktree.branch,
timestamp: Date.now(),
repositoryRoot: await getRepositoryRoot()
};
await saveSession(sessionData);
await launchClaudeCode(worktree.path, { mode, skipPermissions });
}
catch (error) {
if (error instanceof ClaudeError) {
printError(`Failed to launch Claude Code: ${error.message}`);
if (error.message.includes('command not found')) {
printInfo('Install with: pnpm add -g @anthropic-ai/claude-code');
}
}
else {
printError(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
}
await confirmContinue('Press enter to continue...');
}
}
else {
printError('Claude Code is not available. Please install it first.');
printInfo('Install with: pnpm add -g @anthropic-ai/claude-code');
await confirmContinue('Press enter to continue...');
}
return true; // Return to main menu after opening
case 'remove':
if (worktree.isAccessible === false) {
// Special handling for inaccessible worktrees
const shouldRemove = await confirm({
message: 'This worktree is inaccessible. Do you want to remove it from Git\'s records?',
default: false
});
if (shouldRemove) {
await removeWorktree(worktree.path, true); // Force removal
printSuccess(`Worktree record removed: ${worktree.path}`);
// Update worktrees list
const index = worktrees.indexOf(worktree);
worktrees.splice(index, 1);
}
}
else {
if (await confirmWorktreeRemoval(worktree.path)) {
await removeWorktree(worktree.path);
printSuccess(`Worktree removed: ${worktree.path}`);
// Update worktrees list
const index = worktrees.indexOf(worktree);
worktrees.splice(index, 1);
}
}
break;
case 'remove-branch':
if (worktree.isAccessible === false) {
// Special handling for inaccessible worktrees
const shouldRemove = await confirm({
message: 'This worktree is inaccessible. Do you want to remove it from Git\'s records and delete the branch?',
default: false
});
if (shouldRemove) {
await removeWorktree(worktree.path, true); // Force removal
printSuccess(`Worktree record removed: ${worktree.path}`);
if (await confirmBranchRemoval(worktree.branch)) {
await deleteBranch(worktree.branch, true); // Force delete
printSuccess(`Branch deleted: ${worktree.branch}`);
}
// Update worktrees list
const index = worktrees.indexOf(worktree);
worktrees.splice(index, 1);
}
}
else {
if (await confirmWorktreeRemoval(worktree.path)) {
await removeWorktree(worktree.path);
printSuccess(`Worktree removed: ${worktree.path}`);
if (await confirmBranchRemoval(worktree.branch)) {
await deleteBranch(worktree.branch, true); // Force delete
printSuccess(`Branch deleted: ${worktree.branch}`);
}
// Update worktrees list
const index = worktrees.indexOf(worktree);
worktrees.splice(index, 1);
}
}
break;
case 'back':
continue; // Continue worktree management loop
}
}
}
catch (error) {
printError(`Failed to manage worktrees: ${error instanceof Error ? error.message : String(error)}`);
await confirmContinue('Press enter to continue...');
return true;
}
}
async function handleCleanupMergedPRs() {
try {
// Check if GitHub CLI is available
if (!(await isGitHubCLIAvailable())) {
printError('GitHub CLI is not installed. Please install it to use this feature.');
printInfo('Install GitHub CLI: https://cli.github.com/');
return true;
}
// Check if authenticated
if (!(await checkGitHubAuth())) {
return true;
}
printInfo('Fetching latest changes from remote...');
await fetchAllRemotes();
printInfo('Checking for merged pull requests...');
const cleanupTargets = await getMergedPRWorktrees();
if (cleanupTargets.length === 0) {
console.log(chalk.green('✨ すべてクリーンです!クリーンアップが必要なworktreeはありません。'));
await confirmContinue('Press enter to continue...');
return true;
}
// Display targets
displayCleanupTargets(cleanupTargets);
// Select targets to clean up
const selectedTargets = await selectCleanupTargets(cleanupTargets);
if (selectedTargets.length === 0) {
console.log(chalk.yellow('🚫 クリーンアップをキャンセルしました。'));
return true;
}
// Confirm cleanup
if (!(await confirmCleanup(selectedTargets))) {
console.log(chalk.yellow('🚫 クリーンアップをキャンセルしました。'));
return true;
}
// Check if there are branches with unpushed commits and ask about pushing
const shouldPushUnpushed = await confirmPushUnpushedCommits(selectedTargets);
// Ask about remote branch deletion
const deleteRemoteBranches = await confirmRemoteBranchDeletion(selectedTargets);
// Perform cleanup
const results = [];
for (const target of selectedTargets) {
try {
// Push unpushed commits if requested and needed (only for worktree targets)
if (shouldPushUnpushed && target.hasUnpushedCommits && target.cleanupType === 'worktree-and-branch') {
printInfo(`Pushing unpushed commits in branch: ${target.branch}`);
try {
await pushBranchToRemote(target.worktreePath, target.branch);
printSuccess(`Successfully pushed changes for branch: ${target.branch}`);
}
catch (error) {
printWarning(`Failed to push branch ${target.branch}: ${error instanceof Error ? error.message : String(error)}`);
// Ask user if they want to proceed without pushing
if (!(await confirmProceedWithoutPush(target.branch))) {
printInfo(`Skipping deletion of branch: ${target.branch}`);
continue; // Skip this target
}
}
}
// Handle different cleanup types
if (target.cleanupType === 'worktree-and-branch') {
printInfo(`Removing worktree: ${target.worktreePath}`);
await removeWorktree(target.worktreePath, true); // Force remove
printInfo(`Deleting local branch: ${target.branch}`);
await deleteBranch(target.branch, true); // Force delete
}
else if (target.cleanupType === 'branch-only') {
printInfo(`Deleting local branch: ${target.branch}`);
await deleteBranch(target.branch, true); // Force delete
}
if (deleteRemoteBranches && target.hasRemoteBranch) {
printInfo(`Deleting remote branch: origin/${target.branch}`);
try {
await deleteRemoteBranch(target.branch);
printSuccess(`Successfully deleted remote branch: origin/${target.branch}`);
}
catch (error) {
// リモートブランチの削除に失敗してもローカルの削除は成功として扱う
printWarning(`Failed to delete remote branch: ${error instanceof Error ? error.message : String(error)}`);
}
}
results.push({ target, success: true });
}
catch (error) {
results.push({
target,
success: false,
error: error instanceof Error ? error.message : String(error)
});
}
}
// Display results
displayCleanupResults(results);
return true;
}
catch (error) {
printError(`Failed to cleanup merged PRs: ${error instanceof Error ? error.message : String(error)}`);
await confirmContinue('Press enter to continue...');
return true;
}
}
async function handlePostClaudeChanges(worktreePath) {
try {
// 正確なブランチ名を取得
const branchName = await getCurrentBranchName(worktreePath);
const isReleaseBranch = branchName.startsWith('release/');
// リリースブランチの場合は特別な処理
if (isReleaseBranch) {
// 変更がある場合は自動的にコミット
if (await hasUncommittedChanges(worktreePath)) {
const version = branchName.replace('release/', '');
const commitMessage = `chore: prepare release ${version}`;
printInfo(`Committing release changes: ${commitMessage}`);
await commitChanges(worktreePath, commitMessage);
printSuccess('Release changes committed successfully!');
}
// リリースアクションを選択
const action = await selectReleaseAction();
switch (action) {
case 'complete':
try {
await pushBranchToRemote(worktreePath, branchName);
printSuccess(`Pushed release branch: ${branchName}`);
// GitHub CLIが利用可能か確認
if (await isGitHubCLIAvailable()) {
const version = branchName.replace('release/', '');
printInfo('\nCreating pull request...');
try {
const { execa } = await import('execa');
const prTitle = `Release v${version}`;
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`;
const { stdout } = await execa('gh', [
'pr', 'create',
'--base', 'main',
'--head', branchName,
'--title', prTitle,
'--body', prBody
], { cwd: worktreePath });
printSuccess('Pull request created successfully!');
printInfo(stdout);
// リリースブランチのworktreeとローカルブランチを削除
printInfo('\nCleaning up release worktree and local branch...');
try {
await removeWorktree(worktreePath, true);
printSuccess('Release worktree removed successfully.');
// ローカルブランチも削除(リモートブランチは残す)
await deleteBranch(branchName, true);
printSuccess(`Local branch ${branchName} deleted successfully.`);
printInfo('\nRelease process initiated. The PR is ready for review.');
printInfo('Remote branch is preserved for the PR.');
}
catch (error) {
printWarning('Failed to clean up. Please remove worktree/branch manually.');
}
}
catch (error) {
printWarning('Failed to create PR automatically. Please create it manually.');
printInfo('\nGit Flow Release Process:');
printInfo('1. Create a PR to main branch');
printInfo('2. After merge, create a tag on main branch');
printInfo('3. Merge back to develop branch');
}
}
else {
printInfo('\nGitHub CLI not found. Please create the PR manually:');
printInfo('1. Create a PR to main branch');
printInfo('2. After merge, create a tag on main branch');
printInfo('3. Merge back to develop branch');
}
}
catch (error) {
printError(`Failed to push: ${error instanceof Error ? error.message : String(error)}`);
}
break;
case 'continue':
printInfo('Release branch saved. You can continue working on it later.');
break;
case 'nothing':
// Just exit
break;
}
return;
}
// 通常のブランチの場合は従来の処理
// Check if there are uncommitted changes
if (!(await hasUncommittedChanges(worktreePath))) {
return;
}
while (true) {
const action = await selectChangesAction();
switch (action) {
case 'status':
const status = await showStatus(worktreePath);
console.log('\n' + status + '\n');
await confirmContinue('Press enter to continue...');
break;
case 'commit':
const commitMessage = await inputCommitMessage();
await commitChanges(worktreePath, commitMessage);
printSuccess('Changes committed successfully!');
// リリースブランチの場合は、リリースアクションを選択
if (isReleaseBranch) {
const action = await selectReleaseAction();
switch (action) {
case 'complete':
try {
await pushBranchToRemote(worktreePath, branchName);
printSuccess(`Pushed release branch: ${branchName}`);
printInfo('\nGit Flow Release Process:');
printInfo('1. Create a PR to main branch');
printInfo('2. After merge, create a tag on main branch');
printInfo('3. Merge back to develop branch');
printInfo('\nUse GitHub/GitLab to create the PR.');
}
catch (error) {
printError(`Failed to push: ${error instanceof Error ? error.message : String(error)}`);
}
break;
case 'continue':
printInfo('Release branch saved with your commits. You can continue working on it later.');
break;
case 'nothing':
// Just exit
break;
}
}
return;
case 'stash':
await stashChanges(worktreePath, 'Stashed by Claude Worktree Manager');
printSuccess('Changes stashed successfully!');
return;
case 'discard':
if (await confirmDiscardChanges()) {
await discardAllChanges(worktreePath);
printSuccess('All changes discarded.');
return;
}
break;
case 'continue':
return;
}
}
}
catch (error) {
printError(`Failed to handle changes: ${error instanceof Error ? error.message : String(error)}`);
await confirmContinue('Press enter to continue...');
}
}
// Run the application if this module is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
}
//# sourceMappingURL=index.js.map