import { useState, useEffect, useCallback, type KeyboardEvent } from 'react'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Terminal, Save, RotateCcw, Info, X, Play, FlaskConical } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; import { cn } from '@/lib/utils'; import { useProjectSettings } from '@/hooks/queries'; import { useUpdateProjectSettings } from '@/hooks/mutations'; import type { Project } from '@/lib/electron'; /** Preset dev server commands for quick selection */ const DEV_SERVER_PRESETS = [ { label: 'npm run dev', command: 'npm run dev' }, { label: 'yarn dev', command: 'yarn dev' }, { label: 'pnpm dev', command: 'pnpm dev' }, { label: 'bun dev', command: 'bun dev' }, { label: 'npm start', command: 'npm start' }, { label: 'cargo watch', command: 'cargo watch -x run' }, { label: 'go run', command: 'go run .' }, ] as const; /** Preset test commands for quick selection */ const TEST_PRESETS = [ { label: 'npm test', command: 'npm test' }, { label: 'yarn test', command: 'yarn test' }, { label: 'pnpm test', command: 'pnpm test' }, { label: 'bun test', command: 'bun test' }, { label: 'pytest', command: 'pytest' }, { label: 'cargo test', command: 'cargo test' }, { label: 'go test', command: 'go test ./...' }, ] as const; interface CommandsSectionProps { project: Project; } export function CommandsSection({ project }: CommandsSectionProps) { // Fetch project settings using TanStack Query const { data: projectSettings, isLoading, isError } = useProjectSettings(project.path); // Mutation hook for updating project settings const updateSettingsMutation = useUpdateProjectSettings(project.path); // Local state for the input fields const [devCommand, setDevCommand] = useState(''); const [originalDevCommand, setOriginalDevCommand] = useState(''); const [testCommand, setTestCommand] = useState(''); const [originalTestCommand, setOriginalTestCommand] = useState(''); // Sync local state when project settings load or project changes useEffect(() => { // Reset local state when project changes to avoid showing stale values setDevCommand(''); setOriginalDevCommand(''); setTestCommand(''); setOriginalTestCommand(''); }, [project.path]); useEffect(() => { if (projectSettings) { const dev = projectSettings.devCommand || ''; const test = projectSettings.testCommand || ''; setDevCommand(dev); setOriginalDevCommand(dev); setTestCommand(test); setOriginalTestCommand(test); } }, [projectSettings]); // Check if there are unsaved changes const hasDevChanges = devCommand !== originalDevCommand; const hasTestChanges = testCommand !== originalTestCommand; const hasChanges = hasDevChanges || hasTestChanges; const isSaving = updateSettingsMutation.isPending; // Save all commands const handleSave = useCallback(() => { const normalizedDevCommand = devCommand.trim(); const normalizedTestCommand = testCommand.trim(); updateSettingsMutation.mutate( { devCommand: normalizedDevCommand || null, testCommand: normalizedTestCommand || null, }, { onSuccess: () => { setDevCommand(normalizedDevCommand); setOriginalDevCommand(normalizedDevCommand); setTestCommand(normalizedTestCommand); setOriginalTestCommand(normalizedTestCommand); }, } ); }, [devCommand, testCommand, updateSettingsMutation]); // Reset to original values const handleReset = useCallback(() => { setDevCommand(originalDevCommand); setTestCommand(originalTestCommand); }, [originalDevCommand, originalTestCommand]); // Use a preset command const handleUseDevPreset = useCallback((command: string) => { setDevCommand(command); }, []); const handleUseTestPreset = useCallback((command: string) => { setTestCommand(command); }, []); // Clear commands const handleClearDev = useCallback(() => { setDevCommand(''); }, []); const handleClearTest = useCallback(() => { setTestCommand(''); }, []); // Handle keyboard shortcuts (Enter to save) const handleKeyDown = useCallback( (e: KeyboardEvent) => { if (e.key === 'Enter' && hasChanges && !isSaving) { e.preventDefault(); handleSave(); } }, [hasChanges, isSaving, handleSave] ); return (

Project Commands

Configure custom commands for development and testing.

{isLoading ? (
) : isError ? (
Failed to load project settings. Please try again.
) : ( <> {/* Dev Server Command Section */}

Dev Server

{hasDevChanges && ( (unsaved) )}
setDevCommand(e.target.value)} onKeyDown={handleKeyDown} placeholder="e.g., npm run dev, yarn dev, cargo watch" className="font-mono text-sm pr-8" data-testid="dev-command-input" /> {devCommand && ( )}

Leave empty to auto-detect based on your package manager.

{/* Dev Presets */}
{DEV_SERVER_PRESETS.map((preset) => ( ))}
{/* Divider */}
{/* Test Command Section */}

Test Runner

{hasTestChanges && ( (unsaved) )}
setTestCommand(e.target.value)} onKeyDown={handleKeyDown} placeholder="e.g., npm test, pytest, cargo test" className="font-mono text-sm pr-8" data-testid="test-command-input" /> {testCommand && ( )}

Leave empty to auto-detect based on your project structure.

{/* Test Presets */}
{TEST_PRESETS.map((preset) => ( ))}
{/* Auto-detection Info */}

Auto-detection

When no custom command is set, the system automatically detects your package manager and test framework based on project files (package.json, Cargo.toml, go.mod, etc.).

{/* Action Buttons */}
)}
); }