import { useCallback } from "react"; import { useStore } from "../state/store.tsx"; import { setMessage, startAction, endAction } from "../state/actions.ts"; import { bunGitService } from "../services/git.ts"; import { defaultGitHubService } from "../services/github.ts"; import { parseGitHubUrl } from "../github/cli.ts"; import { executeCommand } from "../operations/commands.ts"; import { runSequentialBatch } from "../operations/sequential.ts"; import { UI } from "../constants.ts"; import type { CommandConfig, GitforestConfig, Project } from "../types/index.ts"; interface UseConfirmDialogActionsOptions { config: GitforestConfig; onRefresh: (options?: { forceRefresh?: boolean }) => Promise; } export function useConfirmDialogActions({ config, onRefresh, }: UseConfirmDialogActionsOptions) { const { state, dispatch } = useStore(); const handleConfirm = useCallback( async (options: { visibility?: "private" | "public" }) => { const { modal } = state; if (modal.kind !== "confirm") return; const confirmDialog = modal.data; const { operation, projectPaths } = confirmDialog; const isPrivate = options.visibility === "private"; // Find projects by path from the canonical project list. The visible list // can change between selection and confirmation. const projects = state.projects.filter((p) => projectPaths.includes(p.path) ); dispatch({ type: "CLOSE_MODAL" }); if (operation === "setup") { await handleSetup(projects, isPrivate); } else if (operation === "create") { await handleCreate(projects, isPrivate); } else if (operation === "archive") { await handleArchive(projects); } else if (operation === "command") { await handleCommand(projectPaths, confirmDialog.command); } await onRefresh(); }, [state, dispatch, config, onRefresh] ); const handleCancel = useCallback(() => { dispatch({ type: "CLOSE_MODAL" }); }, [dispatch]); const handleSetup = async (projects: Project[], isPrivate: boolean) => { const nonGitProjects = projects.filter((p) => p.type === "non-git"); const gitProjects = projects.filter((p) => p.type === "git"); const allProjects = [...nonGitProjects, ...gitProjects]; dispatch(startAction(`Setting up ${projects.length} projects`)); const initSummary = await runSequentialBatch(nonGitProjects, async (p) => { const result = await bunGitService.init(p.path); return { success: result.success }; }); const createSummary = await runSequentialBatch(allProjects, async (p) => { const result = await defaultGitHubService.createRepo({ name: p.name, isPrivate, localPath: p.path, }); return { success: result.success }; }); dispatch(endAction()); dispatch( setMessage( `Setup complete: ${initSummary.successes} initialized, ${createSummary.successes}/${allProjects.length} repos created` ) ); }; const handleCreate = async (projects: Project[], isPrivate: boolean) => { dispatch(startAction(`Creating ${projects.length} GitHub repos`)); const summary = await runSequentialBatch(projects, async (p) => { const result = await defaultGitHubService.createRepo({ name: p.name, isPrivate, localPath: p.path, }); return { success: result.success }; }); dispatch(endAction()); dispatch(setMessage(`Created ${summary.successes}/${projects.length} GitHub repos`)); }; const handleArchive = async (projects: Project[]) => { dispatch(startAction(`Archiving ${projects.length} repos`)); const summary = await runSequentialBatch(projects, async (p) => { // Resolve the GitHub identity from the project's remote URL — using // project.name silently archives the wrong repo when local dir name // differs from the GitHub repo name or the user owns multiple forks. const remoteUrl = p.status?.remoteUrl ?? null; const parsed = remoteUrl ? parseGitHubUrl(remoteUrl) : null; const target = parsed ? `${parsed.owner}/${parsed.repo}` : p.name; const result = await defaultGitHubService.archiveRepo(target); return { success: result.success }; }); dispatch(endAction()); dispatch(setMessage(`Archived ${summary.successes}/${projects.length} repos`)); }; const handleCommand = async (projectPaths: string[], command?: CommandConfig) => { if (!command) { dispatch(setMessage("No command configured for confirmation")); return; } dispatch(startAction(`Running: ${command.name} on ${projectPaths.length} repo(s)`)); const summary = await runSequentialBatch(projectPaths, async (path) => { const result = await executeCommand(command, path); return result.success ? { success: true, data: result.output ?? "" } : { success: false, error: result.error ?? "unknown" }; }); dispatch(endAction()); const lastOutput = summary.results.findLast((r) => r.success)?.data ?? ""; const lastError = summary.firstError ?? "unknown"; if (summary.failures === 0) { const shortOutput = lastOutput && lastOutput.length > UI.OUTPUT_TRUNCATION_LENGTH ? lastOutput.slice(0, UI.OUTPUT_TRUNCATION_LENGTH) + "..." : lastOutput || "Done"; dispatch(setMessage(`${command.name}: ${summary.successes}/${projectPaths.length} - ${shortOutput}`)); } else { dispatch(setMessage(`${command.name}: ${summary.successes}/${projectPaths.length} succeeded (${summary.failures} failed: ${lastError})`)); } }; return { handleConfirm, handleCancel, }; }