UNPKG

willow-cli

Version:

CLI for installing Willow Design System components

314 lines (313 loc) 16.9 kB
import { execSync } from 'child_process'; import { writeFile } from 'fs/promises'; import { fileExists, createDirectory, writeFileContent, getComponentDir, getLibDir, } from '../utils/fileSystem.js'; import { createComponentsJson, createGlobalCSS, createTailwindConfig, createPostCSSConfig, createViteConfig, createTSConfig, createBoltPrompt, } from '../utils/configTemplates.js'; import { detectProjectType, validateProjectRequirements, logProjectEnvironment } from '../utils/projectDetection.js'; import { createDesignSystemFiles, createCoreLibFiles, createVersionFile } from '../utils/createDesignSystemFiles.js'; import { handleError, safeExecute, ProgressTracker } from '../utils/errorHandling.js'; import { Logger } from '../utils/logger.js'; import { fixWillowImports } from '../utils/importFixer.js'; export function registerInitCommand(program) { program .command('init') .description('Initialize Willow Design System in your project') .option('-y, --yes', 'Skip confirmation prompts') .option('--no-install', 'Skip dependency installation') .option('--force', 'Overwrite existing configuration') .option('--with-components', 'Install all Willow components after initialization') .option('--include-unstable', 'Include unstable components (avatar, tooltip)') .option('--debug', 'Enable debug logging') .action(async (options) => { if (options.debug) { Logger.setDebugMode(true); } Logger.title('Initializing Willow Design System'); const progressTracker = new ProgressTracker(); progressTracker.addStep('Validate project'); progressTracker.addStep('Detect project type'); progressTracker.addStep('Create directories'); progressTracker.addStep('Create configuration files'); progressTracker.addStep('Create design system files'); progressTracker.addStep('Setup CSS'); progressTracker.addStep('Install dependencies'); progressTracker.addStep('Install components'); progressTracker.addStep('Fix imports'); progressTracker.addStep('Validate setup'); try { // Step 1: Validate project await progressTracker.executeStep('Validate project', async () => { if (!(await fileExists('package.json'))) { throw new Error('No package.json found. Please run this command in a project root.'); } const validation = await validateProjectRequirements(); if (!validation.valid) { Logger.error('Project validation failed:'); validation.issues.forEach(issue => Logger.error(issue, { prefix: ' •' })); throw new Error('Project requirements not met'); } if (validation.warnings.length > 0) { Logger.warning('Project warnings:'); validation.warnings.forEach(warning => Logger.warning(warning, { prefix: ' •' })); } }); // Step 2: Detect project type const projectType = await progressTracker.executeStep('Detect project type', async () => { const detected = await detectProjectType(); logProjectEnvironment(detected); // For online IDEs, force specific options if (detected.isOnlineIDE) { options.force = true; options.install = true; options.yes = true; Logger.info('Online IDE detected - auto-configuring for best experience', { prefix: '🚀' }); } return detected; }); // Check for existing configuration if (await fileExists('components.json') && !options.force) { throw new Error('Components.json already exists! Use --force to overwrite existing configuration.'); } // Step 3: Create directories await progressTracker.executeStep('Create directories', async () => { const componentDir = await getComponentDir(projectType); const libDir = await getLibDir(projectType); await createDirectory(componentDir); await createDirectory(libDir); Logger.debug(`Component directory: ${componentDir}`); Logger.debug(`Library directory: ${libDir}`); }); // Step 4: Create configuration files await progressTracker.executeStep('Create configuration files', async () => { const componentDir = await getComponentDir(projectType); const libDir = await getLibDir(projectType); // Create components.json const componentsConfig = createComponentsJson(projectType); await writeFileContent('components.json', JSON.stringify(componentsConfig, null, 2)); Logger.substep('Created components.json'); // Create core lib files await createCoreLibFiles(libDir, projectType); Logger.substep('Created core library files'); // Create version tracking await safeExecute(() => createVersionFile(), 'Version file creation', { logWarning: false }); }); // Step 5: Create design system files await progressTracker.executeStep('Create design system files', async () => { const libDir = await getLibDir(projectType); await createDesignSystemFiles(libDir, projectType); }); // Step 6: Setup CSS and Tailwind await progressTracker.executeStep('Setup CSS', async () => { // Create global CSS const cssPath = await createGlobalCSS(projectType, options.force); Logger.substep(`Created ${cssPath} with Willow styles`); // Create Tailwind config const tailwindPath = await createTailwindConfig(projectType, projectType.isOnlineIDE); Logger.substep(`Created ${tailwindPath}`); // Create PostCSS config if needed if (!(await fileExists('postcss.config.js'))) { await createPostCSSConfig(); Logger.substep('Created postcss.config.js'); } // Create Vite config for Vite projects (especially online IDEs) if (projectType.isVite || projectType.isOnlineIDE) { await createViteConfig(projectType); Logger.substep('Created Vite config with path aliases'); // Create TypeScript config for proper path resolution await createTSConfig(projectType); Logger.substep('Created TypeScript config with path mappings'); } // Create .bolt/prompt for online IDEs if (projectType.isOnlineIDE || await fileExists('.bolt')) { await createDirectory('.bolt'); const aiPrompt = await createBoltPrompt(); await writeFileContent('.bolt/prompt', aiPrompt); Logger.substep('Created .bolt/prompt with Willow AI assistant'); } }); // Step 7: Install dependencies if (options.install !== false) { await progressTracker.executeStep('Install dependencies', async () => { const dependencies = getDependencies(projectType); try { const packageManager = projectType.packageManager || await detectPackageManager(); const installCmd = getInstallCommand(packageManager, dependencies); Logger.substep(`Installing with ${packageManager}...`); execSync(installCmd, { stdio: 'inherit' }); } catch (error) { // For online IDEs, suppress installation errors as they often have permission issues if (projectType.isOnlineIDE) { Logger.warning('Dependencies may need manual installation in online IDE'); Logger.info('Add these to your package.json:', { prefix: '📦' }); Logger.list(dependencies, { indent: 2 }); } else { Logger.error('Failed to install dependencies'); Logger.error('Please install the following dependencies manually:'); Logger.list(dependencies, { indent: 2 }); throw error; } } }); } // Step 8: Install components (if requested) if (options.withComponents || projectType.isOnlineIDE) { await progressTracker.executeStep('Install components', async () => { Logger.section('Installing all Willow components'); const componentDir = await getComponentDir(projectType); const libDir = await getLibDir(projectType); const { installAllWillowComponents } = await import('../utils/componentInstaller.js'); const installResult = await installAllWillowComponents({ overwrite: true, baseDir: process.cwd(), includeUnstable: options.includeUnstable, componentDir, libDir, isVite: projectType.isVite, projectType }); if (installResult.installed.length > 0) { Logger.success(`Installed ${installResult.installed.length} components`); // Update barrel exports const { updateBarrelExport } = await import('../utils/componentInstaller.js'); await updateBarrelExport(componentDir, { dryRun: false }); } if (installResult.failed.length > 0) { Logger.error(`Failed to install ${installResult.failed.length} components`); } // Handle npm dependencies for components if (installResult.npmDependencies && installResult.npmDependencies.length > 0) { if (projectType.isOnlineIDE) { // Auto-install npm packages in online IDEs try { const packageManager = projectType.packageManager || 'npm'; const packageCmd = getInstallCommand(packageManager, installResult.npmDependencies); execSync(packageCmd, { stdio: 'inherit' }); Logger.success('Installed component dependencies'); } catch (error) { Logger.warning('Some packages may need manual installation'); Logger.info('Add these packages if needed:', { prefix: '📦' }); Logger.list(installResult.npmDependencies, { indent: 2 }); } } else { Logger.info('Additional npm packages needed:', { prefix: '📦' }); const packageManager = projectType.packageManager || 'npm'; const installCmd = getInstallCommand(packageManager, installResult.npmDependencies); Logger.info(installCmd); } } }); } // Component guide installation (optional) try { const { loadTemplate } = await import('../utils/templateLoader.js'); const guideContent = await loadTemplate('docs/component-guide.md'); await writeFile('COMPONENT-GUIDE.md', guideContent, 'utf-8'); Logger.success('📚 Created COMPONENT-GUIDE.md with usage examples'); } catch (error) { Logger.debug('Component guide not available - skipping'); } // Step 10: Fix any incorrect imports await progressTracker.executeStep('Fix imports', async () => { await fixWillowImports(process.cwd()); }); // Step 11: Validate complete setup await progressTracker.executeStep('Validate setup', async () => { const { validateWillowSetup, validateComponentImports } = await import('../utils/validationChecker.js'); // Run comprehensive validation const setupValidation = await validateWillowSetup(projectType); const importValidation = await validateComponentImports(); if (!setupValidation.isValid || !importValidation.isValid) { const allErrors = [...setupValidation.errors, ...importValidation.errors]; const allWarnings = [...setupValidation.warnings, ...importValidation.warnings]; if (allErrors.length > 0) { Logger.warning('Some validation issues were found:'); allErrors.forEach(error => Logger.substep(`❌ ${error}`)); } if (allWarnings.length > 0) { Logger.warning('Validation warnings:'); allWarnings.forEach(warning => Logger.substep(`⚠️ ${warning}`)); } // Don't fail the entire init, but warn user Logger.warning('Setup completed with validation issues. Please review the above.'); } else { Logger.substep('✅ All systems validated successfully'); Logger.substep(`📁 Verified ${setupValidation.checkedFiles.length} files`); } }); // Success summary Logger.spacer(); Logger.success('Willow Design System initialized successfully!'); if (!options.withComponents && !projectType.isOnlineIDE) { Logger.section('Next steps'); Logger.list([ 'Add components: willow add button', 'Import and use: import { Button } from "@/components/ui"', 'View all components: willow list' ], { indent: 2 }); } else { Logger.section('Start using Willow components'); Logger.info('import { Button, Card, Badge } from "@/components/ui"', { indent: 2 }); } // Show warning for online IDEs if (projectType.isOnlineIDE) { Logger.warning('Online IDE detected:'); Logger.list([ 'Automatically installed all components', 'Using .js config files for compatibility' ], { indent: 2 }); } progressTracker.summarize(); } catch (error) { handleError(error, 'Initialization failed'); progressTracker.summarize(); process.exit(1); } }); } function getDependencies(projectType) { const baseDeps = [ 'tailwindcss', 'tailwindcss-animate', 'autoprefixer', 'postcss', 'class-variance-authority', 'clsx', 'tailwind-merge', 'lucide-react', '@radix-ui/react-slot' ]; // Add TypeScript types if not in online IDE and project uses TypeScript if (!projectType.isOnlineIDE && projectType.hasTypeScript) { baseDeps.push('@types/node', '@types/react', '@types/react-dom'); } return baseDeps; } async function detectPackageManager() { if (await fileExists('bun.lockb')) return 'bun'; if (await fileExists('pnpm-lock.yaml')) return 'pnpm'; if (await fileExists('yarn.lock')) return 'yarn'; return 'npm'; } function getInstallCommand(packageManager, dependencies) { const depString = dependencies.join(' '); switch (packageManager) { case 'yarn': return `yarn add ${depString}`; case 'pnpm': return `pnpm add ${depString}`; case 'bun': return `bun add ${depString}`; default: return `npm install ${depString}`; } }