/**
* Validate project name format
* - Must start with lowercase letter
* - Only lowercase letters, numbers, and hyphens
* - Max 50 characters
*/
export function validateProjectName(value: string): string | true {
if (!value.trim()) {
return 'Project name is required';
}
if (!/^[a-z][a-z0-9-]*$/.test(value)) {
return 'Project name must start with a letter and contain only lowercase letters, numbers, and hyphens';
}
if (value.length > 50) {
return 'Project name must be 50 characters or less';
}
return true;
}
/**
* Validate port number
*/
export function validatePort(value: string): string | true {
const port = parseInt(value, 10);
if (isNaN(port)) {
return 'Port must be a number';
}
if (port < 1024 || port > 65535) {
return 'Port must be between 1024 and 65535';
}
return true;
}
/**
* Validate URL format
*/
export function validateUrl(value: string): string | true {
try {
new URL(value);
return true;
} catch {
return 'Please enter a valid URL';
}
}
/**
* Create async validator that checks for conflicts
*/
export function createConflictValidator(
checkFn: (value: string) => Promise<boolean>
): (value: string) => Promise<string | true> {
return async (value: string) => {
const exists = await checkFn(value);
if (exists) {
return `"${value}" already exists`;
}
return true;
};
}
Custom Formatters (formatters.ts)
import chalk from 'chalk';
/**
* Format project name for display
*/
export function formatProjectName(value: string): string {
return chalk.cyan(value);
}
/**
* Format feature list for summary
*/
export function formatFeatures(features: string[]): string {
if (features.length === 0) {
return chalk.dim('None selected');
}
return features.map(f => chalk.green(`+ ${f}`)).join('\n');
}
/**
* Format configuration summary
*/
export function formatSummary(config: ProjectConfig): string {
return `
${chalk.bold('Project Configuration:')}
${chalk.dim('Name:')} ${formatProjectName(config.projectName)}
${chalk.dim('Template:')} ${config.template}
${chalk.dim('Features:')}
${formatFeatures(config.features).split('\n').map(l => ' ' + l).join('\n')}
${chalk.dim('Install:')} ${config.installDeps ? chalk.green('Yes') : chalk.yellow('No')}
`;
}