UNPKG

@dankupfer/create-dn-starter

Version:

Interactive CLI for creating modular React Native apps with Expo

172 lines (165 loc) 6.99 kB
import path from 'path'; import fs from 'fs-extra'; import { Command } from 'commander'; import inquirer from 'inquirer'; import chalk from 'chalk'; import { fileURLToPath } from 'url'; import { ProjectGenerator } from './generator.js'; import packageJson from '../package.json' with { type: 'json' }; // Load templates from appConfig.json async function loadAvailableTemplates() { // Get the CLI directory const currentFilePath = fileURLToPath(import.meta.url); const cliSrcDir = path.dirname(currentFilePath); const cliDir = path.resolve(cliSrcDir, '..'); // Look for appConfig.json in the bundled template const configPath = path.join(cliDir, 'template', 'src', 'config', 'appConfig.json'); if (!fs.existsSync(configPath)) { throw new Error(`Template configuration not found at: ${configPath} This usually means: 1. The CLI package is corrupted or incomplete 2. The template was not properly bundled during build 3. You're running from source without building the template Please try: - Reinstalling the CLI: npm uninstall -g create-dn-starter && npm install -g create-dn-starter - Or if running from source: npm run build`); } let configData; try { configData = await fs.readJson(configPath); } catch (error) { throw new Error(`Failed to parse template configuration at: ${configPath} The appConfig.json file exists but contains invalid JSON. Error: ${error instanceof Error ? error.message : String(error)}`); } if (!configData.availableTemplates || typeof configData.availableTemplates !== 'object') { throw new Error(`Invalid template configuration format in: ${configPath} Expected structure: { "selectedTemplate": "...", "availableTemplates": { "templateId": { "name": "...", "description": "...", "entryPoint": "...", "features": [...] } } }`); } // Convert the config format to our TemplateOption format // Convert the config format to our TemplateOption format const templates = Object.entries(configData.availableTemplates).map(([id, config]) => { if (!config.name || !config.description || !config.entryPoint || !config.modules) { throw new Error(`Invalid template configuration for "${id}" in ${configPath} Each template must have: name, description, entryPoint, and modules`); } return { id, name: config.name, description: config.description, modules: config.modules, // Add this line entryPoint: config.entryPoint }; }); if (templates.length === 0) { throw new Error(`No templates found in configuration: ${configPath} The availableTemplates object is empty. At least one template must be defined.`); } return templates; } export async function cli(args) { const program = new Command(); program .name(packageJson.name) .description('Create a new React Native project with different starter templates') .version(packageJson.version) .argument('[project-name]', 'Name of the project') .option('-t, --template <template>', 'Template to use') .option('-y, --yes', 'Skip prompts and use default options') .action(async (projectName, cmdOptions) => { console.log(chalk.blue.bold('🚀 React Native Starter Kit CLI')); console.log(chalk.gray(`Name: ${program.name()}`)); console.log(chalk.gray(`Version: ${program.version()}`)); console.log(chalk.cyan('Create React Native apps with different starter templates\n')); // Load available templates (this will throw with clear error if it fails) let AVAILABLE_TEMPLATES; try { AVAILABLE_TEMPLATES = await loadAvailableTemplates(); } catch (error) { console.error(chalk.red('Failed to load template configuration:')); console.error(chalk.red(error instanceof Error ? error.message : String(error))); process.exit(1); } // Get project name if (!projectName) { const response = await inquirer.prompt([{ type: 'input', name: 'projectName', message: 'What is the name of your project?', default: 'my-rn-app', validate: (input) => { if (/^[a-z0-9-]+$/.test(input)) return true; return 'Project name may only contain lowercase letters, numbers, and hyphens'; } }]); projectName = response.projectName; } // Get template choice let selectedTemplate = cmdOptions.template; if (!selectedTemplate && !cmdOptions.yes) { const templateChoices = AVAILABLE_TEMPLATES.map(template => ({ name: `${template.name} - ${template.description}`, value: template.id, short: template.name })); const response = await inquirer.prompt([{ type: 'list', name: 'template', message: 'Which template would you like to use?', choices: templateChoices, default: AVAILABLE_TEMPLATES[0]?.id || 'basic' }]); selectedTemplate = response.template; } else if (!selectedTemplate) { // Use first available template as default for --yes flag selectedTemplate = AVAILABLE_TEMPLATES[0]?.id || 'basic'; } // Validate template const templateConfig = AVAILABLE_TEMPLATES.find(t => t.id === selectedTemplate); if (!templateConfig) { console.error(chalk.red(`Invalid template: ${selectedTemplate}`)); console.log(chalk.yellow('Available templates:'), AVAILABLE_TEMPLATES.map(t => t.id).join(', ')); process.exit(1); } const projectOptions = { projectName, template: selectedTemplate }; // Show selected options console.log(chalk.green(`\nCreating project: ${chalk.bold(projectName)}`)); console.log(chalk.green(`Template: ${chalk.bold(templateConfig.name)}`)); console.log(chalk.gray(`Modules: ${templateConfig.modules.join(', ')}\n`)); // Generate the project const generator = new ProjectGenerator(projectOptions, AVAILABLE_TEMPLATES); try { await generator.generate(); } catch (error) { console.error(chalk.red(`Failed to create project: ${error instanceof Error ? error.message : String(error)}`)); process.exit(1); } }); program.parse(args); } // Allow command line use if (process.argv[1].endsWith('index.js')) { cli(process.argv).catch(err => { console.error(err); process.exit(1); }); }