UNPKG

qraft

Version:

A powerful CLI tool to qraft structured project setups from GitHub template repositories

304 lines 12.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BoxNameDerivation = void 0; class BoxNameDerivation { deriveBoxName(localPath, structure, tags, analysis, providedName) { if (providedName) { return { primaryName: this.sanitizeName(providedName), alternatives: [], confidence: 1.0, derivationPath: ['user-provided'] }; } const suggestions = this.generateNameSuggestions(localPath, structure, tags, analysis); const sortedSuggestions = suggestions.sort((a, b) => b.confidence - a.confidence); const primaryName = sortedSuggestions[0]?.name || this.fallbackName(localPath); const alternatives = sortedSuggestions.slice(1, 5); // Top 4 alternatives return { primaryName, alternatives, confidence: sortedSuggestions[0]?.confidence || 0.5, derivationPath: this.buildDerivationPath(sortedSuggestions[0], localPath) }; } generateNameSuggestions(localPath, structure, tags, analysis) { const suggestions = []; // 1. Package.json name (highest priority) const packageName = this.extractPackageName(structure); if (packageName) { suggestions.push({ name: this.sanitizeName(packageName), confidence: 0.95, reason: 'Extracted from package.json name field', category: 'package' }); } // 2. Directory name with context const directoryName = this.extractDirectoryName(localPath); if (directoryName) { suggestions.push({ name: this.sanitizeName(directoryName), confidence: 0.8, reason: 'Based on directory name', category: 'directory' }); // Enhanced directory name with language/framework (lower confidence than base name) if (analysis.framework) { suggestions.push({ name: this.sanitizeName(`${directoryName}-${analysis.framework}`), confidence: 0.75, reason: `Directory name enhanced with ${analysis.framework} framework`, category: 'directory' }); } else if (analysis.primaryLanguage !== 'Unknown') { suggestions.push({ name: this.sanitizeName(`${directoryName}-${analysis.primaryLanguage.toLowerCase()}`), confidence: 0.72, reason: `Directory name enhanced with ${analysis.primaryLanguage} language`, category: 'directory' }); } } // 3. Semantic-based names if (tags.semanticTags.length > 0) { const primarySemantic = tags.semanticTags[0]; const baseName = directoryName || 'project'; suggestions.push({ name: this.sanitizeName(`${baseName}-${primarySemantic}`), confidence: 0.75, reason: `Based on primary semantic tag: ${primarySemantic}`, category: 'semantic' }); // Special semantic combinations if (tags.semanticTags.includes('api')) { suggestions.push({ name: this.sanitizeName(`${baseName}-api`), confidence: 0.78, reason: 'API project detected', category: 'semantic' }); } if (tags.semanticTags.includes('ai')) { suggestions.push({ name: this.sanitizeName(`${baseName}-ml`), confidence: 0.77, reason: 'AI/ML project detected', category: 'semantic' }); } } // 4. Structure-based names if (analysis.isMonorepo) { suggestions.push({ name: this.sanitizeName(`${directoryName || 'workspace'}-monorepo`), confidence: 0.7, reason: 'Monorepo structure detected', category: 'structure' }); } // 5. Framework-specific names if (tags.frameworkTags.length > 0) { const framework = tags.frameworkTags[0]; const baseName = directoryName || 'app'; suggestions.push({ name: this.sanitizeName(`${baseName}-${framework}`), confidence: 0.73, reason: `${framework} framework detected`, category: 'structure' }); // Special framework patterns if (framework === 'nextjs') { suggestions.push({ name: this.sanitizeName(`${baseName}-next-app`), confidence: 0.74, reason: 'Next.js application pattern', category: 'structure' }); } if (framework === 'react') { suggestions.push({ name: this.sanitizeName(`${baseName}-react-app`), confidence: 0.72, reason: 'React application pattern', category: 'structure' }); } } // 6. Nested structure analysis const nestedSuggestions = this.analyzeNestedStructure(structure, directoryName || 'project'); suggestions.push(...nestedSuggestions); // 7. File-based suggestions const fileSuggestions = this.analyzeFilePatterns(structure, directoryName || 'project'); suggestions.push(...fileSuggestions); return suggestions; } analyzeNestedStructure(structure, baseName) { const suggestions = []; const directories = structure.directories.map(d => d.name); // Check for common patterns if (directories.includes('src') && directories.includes('tests')) { suggestions.push({ name: this.sanitizeName(`${baseName}-library`), confidence: 0.68, reason: 'Library structure detected (src + tests)', category: 'structure' }); } if (directories.includes('packages')) { suggestions.push({ name: this.sanitizeName(`${baseName}-workspace`), confidence: 0.69, reason: 'Workspace structure detected', category: 'structure' }); } if (directories.includes('apps') && directories.includes('libs')) { suggestions.push({ name: this.sanitizeName(`${baseName}-nx-workspace`), confidence: 0.71, reason: 'Nx workspace structure detected', category: 'structure' }); } if (directories.includes('frontend') && directories.includes('backend')) { suggestions.push({ name: this.sanitizeName(`${baseName}-fullstack`), confidence: 0.72, reason: 'Full-stack structure detected', category: 'structure' }); } return suggestions; } analyzeFilePatterns(structure, baseName) { const suggestions = []; const fileNames = structure.files.map(f => f.name); // Check for specific file patterns if (fileNames.includes('Dockerfile')) { suggestions.push({ name: this.sanitizeName(`${baseName}-docker`), confidence: 0.65, reason: 'Docker configuration detected', category: 'structure' }); } if (fileNames.includes('terraform.tf') || fileNames.some(f => f.endsWith('.tf'))) { suggestions.push({ name: this.sanitizeName(`${baseName}-terraform`), confidence: 0.67, reason: 'Terraform configuration detected', category: 'structure' }); } if (fileNames.includes('serverless.yml') || fileNames.includes('serverless.yaml')) { suggestions.push({ name: this.sanitizeName(`${baseName}-serverless`), confidence: 0.66, reason: 'Serverless configuration detected', category: 'structure' }); } if (fileNames.some(f => f.startsWith('requirements') && f.endsWith('.txt'))) { suggestions.push({ name: this.sanitizeName(`${baseName}-python`), confidence: 0.64, reason: 'Python requirements file detected', category: 'structure' }); } return suggestions; } extractPackageName(structure) { const packageFile = structure.files.find(f => f.name === 'package.json'); if (packageFile?.content) { try { const packageJson = JSON.parse(packageFile.content); return packageJson.name; } catch { return null; } } return null; } extractDirectoryName(localPath) { const path = require('path'); const resolved = path.resolve(localPath); const basename = path.basename(resolved); // Handle complex paths by cleaning them up if (basename === '.' || basename === '') { const parts = resolved.split(path.sep).filter((p) => p && p !== '.'); return parts[parts.length - 1] || 'project'; } return basename; } sanitizeName(name) { return name .toLowerCase() .replace(/[@\/\\]/g, '-') // Replace package scopes and path separators .replace(/[^a-z0-9-]/g, '-') // Replace invalid characters .replace(/-+/g, '-') // Collapse multiple hyphens .replace(/^-/, '') // Remove leading hyphen .replace(/-$/, '') // Remove trailing hyphen .substring(0, 50); // Limit length } fallbackName(localPath) { const directoryName = this.extractDirectoryName(localPath); return this.sanitizeName(directoryName || 'box'); } buildDerivationPath(suggestion, _localPath) { if (!suggestion) { return ['fallback', 'directory-name']; } const path = [suggestion.category]; switch (suggestion.category) { case 'package': path.push('package-json', 'name-field'); break; case 'directory': path.push('directory-name'); if (suggestion.reason.includes('enhanced')) { path.push('enhanced-with-context'); } break; case 'semantic': path.push('semantic-tags', 'primary-tag'); break; case 'structure': path.push('structure-analysis', 'pattern-detection'); break; default: path.push('unknown'); } return path; } // Get name suggestions for interactive selection getNameSuggestions(localPath, structure, tags, analysis, count = 5) { const suggestions = this.generateNameSuggestions(localPath, structure, tags, analysis); return suggestions .sort((a, b) => b.confidence - a.confidence) .slice(0, count); } // Validate if a name is acceptable validateName(name) { if (!name || name.trim().length === 0) { return { valid: false, reason: 'Name cannot be empty' }; } const sanitized = this.sanitizeName(name); if (sanitized.length < 2) { return { valid: false, reason: 'Name must be at least 2 characters long' }; } if (sanitized !== name.toLowerCase()) { return { valid: false, reason: 'Name contains invalid characters. Use only letters, numbers, and hyphens.' }; } const reservedNames = ['api', 'www', 'admin', 'root', 'test', 'demo']; if (reservedNames.includes(sanitized)) { return { valid: false, reason: `"${sanitized}" is a reserved name` }; } return { valid: true }; } } exports.BoxNameDerivation = BoxNameDerivation; //# sourceMappingURL=boxNameDerivation.js.map