@dankupfer/create-dn-starter
Version:
Interactive CLI for creating modular React Native apps with Expo
172 lines (165 loc) • 6.99 kB
JavaScript
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);
});
}