validation.ts•9.13 kB
import { ValidationResult } from '../types/index.js';
export class ValidationService {
static validateGitHubUrl(url: string): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!url) {
errors.push('URL is required');
return { isValid: false, errors, warnings };
}
// Check if it's a valid GitHub URL
const githubRegex = /^https:\/\/github\.com\/[\w\-\.]+\/[\w\-\.]+\/?$/;
if (!githubRegex.test(url)) {
errors.push('Invalid GitHub URL format. Expected: https://github.com/owner/repo');
}
// Check for common issues
if (url.includes('/tree/') || url.includes('/blob/')) {
warnings.push('URL appears to point to a specific file or branch. Consider using the repository root URL.');
}
if (url.endsWith('.git')) {
warnings.push('URL ends with .git. This may cause issues with the GitHub API.');
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
static validateFilePath(filePath: string): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!filePath) {
errors.push('File path is required');
return { isValid: false, errors, warnings };
}
// Check for security issues
if (filePath.includes('..')) {
errors.push('File path contains directory traversal sequences (..)');
}
if (filePath.startsWith('/')) {
errors.push('File path should not start with /');
}
// Check for common issues
if (filePath.includes('\\')) {
warnings.push('File path contains backslashes. Use forward slashes instead.');
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
static validateRefactorOptions(options: any): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!options) {
return { isValid: true, errors, warnings };
}
// Validate naming convention
if (options.namingConvention) {
const validConventions = ['camelCase', 'snake_case', 'kebab-case', 'PascalCase'];
if (!validConventions.includes(options.namingConvention)) {
errors.push(`Invalid naming convention. Must be one of: ${validConventions.join(', ')}`);
}
}
// Validate modernization level
if (options.modernizationLevel) {
const validLevels = ['minimal', 'moderate', 'aggressive'];
if (!validLevels.includes(options.modernizationLevel)) {
errors.push(`Invalid modernization level. Must be one of: ${validLevels.join(', ')}`);
}
}
// Validate target framework
if (options.targetFramework) {
const supportedFrameworks = ['react', 'vue', 'angular', 'express', 'fastify', 'koa'];
if (!supportedFrameworks.includes(options.targetFramework.toLowerCase())) {
warnings.push(`Framework '${options.targetFramework}' may not be fully supported. Supported frameworks: ${supportedFrameworks.join(', ')}`);
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
static validateComponentTypes(types: string[]): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!types || types.length === 0) {
return { isValid: true, errors, warnings };
}
const validTypes = ['ui-components', 'hooks', 'utilities', 'services', 'models', 'types'];
for (const type of types) {
if (!validTypes.includes(type)) {
errors.push(`Invalid component type: ${type}. Valid types: ${validTypes.join(', ')}`);
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
static validateLanguage(language: string): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!language) {
errors.push('Language is required');
return { isValid: false, errors, warnings };
}
const supportedLanguages = [
'javascript',
'typescript',
'python',
'java',
'cpp',
'c',
'go',
'rust',
'php',
'ruby',
'swift',
'kotlin',
'dart',
];
if (!supportedLanguages.includes(language.toLowerCase())) {
warnings.push(`Language '${language}' may not be fully supported. Supported languages: ${supportedLanguages.join(', ')}`);
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
static validateTemplateOptions(options: any): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!options) {
return { isValid: true, errors, warnings };
}
// Validate template type
if (options.templateType) {
const validTypes = ['starter', 'component-library', 'microservice', 'fullstack', 'cli-tool', 'library'];
if (!validTypes.includes(options.templateType)) {
errors.push(`Invalid template type: ${options.templateType}. Valid types: ${validTypes.join(', ')}`);
}
}
// Validate package manager
if (options.packageManager) {
const validManagers = ['npm', 'yarn', 'pnpm', 'bun'];
if (!validManagers.includes(options.packageManager)) {
errors.push(`Invalid package manager: ${options.packageManager}. Valid managers: ${validManagers.join(', ')}`);
}
}
// Validate name
if (options.name && !/^[a-z0-9-]+$/.test(options.name)) {
errors.push('Template name must contain only lowercase letters, numbers, and hyphens');
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
static validateSearchQuery(query: string): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!query) {
errors.push('Search query is required');
return { isValid: false, errors, warnings };
}
if (query.length < 2) {
errors.push('Search query must be at least 2 characters long');
}
if (query.length > 1000) {
errors.push('Search query is too long (max 1000 characters)');
}
// Check for potentially problematic regex patterns
try {
new RegExp(query);
} catch (e) {
warnings.push('Search query may contain invalid regex patterns');
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
static validateCodeInput(code: string): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!code) {
errors.push('Code input is required');
return { isValid: false, errors, warnings };
}
if (code.length > 1000000) { // 1MB limit
errors.push('Code input is too large (max 1MB)');
}
// Check for potentially malicious patterns
const maliciousPatterns = [
/eval\s*\(/,
/Function\s*\(/,
/document\.write/,
/innerHTML\s*=/,
/dangerouslySetInnerHTML/,
];
for (const pattern of maliciousPatterns) {
if (pattern.test(code)) {
warnings.push('Code contains potentially unsafe patterns');
break;
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
static validateDependencyMappings(mappings: Record<string, string>): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!mappings) {
return { isValid: true, errors, warnings };
}
for (const [oldDep, newDep] of Object.entries(mappings)) {
if (!oldDep || !newDep) {
errors.push('Dependency mappings must have both old and new values');
continue;
}
// Check for valid package names
const packageNameRegex = /^[@a-z0-9-~][a-z0-9-._~]*\/[a-z0-9-._~]*$|^[a-z0-9-~][a-z0-9-._~]*$/;
if (!packageNameRegex.test(oldDep)) {
warnings.push(`Old dependency name '${oldDep}' may not be valid`);
}
if (!packageNameRegex.test(newDep)) {
warnings.push(`New dependency name '${newDep}' may not be valid`);
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
static validateFileExtensions(extensions: string[]): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!extensions || extensions.length === 0) {
return { isValid: true, errors, warnings };
}
for (const ext of extensions) {
if (!ext.startsWith('.')) {
errors.push(`File extension '${ext}' must start with a dot`);
}
if (ext.length < 2) {
errors.push(`File extension '${ext}' is too short`);
}
if (!/^\.[\w]+$/.test(ext)) {
errors.push(`File extension '${ext}' contains invalid characters`);
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
};
}
}