UNPKG

@shutootaki/gwm

Version:
169 lines 7.29 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { useState, useEffect, useMemo, useCallback } from 'react'; import { execSync } from 'child_process'; import { SelectList } from './SelectList.js'; import { TextInput, validateBranchName, generateWorktreePreview, } from './TextInput.js'; import { formatErrorForDisplay } from '../utils/index.js'; import { useWorktree } from '../hooks/useWorktree.js'; import { LoadingSpinner } from './ui/LoadingSpinner.js'; import { Notice } from './ui/Notice.js'; import { getRemoteBranchesWithInfo } from '../utils/git.js'; import { escapeShellArg } from '../utils/shell.js'; export const WorktreeAdd = ({ branchName, isRemote = false, fromBranch, openCode = false, openCursor = false, outputPath = false, }) => { const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const [remoteBranches, setRemoteBranches] = useState([]); const [isRemoteBranchesLoaded, setIsRemoteBranchesLoaded] = useState(false); // 初期表示モードは引数有無に応じて決定する // * branchName 指定あり → すぐに worktree 作成するので "loading" 表示のみ // * -r 指定でリモート一覧を取りに行く場合も "loading" // * それ以外は新規ブランチ入力モード const initialViewMode = branchName ? 'loading' : isRemote ? 'loading' : 'input'; const [viewMode, setViewMode] = useState(initialViewMode); // onSuccess / onError の参照が毎レンダー変わらないように useCallback でメモ化 const handleSuccess = useCallback((data) => { setSuccess(JSON.stringify(data)); }, []); const handleError = useCallback((message) => { setError(message); }, []); const { createWorktree } = useWorktree({ fromBranch, openCode, openCursor, outputPath, onSuccess: handleSuccess, onError: handleError, }); const fetchRemoteBranches = async () => { try { // リモート一覧を取得して fetch const remotes = execSync('git remote', { cwd: process.cwd(), encoding: 'utf8', }) .split('\n') .map((r) => r.trim()) .filter(Boolean); const targetRemotes = remotes.includes('origin') ? ['origin'] : remotes; for (const remote of targetRemotes) { execSync(`git fetch --prune ${escapeShellArg(remote)}`, { cwd: process.cwd(), }); } // リモートブランチの詳細情報を取得 const branches = getRemoteBranchesWithInfo(); setRemoteBranches(branches); setIsRemoteBranchesLoaded(true); setViewMode('select'); } catch (err) { setError(formatErrorForDisplay(err)); } }; useEffect(() => { if (branchName) { // 引数でブランチ名が指定された場合、直接作成 createWorktree(branchName, isRemote); } else if (isRemote) { // -r フラグが指定された場合、リモートブランチ選択モードに setViewMode('loading'); fetchRemoteBranches(); } else { // 引数なしの場合、デフォルトで新規ブランチ入力モード setViewMode('input'); } }, [branchName, isRemote, createWorktree]); // remoteBranches の SelectList 用変換をメモ化 const selectItems = useMemo(() => remoteBranches.map((branch) => ({ label: branch.name, value: branch.name, description: branch.lastCommitMessage, metadata: { lastCommitDate: branch.lastCommitDate, lastCommitterName: branch.lastCommitterName, lastCommitMessage: branch.lastCommitMessage, }, })), [remoteBranches]); const handleBranchSelect = (item) => { createWorktree(item.value, true); }; const handleBranchInput = (branchName) => { createWorktree(branchName, false); }; const handleCancel = () => { setError('Operation cancelled'); }; const handleModeSwitch = () => { if (viewMode === 'input') { // 新規ブランチ入力からリモートブランチ選択に切り替え if (!isRemoteBranchesLoaded) { setViewMode('loading'); fetchRemoteBranches(); } else { setViewMode('select'); } } else if (viewMode === 'select') { // リモートブランチ選択から新規ブランチ入力に切り替え setViewMode('input'); } }; // 成功してエディタを開いた場合は 1 秒後に自動で CLI を終了する useEffect(() => { if (success && (openCode || openCursor)) { const timer = setTimeout(() => process.exit(0), 1000); return () => clearTimeout(timer); } }, [success, openCode, openCursor]); if (success) { // エディタを開くオプションが指定されている場合は簡易表示 if (openCode || openCursor) { let worktreePath; try { const parsed = JSON.parse(success); worktreePath = parsed.path; } catch { worktreePath = success; } const editorName = openCode ? 'VS Code' : 'Cursor'; return (_jsx(Notice, { variant: "success", title: `Worktree created and opened in ${editorName}`, messages: `✓ ${worktreePath}` })); } let worktreePath; let actions = []; try { const parsed = JSON.parse(success); worktreePath = parsed.path; actions = parsed.actions || []; } catch { // 後方互換性のため、文字列の場合はそのままパスとして扱う worktreePath = success; } return (_jsx(Notice, { variant: "success", title: "Worktree created successfully!", messages: [ `Location: ${worktreePath}`, ...actions.map((a) => `• ${a}`), `Use: cd "${worktreePath}" to navigate`, ] })); } if (error) { return (_jsx(Notice, { variant: "error", title: "Failed to create worktree", messages: [error, 'Branch may already exist or permission issue'] })); } if (viewMode === 'input') { return (_jsx(TextInput, { title: "Create new worktree", placeholder: "Enter new branch name...", onSubmit: handleBranchInput, onCancel: handleCancel, onModeSwitch: handleModeSwitch, validate: validateBranchName, preview: generateWorktreePreview })); } if (viewMode === 'select') { return (_jsx(SelectList, { items: selectItems, onSelect: handleBranchSelect, onCancel: handleCancel, title: "Create worktree from remote branch", placeholder: "Search remote branches..." })); } // viewMode === 'loading' return _jsx(LoadingSpinner, { label: "Fetching remote branches..." }); }; //# sourceMappingURL=WorktreeAdd.js.map