UNPKG

@lucidlayer/traceform-onboard

Version:

Interactive CLI tool and onboarding wizard for setting up, validating, and configuring Traceform in React projects. Automates project setup, developer onboarding, and toolchain validation for React, TypeScript, and monorepos.

154 lines (153 loc) 7.77 kB
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime"; /* // SPDX-License-Identifier: Apache-2.0 */ import { useState, useEffect } from 'react'; import { Box, Text, useInput } from 'ink'; import Spinner from 'ink-spinner'; // Simplified version check logic adapted for React state async function checkCommandVersion(command, versionArg, minVersion) { try { const { execa } = await import('execa'); // Dynamic import const { stdout } = await execa(command, [versionArg]); const version = stdout.trim().replace(/^v/, ''); const versionParts = version.split('.').map(Number); const minVersionParts = minVersion.split('.').map(Number); for (let i = 0; i < minVersionParts.length; i++) { // Handle cases like '18' vs '18.17.0' where versionParts might be shorter if (i >= versionParts.length || versionParts[i] < minVersionParts[i]) { return { passed: false, message: `Error: ${command} version ${version} is below the required minimum ${minVersion}. Please install or update.`, version, }; } if (versionParts[i] > minVersionParts[i]) { return { passed: true, message: `${command} v${version} found.`, version }; } } // If loops completes, versions are equal up to minVersionParts length, or versionParts is longer (e.g., 18.17.1 vs 18.17.0) return { passed: true, message: `${command} v${version} found.`, version }; } catch (error) { return { passed: false, message: `Error: Couldn't run '${command}'. Is it installed and in your PATH?`, }; } } const PrerequisitesStep = ({ onComplete, stepIndex, totalSteps }) => { const [nodeStatus, setNodeStatus] = useState(null); const [pmStatus, setPmStatus] = useState(null); const [nodePassed, setNodePassed] = useState(null); const [pmPassed, setPmPassed] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isCheckComplete, setIsCheckComplete] = useState(false); const [showContinuePrompt, setShowContinuePrompt] = useState(false); // State to indicate readiness for Enter key const [promptMessage, setPromptMessage] = useState(null); // Effect to run the checks useEffect(() => { const runChecks = async () => { setIsLoading(true); setIsCheckComplete(false); setNodeStatus('Checking Node.js version...'); const nodeResult = await checkCommandVersion('node', '-v', '18.17.0'); setNodeStatus(nodeResult.message); setNodePassed(nodeResult.passed); if (!nodeResult.passed) { setPmStatus('Skipped (Node.js check failed)'); setPmPassed(false); setIsLoading(false); setIsCheckComplete(true); return; } setPmStatus('Checking for package manager (npm, yarn, or pnpm)...'); let packageManagerFound = false; let pmMessage = ''; let pmFound = null; // Store which PM was found // Check npm const npmResult = await checkCommandVersion('npm', '-v', '8.0.0'); if (npmResult.passed) { packageManagerFound = true; pmMessage = `npm v${npmResult.version} found.`; pmFound = 'npm'; } // Check yarn only if npm wasn't found if (!packageManagerFound) { const yarnResult = await checkCommandVersion('yarn', '-v', '1.22.0'); if (yarnResult.passed) { packageManagerFound = true; pmMessage = `yarn v${yarnResult.version} found.`; pmFound = 'yarn'; } } // Check pnpm only if npm and yarn weren't found if (!packageManagerFound) { const pnpmResult = await checkCommandVersion('pnpm', '-v', '7.0.0'); if (pnpmResult.passed) { packageManagerFound = true; pmMessage = `pnpm v${pnpmResult.version} found.`; pmFound = 'pnpm'; } } if (!packageManagerFound) { setPmStatus('Error: No supported package manager found (npm >= 8, yarn >= 1.22, pnpm >= 7). Please install or update.'); setPmPassed(false); } else { setPmStatus(`Package manager check passed (${pmMessage})`); setPmPassed(true); } setIsLoading(false); setIsCheckComplete(true); }; void runChecks(); }, []); // Run only once on mount // Effect to trigger continue prompt or call onComplete on failure useEffect(() => { if (isCheckComplete && !isLoading) { const success = nodePassed === true && pmPassed === true; if (success) { setShowContinuePrompt(true); // Enable listening for Enter setPromptMessage('Press Enter to continue...'); // Set prompt message } else { // If checks failed, call onComplete immediately onComplete(false); } } // Intentionally not including onComplete in dependencies to avoid potential loops if parent re-renders }, [isCheckComplete, isLoading, nodePassed, pmPassed]); // Input handler for continuing useInput((input, key) => { if (showContinuePrompt && key.return) { // Check if the prompt is active and Enter key was pressed setShowContinuePrompt(false); // Disable prompt setPromptMessage(null); // Clear message onComplete(true); // Signal success return; // Prevent event from propagating } }, { isActive: showContinuePrompt } // Only activate the hook when the prompt should be shown ); // Add universal quit handler useInput((input, key) => { if (input.toLowerCase() === 'q') { onComplete(false); return; } }); const getStatusColor = (passed) => { if (passed === null) return 'yellow'; return passed ? 'green' : 'red'; }; const getStatusIcon = (passed) => { if (isLoading && passed === null) return ''; // Handled by spinner text below if (passed === null) return '○'; // Still waiting or skipped return passed ? '✔' : '✖'; }; return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "cyan", children: ["Step ", stepIndex, " of ", totalSteps] }), _jsx(Text, { bold: true, children: "--- Step 1: Prerequisites ---" }), _jsx(Box, { children: _jsxs(Text, { color: getStatusColor(nodePassed), children: [isLoading && nodeStatus?.startsWith('Checking') ? _jsx(Spinner, { type: "dots" }) : getStatusIcon(nodePassed), ` ${nodeStatus ?? 'Waiting...'}`] }) }), _jsx(Box, { children: _jsxs(Text, { color: getStatusColor(pmPassed), children: [isLoading && nodePassed && pmStatus?.startsWith('Checking') ? _jsx(Spinner, { type: "dots" }) : getStatusIcon(pmPassed), ` ${pmStatus ?? 'Waiting...'}`] }) }), !isLoading && isCheckComplete && !(nodePassed && pmPassed) && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: "Prerequisite checks failed. Please address the issues above and restart the wizard." }) })), promptMessage && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "cyan", children: "Press Enter to continue or Q to quit" }) }))] })); }; export default PrerequisitesStep;