create-roadkit
Version:
Beautiful Next.js roadmap website generator with full-screen kanban boards, dark/light mode, and static export
977 lines (869 loc) • 31.4 kB
text/typescript
/**
* Secure CLI implementation for RoadKit with comprehensive error handling and recovery
*
* This module provides a production-ready CLI system with:
* - Comprehensive input validation and sanitization
* - Proper error handling without process.exit calls
* - Security logging and audit trails
* - Recovery mechanisms for common failures
* - Progress tracking and user feedback
*/
import { input, select, confirm, checkbox } from '@inquirer/prompts';
import chalk from 'chalk';
import ora from 'ora';
import boxen from 'boxen';
import { resolve, dirname } from 'path';
import { existsSync } from 'fs';
import {
validateCLIOptions,
validateProjectName,
validateTemplateType,
validateThemeType,
sanitizePath,
SecurityError
} from '../utils/security.js';
import { Logger, logger, ErrorRecovery } from '../utils/logger.js';
import { SecureTemplateManager, SecureScaffoldResult } from '../core/secure-templates.js';
/**
* CLI options that can be passed from command line arguments
*/
export interface SecureCLIOptions {
name?: string;
template?: 'basic' | 'advanced' | 'enterprise' | 'custom';
theme?: 'modern' | 'classic' | 'minimal' | 'corporate';
output?: string;
skipPrompts?: boolean;
verbose?: boolean;
dryRun?: boolean;
overwrite?: boolean;
skipInstall?: boolean;
skipGit?: boolean;
}
/**
* Complete user choices for project generation
*/
interface SecureUserChoices {
projectName: string;
description: string;
author: {
name: string;
email?: string;
url?: string;
};
template: 'basic' | 'advanced' | 'enterprise' | 'custom';
theme: 'modern' | 'classic' | 'minimal' | 'corporate';
outputDirectory: string;
language: 'javascript' | 'typescript';
features: {
analytics: boolean;
seo: boolean;
pwa: boolean;
authentication: boolean;
database: boolean;
api: boolean;
testing: boolean;
deployment: boolean;
};
customization: {
primaryColor: string;
secondaryColor: string;
fontFamily: 'inter' | 'roboto' | 'open-sans' | 'poppins';
logoUrl?: string;
faviconUrl?: string;
};
technical: {
typescript: boolean;
eslint: boolean;
prettier: boolean;
tailwind: boolean;
shadcnUi: boolean;
};
overwrite: boolean;
gitInit: boolean;
installDependencies: boolean;
}
/**
* CLI execution result with detailed information
*/
export interface SecureCLIResult {
success: boolean;
projectPath?: string;
duration: number;
errors: string[];
warnings: string[];
securityWarnings: string[];
filesCreated: string[];
filesSkipped: string[];
nextSteps: string[];
canRetry: boolean;
retryInstructions?: string[];
}
/**
* Secure CLI implementation with comprehensive error handling
*/
export class SecureRoadKitCLI {
private logger: Logger;
private errorRecovery: ErrorRecovery;
private templateManager: SecureTemplateManager;
private spinner?: any;
private startTime: number = 0;
constructor(options: { verbose?: boolean; logDir?: string; templateDir?: string } = {}) {
// Initialize secure logging
this.logger = new Logger({
level: options.verbose ? 0 : 1, // DEBUG or INFO
enableConsole: true,
enableFile: true,
logDir: options.logDir || './logs',
verbose: options.verbose || false,
colored: true
});
this.errorRecovery = new ErrorRecovery(this.logger);
// Initialize secure template manager
const defaultTemplateDir = process.env.NODE_ENV === 'test'
? resolve(__dirname, '../../test-temp/templates')
: resolve(process.cwd(), 'src/templates');
const templateDir = options.templateDir || defaultTemplateDir;
this.templateManager = new SecureTemplateManager(templateDir, this.logger);
this.logger.info('SecureRoadKitCLI initialized', 'CLI', {
verbose: options.verbose,
templateDir,
logDir: options.logDir
});
}
/**
* Main CLI execution method with comprehensive error handling
*/
async execute(options: SecureCLIOptions = {}): Promise<SecureCLIResult> {
this.startTime = Date.now();
const result: SecureCLIResult = {
success: false,
duration: 0,
errors: [],
warnings: [],
securityWarnings: [],
filesCreated: [],
filesSkipped: [],
nextSteps: [],
canRetry: false
};
try {
// Step 1: Validate and sanitize CLI options
this.logger.startOperation('CLI Validation');
const validatedOptions = await this.validateCLIOptions(options);
// Step 2: Display welcome and collect user input
if (!validatedOptions.skipPrompts) {
this.displayWelcome();
}
const userChoices = await this.collectUserChoices(validatedOptions);
// Step 3: Display configuration summary and confirm
if (!validatedOptions.skipPrompts) {
await this.confirmProjectGeneration(userChoices);
}
// Step 4: Generate project with security controls
const scaffoldResult = await this.generateProjectSecurely(userChoices);
// Step 5: Process results and provide feedback
this.processScaffoldResult(scaffoldResult, result);
result.duration = Date.now() - this.startTime;
this.logger.completeOperation('CLI Execution', result.duration);
return result;
} catch (error) {
return await this.handleCLIError(error, result);
} finally {
// Always stop spinner and flush logs
if (this.spinner) {
this.spinner.stop();
}
await this.logger.flush();
}
}
/**
* Validates and sanitizes CLI options
*/
private async validateCLIOptions(options: SecureCLIOptions): Promise<SecureCLIOptions> {
this.logger.debug('Validating CLI options', 'Security', { options });
const validation = validateCLIOptions(options);
if (!validation.isValid) {
this.logger.security(
`CLI options validation failed: ${validation.error}`,
'input_validation',
'high',
JSON.stringify(options)
);
throw new SecurityError(
`Invalid CLI options: ${validation.error}`,
'input_validation'
);
}
if (validation.warnings && validation.warnings.length > 0) {
validation.warnings.forEach(warning => {
this.logger.security(
`CLI options warning: ${warning}`,
'input_validation',
'low'
);
});
}
this.logger.debug('CLI options validated successfully', 'Security');
return validation.data || options;
}
/**
* Displays welcome message with security notice
*/
private displayWelcome(): void {
const welcomeMessage = `${chalk.bold.blue('🛣️ RoadKit CLI')}
${chalk.gray('Secure Next.js roadmap website generator')}
Welcome to RoadKit! This tool will help you create a fully functional,
secure, and customizable roadmap website built with Next.js, TypeScript,
and Tailwind CSS.
${chalk.yellow('Security Notice:')} All inputs are validated and sanitized
to ensure secure project generation.
`;
console.log(boxen(welcomeMessage, {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'blue',
}));
}
/**
* Collects and validates user choices with comprehensive security checks
*/
private async collectUserChoices(options: SecureCLIOptions): Promise<SecureUserChoices> {
this.logger.info('Collecting user input with security validation', 'CLI');
// Project name with validation
let projectName: string;
if (options.name) {
const nameValidation = validateProjectName(options.name);
if (!nameValidation.isValid) {
throw new SecurityError(`Invalid project name: ${nameValidation.error}`, 'input_validation');
}
projectName = nameValidation.data!;
} else if (options.skipPrompts) {
throw new SecurityError('Project name is required when skipping prompts', 'input_validation');
} else {
projectName = await input({
message: 'What is your project name?',
validate: (value: string) => {
const validation = validateProjectName(value);
return validation.isValid ? true : validation.error!;
},
});
}
// Description with length and content validation
let description: string;
if (options.skipPrompts) {
description = `Generated roadmap project for ${projectName}`;
} else {
description = await input({
message: 'Provide a brief description of your roadmap:',
validate: (value: string) => {
if (!value.trim()) return 'Description is required';
if (value.length > 200) return 'Description must be 200 characters or less';
// Check for potentially dangerous content
if (/<script|javascript:|data:|vbscript:/i.test(value)) {
return 'Description contains prohibited content';
}
return true;
},
});
}
// Author information with validation
let authorName: string;
if (options.skipPrompts) {
authorName = 'Generated User';
} else {
authorName = await input({
message: 'Author name:',
validate: (value: string) => {
if (!value.trim()) return 'Author name is required';
if (value.length > 100) return 'Author name too long';
if (/<script|javascript:|data:/i.test(value)) {
return 'Author name contains prohibited content';
}
return true;
},
});
}
let authorEmail: string | undefined;
if (!options.skipPrompts) {
const emailInput = await input({
message: 'Author email (optional):',
validate: (value: string) => {
if (!value.trim()) return true;
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(value)) return 'Please enter a valid email address';
if (value.length > 254) return 'Email address too long';
return true;
},
});
authorEmail = emailInput || undefined;
}
let authorUrl: string | undefined;
if (!options.skipPrompts) {
const urlInput = await input({
message: 'Author website URL (optional):',
validate: (value: string) => {
if (!value.trim()) return true;
try {
const url = new URL(value);
if (!['http:', 'https:'].includes(url.protocol)) {
return 'Only HTTP and HTTPS URLs are allowed';
}
if (value.length > 2000) return 'URL too long';
return true;
} catch {
return 'Please enter a valid URL';
}
},
});
authorUrl = urlInput || undefined;
}
// Template selection with validation
let template: SecureUserChoices['template'];
if (options.template) {
const templateValidation = validateTemplateType(options.template);
if (!templateValidation.isValid) {
throw new SecurityError(`Invalid template: ${templateValidation.error}`, 'input_validation');
}
template = templateValidation.data! as SecureUserChoices['template'];
} else {
template = await select({
message: 'Choose a project template:',
choices: [
{ name: 'Basic - Simple roadmap with essential features', value: 'basic' },
{ name: 'Advanced - Feature-rich with analytics and SEO', value: 'advanced' },
{ name: 'Enterprise - Full-featured with authentication and database', value: 'enterprise' },
{ name: 'Custom - Minimal template for custom builds', value: 'custom' },
],
}) as SecureUserChoices['template'];
}
// Theme selection with validation
let theme: SecureUserChoices['theme'];
if (options.theme) {
const themeValidation = validateThemeType(options.theme);
if (!themeValidation.isValid) {
throw new SecurityError(`Invalid theme: ${themeValidation.error}`, 'input_validation');
}
theme = themeValidation.data! as SecureUserChoices['theme'];
} else if (options.skipPrompts) {
theme = 'modern'; // Default theme when skipping prompts
} else {
theme = await select({
message: 'Choose a visual theme:',
choices: [
{ name: 'Modern - Clean, contemporary design', value: 'modern' },
{ name: 'Classic - Traditional, professional look', value: 'classic' },
{ name: 'Minimal - Simple, focused design', value: 'minimal' },
{ name: 'Corporate - Business-oriented styling', value: 'corporate' },
],
}) as SecureUserChoices['theme'];
}
// Output directory with path validation
let outputDirectory: string;
if (options.output) {
const pathValidation = sanitizePath(options.output);
if (!pathValidation.isValid) {
throw new SecurityError(`Invalid output path: ${pathValidation.error}`, 'path_traversal');
}
outputDirectory = pathValidation.sanitizedPath!;
} else if (options.skipPrompts) {
const defaultPath = `./${projectName}`;
const pathValidation = sanitizePath(defaultPath);
outputDirectory = pathValidation.sanitizedPath!;
} else {
const userPath = await input({
message: 'Output directory:',
default: `./${projectName}`,
validate: (value: string) => {
const pathValidation = sanitizePath(value);
return pathValidation.isValid ? true : pathValidation.error!;
},
});
const pathValidation = sanitizePath(userPath);
outputDirectory = pathValidation.sanitizedPath!;
}
// Language selection
let language: 'javascript' | 'typescript';
if (options.skipPrompts) {
language = 'typescript'; // Default to TypeScript when skipping prompts
} else {
language = await select({
message: 'Choose programming language:',
choices: [
{ name: 'TypeScript (recommended)', value: 'typescript' },
{ name: 'JavaScript', value: 'javascript' },
],
default: 'typescript'
}) as 'javascript' | 'typescript';
}
// Feature selection based on template
const features = await this.collectFeatureChoices(template, options.skipPrompts);
// Customization options
const customization = await this.collectCustomizationChoices(options.skipPrompts);
// Technical configuration
const technical = await this.collectTechnicalChoices(language, options.skipPrompts);
// Additional options
let overwrite: boolean;
if (options.overwrite !== undefined) {
overwrite = options.overwrite;
} else if (options.skipPrompts) {
overwrite = false; // Default to not overwriting when skipping prompts
} else {
overwrite = await confirm({
message: 'Overwrite existing directory if it exists?',
default: false,
});
}
let gitInit: boolean;
if (options.skipGit) {
gitInit = false;
} else if (options.skipPrompts) {
gitInit = true; // Default to initializing git when skipping prompts
} else {
gitInit = await confirm({
message: 'Initialize git repository?',
default: true,
});
}
let installDependencies: boolean;
if (options.skipInstall) {
installDependencies = false;
} else if (options.skipPrompts) {
installDependencies = true; // Default to installing dependencies when skipping prompts
} else {
installDependencies = await confirm({
message: 'Install dependencies after generation?',
default: true,
});
}
const userChoices: SecureUserChoices = {
projectName,
description,
author: {
name: authorName,
email: authorEmail || undefined,
url: authorUrl || undefined,
},
template,
theme,
outputDirectory,
language,
features,
customization,
technical,
overwrite,
gitInit,
installDependencies,
};
this.logger.info('User input collected successfully', 'CLI', {
projectName,
template,
theme,
hasEmail: !!authorEmail,
hasUrl: !!authorUrl
});
return userChoices;
}
/**
* Collects feature choices with validation
*/
private async collectFeatureChoices(template: SecureUserChoices['template'], skipPrompts = false): Promise<SecureUserChoices['features']> {
// Template-specific defaults
const templateDefaults = {
basic: ['seo', 'api', 'testing'],
advanced: ['seo', 'api', 'testing', 'analytics', 'deployment'],
enterprise: ['seo', 'api', 'testing', 'analytics', 'deployment', 'authentication', 'database'],
custom: [],
};
let selectedFeatures: string[];
if (skipPrompts) {
// Use template defaults when skipping prompts
selectedFeatures = templateDefaults[template];
} else {
selectedFeatures = await checkbox({
message: 'Select additional features:',
choices: [
{ name: 'Analytics integration', value: 'analytics', checked: templateDefaults[template].includes('analytics') },
{ name: 'SEO optimization', value: 'seo', checked: templateDefaults[template].includes('seo') },
{ name: 'Progressive Web App (PWA)', value: 'pwa', checked: templateDefaults[template].includes('pwa') },
{ name: 'Authentication system', value: 'authentication', checked: templateDefaults[template].includes('authentication') },
{ name: 'Database integration', value: 'database', checked: templateDefaults[template].includes('database') },
{ name: 'API routes', value: 'api', checked: templateDefaults[template].includes('api') },
{ name: 'Testing setup', value: 'testing', checked: templateDefaults[template].includes('testing') },
{ name: 'Deployment configuration', value: 'deployment', checked: templateDefaults[template].includes('deployment') },
],
});
}
return {
analytics: selectedFeatures.includes('analytics'),
seo: selectedFeatures.includes('seo'),
pwa: selectedFeatures.includes('pwa'),
authentication: selectedFeatures.includes('authentication'),
database: selectedFeatures.includes('database'),
api: selectedFeatures.includes('api'),
testing: selectedFeatures.includes('testing'),
deployment: selectedFeatures.includes('deployment'),
};
}
/**
* Collects customization options with validation
*/
private async collectCustomizationChoices(skipPrompts = false): Promise<SecureUserChoices['customization']> {
let primaryColor: string;
if (skipPrompts) {
primaryColor = '#3b82f6'; // Default primary color
} else {
primaryColor = await input({
message: 'Primary color (hex code):',
default: '#3b82f6',
validate: (value: string) => {
const hexRegex = /^#[0-9A-Fa-f]{6}$/;
return hexRegex.test(value) ? true : 'Please enter a valid hex color (e.g., #3b82f6)';
},
});
}
let secondaryColor: string;
if (skipPrompts) {
secondaryColor = '#64748b'; // Default secondary color
} else {
secondaryColor = await input({
message: 'Secondary color (hex code):',
default: '#64748b',
validate: (value: string) => {
const hexRegex = /^#[0-9A-Fa-f]{6}$/;
return hexRegex.test(value) ? true : 'Please enter a valid hex color (e.g., #64748b)';
},
});
}
let fontFamily: SecureUserChoices['customization']['fontFamily'];
if (skipPrompts) {
fontFamily = 'inter'; // Default font family
} else {
fontFamily = await select({
message: 'Choose a font family:',
choices: [
{ name: 'Inter - Modern, clean sans-serif', value: 'inter' },
{ name: 'Roboto - Google\'s signature font', value: 'roboto' },
{ name: 'Open Sans - Friendly, readable font', value: 'open-sans' },
{ name: 'Poppins - Geometric, modern look', value: 'poppins' },
],
}) as SecureUserChoices['customization']['fontFamily'];
}
let logoUrl: string | undefined;
if (!skipPrompts) {
const logoInput = await input({
message: 'Logo URL (optional):',
validate: (value: string) => {
if (!value.trim()) return true;
try {
const url = new URL(value);
if (!['http:', 'https:'].includes(url.protocol)) {
return 'Only HTTP and HTTPS URLs are allowed';
}
if (value.length > 2000) return 'URL too long';
return true;
} catch {
return 'Please enter a valid URL';
}
},
});
logoUrl = logoInput || undefined;
}
let faviconUrl: string | undefined;
if (!skipPrompts) {
const faviconInput = await input({
message: 'Favicon URL (optional):',
validate: (value: string) => {
if (!value.trim()) return true;
try {
const url = new URL(value);
if (!['http:', 'https:'].includes(url.protocol)) {
return 'Only HTTP and HTTPS URLs are allowed';
}
if (value.length > 2000) return 'URL too long';
return true;
} catch {
return 'Please enter a valid URL';
}
},
});
faviconUrl = faviconInput || undefined;
}
return {
primaryColor,
secondaryColor,
fontFamily,
logoUrl: logoUrl || undefined,
faviconUrl: faviconUrl || undefined,
};
}
/**
* Collects technical configuration
*/
private async collectTechnicalChoices(language: 'javascript' | 'typescript', skipPrompts = false): Promise<SecureUserChoices['technical']> {
let technicalFeatures: string[];
if (skipPrompts) {
// Use sensible defaults when skipping prompts
technicalFeatures = ['eslint', 'prettier', 'tailwind', 'shadcnUi'];
if (language === 'typescript') {
technicalFeatures.push('typescript');
}
} else {
technicalFeatures = await checkbox({
message: 'Select technical features:',
choices: [
{ name: 'TypeScript', value: 'typescript', checked: language === 'typescript' },
{ name: 'ESLint', value: 'eslint', checked: true },
{ name: 'Prettier', value: 'prettier', checked: true },
{ name: 'Tailwind CSS', value: 'tailwind', checked: true },
{ name: 'shadcn/ui components', value: 'shadcnUi', checked: true },
],
});
}
return {
typescript: technicalFeatures.includes('typescript'),
eslint: technicalFeatures.includes('eslint'),
prettier: technicalFeatures.includes('prettier'),
tailwind: technicalFeatures.includes('tailwind'),
shadcnUi: technicalFeatures.includes('shadcnUi'),
};
}
/**
* Confirms project generation with user
*/
private async confirmProjectGeneration(choices: SecureUserChoices): Promise<void> {
const summary = `
${chalk.bold('Project Configuration Summary:')}
${chalk.cyan('Basic Information:')}
Name: ${choices.projectName}
Description: ${choices.description}
Author: ${choices.author.name}${choices.author.email ? ` <${choices.author.email}>` : ''}
${chalk.cyan('Template & Theme:')}
Template: ${choices.template}
Theme: ${choices.theme}
Language: ${choices.language}
${chalk.cyan('Features:')}
Analytics: ${choices.features.analytics ? chalk.green('Yes') : chalk.gray('No')}
SEO: ${choices.features.seo ? chalk.green('Yes') : chalk.gray('No')}
PWA: ${choices.features.pwa ? chalk.green('Yes') : chalk.gray('No')}
Authentication: ${choices.features.authentication ? chalk.green('Yes') : chalk.gray('No')}
Database: ${choices.features.database ? chalk.green('Yes') : chalk.gray('No')}
${chalk.cyan('Output:')}
Directory: ${choices.outputDirectory}
Install Dependencies: ${choices.installDependencies ? chalk.green('Yes') : chalk.gray('No')}
Git Repository: ${choices.gitInit ? chalk.green('Yes') : chalk.gray('No')}
${chalk.yellow('Security:')} All inputs have been validated and sanitized.
`;
console.log(boxen(summary, {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'cyan',
}));
const confirmed = await confirm({
message: 'Generate project with this configuration?',
default: true,
});
if (!confirmed) {
throw new Error('Project generation cancelled by user');
}
}
/**
* Generates project using secure template manager
*/
private async generateProjectSecurely(choices: SecureUserChoices): Promise<SecureScaffoldResult> {
this.logger.startOperation('Project Generation');
this.spinner = ora('Initializing project generation...').start();
const projectConfig = {
name: choices.projectName,
template: choices.template,
theme: choices.theme,
path: dirname(choices.outputDirectory),
language: choices.language,
overwrite: choices.overwrite
};
try {
const result = await this.templateManager.scaffoldProject(projectConfig);
if (this.spinner) {
if (result.success) {
this.spinner.succeed('Project generation completed');
} else {
this.spinner.fail('Project generation failed');
}
this.spinner = undefined;
}
return result;
} catch (error) {
if (this.spinner) {
this.spinner.fail('Project generation error');
this.spinner = undefined;
}
throw error;
}
}
/**
* Processes scaffold result and updates CLI result
*/
private processScaffoldResult(scaffoldResult: SecureScaffoldResult, cliResult: SecureCLIResult): void {
cliResult.success = scaffoldResult.success;
cliResult.projectPath = scaffoldResult.projectPath;
cliResult.filesCreated = scaffoldResult.createdFiles;
cliResult.filesSkipped = scaffoldResult.skippedFiles;
cliResult.warnings = scaffoldResult.warnings;
cliResult.securityWarnings = scaffoldResult.securityWarnings;
if (scaffoldResult.success) {
// Generate next steps
cliResult.nextSteps = this.generateNextSteps(scaffoldResult);
this.displaySuccessResults(cliResult);
} else {
cliResult.errors.push(scaffoldResult.error || 'Unknown scaffolding error');
cliResult.canRetry = true;
this.displayErrorResults(cliResult);
}
}
/**
* Handles CLI errors with recovery attempts
*/
private async handleCLIError(error: unknown, result: SecureCLIResult): Promise<SecureCLIResult> {
result.duration = Date.now() - this.startTime;
if (this.spinner) {
this.spinner.fail('Operation failed');
this.spinner = undefined;
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
result.errors.push(errorMessage);
this.logger.failOperation('CLI Execution', error as Error);
// Security incident logging
if (error instanceof SecurityError) {
this.logger.security(
`Security error in CLI: ${errorMessage}`,
'cli_security_error',
'high',
undefined,
undefined,
{ securityType: error.securityType }
);
result.securityWarnings.push(`Security error: ${errorMessage}`);
}
// Attempt error recovery
result.canRetry = await this.attemptErrorRecovery(error, result);
this.displayErrorResults(result);
return result;
}
/**
* Attempts to recover from errors
*/
private async attemptErrorRecovery(error: unknown, result: SecureCLIResult): Promise<boolean> {
if (error instanceof SecurityError) {
// Security errors cannot be automatically recovered
result.retryInstructions = [
'Review and correct the input that caused the security error',
'Ensure all paths and names follow the security guidelines',
'Run the command again with corrected inputs'
];
return true;
}
if (error instanceof Error && error.message.includes('EACCES')) {
result.retryInstructions = [
'Check file and directory permissions',
'Ensure you have write access to the target directory',
'Try running with elevated permissions if necessary'
];
return true;
}
if (error instanceof Error && error.message.includes('ENOSPC')) {
result.retryInstructions = [
'Free up disk space',
'Try using a different output directory',
'Run the command again after cleaning up space'
];
return true;
}
return false;
}
/**
* Generates next steps for successful projects
*/
private generateNextSteps(scaffoldResult: SecureScaffoldResult): string[] {
const steps = [
`cd ${scaffoldResult.projectPath}`,
'bun install',
'bun dev',
];
if (scaffoldResult.securityWarnings.length > 0) {
steps.unshift('Review security warnings in the logs');
}
return steps;
}
/**
* Displays success results
*/
private displaySuccessResults(result: SecureCLIResult): void {
const successMessage = `${chalk.bold.green('🎉 Project Generated Successfully!')}
${chalk.cyan('Project Details:')}
Path: ${result.projectPath}
Duration: ${result.duration}ms
Files Created: ${result.filesCreated.length}
Files Skipped: ${result.filesSkipped.length}
${result.securityWarnings.length > 0 ? `${chalk.yellow('Security Warnings:')}
${result.securityWarnings.map(warning => ` ⚠ ${warning}`).join('\n')}
` : ''}${chalk.cyan('Next Steps:')}
${result.nextSteps.map(step => ` ${chalk.gray('$')} ${step}`).join('\n')}
${chalk.green('Your secure Next.js roadmap project is ready!')}
`;
console.log(boxen(successMessage, {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green',
}));
}
/**
* Displays error results with recovery information
*/
private displayErrorResults(result: SecureCLIResult): void {
let errorMessage = `${chalk.bold.red('❌ Project Generation Failed')}
${chalk.cyan('Error Details:')}
${result.errors.map(error => ` • ${error}`).join('\n')}
Duration: ${result.duration}ms
`;
if (result.securityWarnings.length > 0) {
errorMessage += `
${chalk.yellow('Security Warnings:')}
${result.securityWarnings.map(warning => ` ⚠ ${warning}`).join('\n')}
`;
}
if (result.canRetry && result.retryInstructions) {
errorMessage += `
${chalk.cyan('Recovery Instructions:')}
${result.retryInstructions.map(instruction => ` • ${instruction}`).join('\n')}
`;
}
console.log(boxen(errorMessage, {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'red',
}));
}
}
/**
* Main entry point for secure CLI execution
*/
export async function runSecureCLI(options: SecureCLIOptions = {}): Promise<SecureCLIResult> {
const cli = new SecureRoadKitCLI({
verbose: options.verbose,
logDir: process.env.ROADKIT_LOG_DIR || './logs'
});
return await cli.execute(options);
}
/**
* Factory function for creating secure CLI instances
*/
export function createSecureCLI(options?: {
verbose?: boolean;
logDir?: string;
templateDir?: string;
}): SecureRoadKitCLI {
return new SecureRoadKitCLI(options);
}