UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

1,269 lines (1,112 loc) 33.8 kB
/** * CI/CD Discovery * Gap Analysis Implementation * * Auto-detects CI/CD infrastructure in projects and provides * integration points for AIOS workflows. */ const fs = require('fs'); const path = require('path'); const { EventEmitter } = require('events'); /** * Provider configurations for different CI/CD systems */ const PROVIDERS = { 'github-actions': { name: 'GitHub Actions', configPaths: ['.github/workflows'], configPatterns: ['*.yml', '*.yaml'], triggerTypes: ['push', 'pull_request', 'workflow_dispatch', 'schedule', 'release'], }, 'gitlab-ci': { name: 'GitLab CI', configPaths: ['.'], configPatterns: ['.gitlab-ci.yml', '.gitlab-ci.yaml'], triggerTypes: ['push', 'merge_request', 'schedule', 'pipeline', 'trigger'], }, jenkins: { name: 'Jenkins', configPaths: ['.'], configPatterns: ['Jenkinsfile', 'jenkins.yml', 'jenkins.yaml'], triggerTypes: ['scm', 'cron', 'upstream', 'manual'], }, circleci: { name: 'CircleCI', configPaths: ['.circleci'], configPatterns: ['config.yml', 'config.yaml'], triggerTypes: ['push', 'pull_request', 'schedule', 'api'], }, 'travis-ci': { name: 'Travis CI', configPaths: ['.'], configPatterns: ['.travis.yml', '.travis.yaml'], triggerTypes: ['push', 'pull_request', 'cron', 'api'], }, 'azure-pipelines': { name: 'Azure Pipelines', configPaths: ['.'], configPatterns: ['azure-pipelines.yml', 'azure-pipelines.yaml'], triggerTypes: ['push', 'pull_request', 'schedule', 'manual'], }, bitbucket: { name: 'Bitbucket Pipelines', configPaths: ['.'], configPatterns: ['bitbucket-pipelines.yml'], triggerTypes: ['push', 'pull_request', 'manual', 'schedule'], }, 'aws-codepipeline': { name: 'AWS CodePipeline', configPaths: ['.'], configPatterns: ['buildspec.yml', 'buildspec.yaml'], triggerTypes: ['push', 'manual', 'cloudwatch'], }, }; /** * ConfigParser - Parses CI/CD configuration files */ class ConfigParser { constructor() { this.yamlParser = null; } /** * Parse YAML content (simple parser without external deps) */ parseYaml(content) { // Simple YAML parser for CI/CD configs const result = {}; const lines = content.split('\n'); const stack = [{ obj: result, indent: -1 }]; for (const line of lines) { // Skip comments and empty lines if (line.trim().startsWith('#') || !line.trim()) continue; const indent = line.search(/\S/); const trimmed = line.trim(); // Pop stack until we find parent with smaller indent while (stack.length > 1 && stack[stack.length - 1].indent >= indent) { stack.pop(); } const parent = stack[stack.length - 1].obj; // Handle list items if (trimmed.startsWith('- ')) { const value = trimmed.substring(2).trim(); const lastKey = Object.keys(parent).pop(); if (lastKey && !Array.isArray(parent[lastKey])) { parent[lastKey] = []; } if (lastKey) { if (value.includes(':')) { const obj = {}; const [k, v] = value.split(':').map((s) => s.trim()); obj[k] = v || {}; parent[lastKey].push(obj); stack.push({ obj: obj, indent: indent }); } else { parent[lastKey].push(value); } } continue; } // Handle key-value pairs const colonIdx = trimmed.indexOf(':'); if (colonIdx > 0) { const key = trimmed.substring(0, colonIdx).trim(); const value = trimmed.substring(colonIdx + 1).trim(); if (value) { // Inline value parent[key] = value.replace(/^["']|["']$/g, ''); } else { // Nested object parent[key] = {}; stack.push({ obj: parent[key], indent: indent }); } } } return result; } /** * Parse Jenkinsfile (Groovy-based) */ parseJenkinsfile(content) { const result = { type: 'jenkinsfile', stages: [], agents: [], environment: {}, triggers: [], options: [], }; // Extract pipeline blocks const pipelineMatch = content.match(/pipeline\s*\{([\s\S]*)\}/); if (!pipelineMatch) { // Scripted pipeline result.type = 'scripted'; const stageMatches = content.matchAll(/stage\s*\(\s*['"]([^'"]+)['"]\s*\)/g); for (const match of stageMatches) { result.stages.push({ name: match[1] }); } return result; } const pipelineContent = pipelineMatch[1]; // Extract agent const agentMatch = pipelineContent.match(/agent\s*\{([^}]+)\}/); if (agentMatch) { const agentContent = agentMatch[1].trim(); if (agentContent.includes('docker')) { result.agents.push({ type: 'docker', config: agentContent }); } else if (agentContent.includes('kubernetes')) { result.agents.push({ type: 'kubernetes', config: agentContent }); } else if (agentContent.includes('label')) { const labelMatch = agentContent.match(/label\s+['"]([^'"]+)['"]/); result.agents.push({ type: 'label', value: labelMatch?.[1] }); } else { result.agents.push({ type: 'any' }); } } // Extract stages const stagesMatch = pipelineContent.match( /stages\s*\{([\s\S]*?)\}(?=\s*(?:post|options|triggers|\}|$))/ ); if (stagesMatch) { const stageMatches = stagesMatch[1].matchAll(/stage\s*\(\s*['"]([^'"]+)['"]\s*\)/g); for (const match of stageMatches) { result.stages.push({ name: match[1] }); } } // Extract environment const envMatch = pipelineContent.match(/environment\s*\{([^}]+)\}/); if (envMatch) { const envLines = envMatch[1].trim().split('\n'); for (const line of envLines) { const eqMatch = line.match(/(\w+)\s*=\s*(.+)/); if (eqMatch) { result.environment[eqMatch[1]] = eqMatch[2].trim(); } } } // Extract triggers const triggersMatch = pipelineContent.match(/triggers\s*\{([^}]+)\}/); if (triggersMatch) { if (triggersMatch[1].includes('pollSCM')) result.triggers.push('scm'); if (triggersMatch[1].includes('cron')) result.triggers.push('cron'); if (triggersMatch[1].includes('upstream')) result.triggers.push('upstream'); } return result; } /** * Parse GitHub Actions workflow */ parseGitHubActions(content, filename) { const config = this.parseYaml(content); const result = { name: config.name || filename, triggers: [], jobs: [], env: config.env || {}, }; // Extract triggers if (config.on) { if (typeof config.on === 'string') { result.triggers.push(config.on); } else if (Array.isArray(config.on)) { result.triggers = config.on; } else { result.triggers = Object.keys(config.on); } } // Extract jobs if (config.jobs) { for (const [jobName, jobConfig] of Object.entries(config.jobs)) { const job = { name: jobName, runsOn: jobConfig['runs-on'] || 'ubuntu-latest', steps: [], needs: jobConfig.needs || [], env: jobConfig.env || {}, }; if (Array.isArray(jobConfig.steps)) { for (const step of jobConfig.steps) { job.steps.push({ name: step.name || step.uses || step.run?.substring(0, 50), uses: step.uses, run: step.run, }); } } result.jobs.push(job); } } return result; } /** * Parse GitLab CI configuration */ parseGitLabCI(content) { const config = this.parseYaml(content); const result = { stages: config.stages || [], jobs: [], variables: config.variables || {}, include: config.include || [], }; // Reserved keywords (not jobs) const reserved = [ 'stages', 'variables', 'include', 'default', 'workflow', 'image', 'services', 'before_script', 'after_script', 'cache', ]; // Extract jobs for (const [key, value] of Object.entries(config)) { if (reserved.includes(key) || key.startsWith('.')) continue; if (typeof value === 'object') { result.jobs.push({ name: key, stage: value.stage || 'test', script: value.script || [], only: value.only || [], except: value.except || [], needs: value.needs || [], }); } } return result; } /** * Parse CircleCI configuration */ parseCircleCI(content) { const config = this.parseYaml(content); const result = { version: config.version, orbs: config.orbs || {}, jobs: [], workflows: [], }; // Extract jobs if (config.jobs) { for (const [name, jobConfig] of Object.entries(config.jobs)) { result.jobs.push({ name, docker: jobConfig.docker || [], steps: jobConfig.steps || [], workingDirectory: jobConfig.working_directory, }); } } // Extract workflows if (config.workflows) { for (const [name, wfConfig] of Object.entries(config.workflows)) { if (name === 'version') continue; result.workflows.push({ name, jobs: wfConfig.jobs || [], triggers: wfConfig.triggers || [], }); } } return result; } /** * Parse any CI/CD config based on provider */ parse(content, provider, filename = '') { switch (provider) { case 'github-actions': return this.parseGitHubActions(content, filename); case 'gitlab-ci': return this.parseGitLabCI(content); case 'jenkins': return this.parseJenkinsfile(content); case 'circleci': return this.parseCircleCI(content); default: return this.parseYaml(content); } } } /** * ProviderDetector - Detects which CI/CD providers are in use */ class ProviderDetector { constructor(rootPath) { this.rootPath = rootPath; } /** * Detect all CI/CD providers in the project */ async detect() { const detected = []; for (const [providerId, config] of Object.entries(PROVIDERS)) { const files = this.findConfigFiles(providerId, config); if (files.length > 0) { detected.push({ id: providerId, name: config.name, files, triggerTypes: config.triggerTypes, }); } } return detected; } /** * Find configuration files for a provider */ findConfigFiles(providerId, config) { const files = []; for (const configPath of config.configPaths) { const fullPath = path.join(this.rootPath, configPath); if (!fs.existsSync(fullPath)) continue; const stat = fs.statSync(fullPath); if (stat.isDirectory()) { // Search directory for matching files try { const entries = fs.readdirSync(fullPath); for (const entry of entries) { if (this.matchesPattern(entry, config.configPatterns)) { files.push(path.join(configPath, entry)); } } } catch { // Ignore permission errors } } else { // Direct file match const basename = path.basename(fullPath); if (this.matchesPattern(basename, config.configPatterns)) { files.push(configPath); } } } return files; } /** * Check if filename matches any pattern */ matchesPattern(filename, patterns) { for (const pattern of patterns) { if (pattern.startsWith('*.')) { const ext = pattern.substring(1); if (filename.endsWith(ext)) return true; } else if (filename === pattern) { return true; } } return false; } } /** * PipelineAnalyzer - Analyzes parsed pipeline configurations */ class PipelineAnalyzer { /** * Analyze a parsed pipeline configuration */ analyze(parsed, provider) { const analysis = { provider, complexity: 'simple', hasTests: false, hasBuild: false, hasDeploy: false, hasLint: false, hasSecurityScan: false, environments: [], secrets: [], dependencies: [], parallelism: false, caching: false, artifacts: false, }; // Analyze based on provider switch (provider) { case 'github-actions': this.analyzeGitHubActions(parsed, analysis); break; case 'gitlab-ci': this.analyzeGitLabCI(parsed, analysis); break; case 'jenkins': this.analyzeJenkins(parsed, analysis); break; case 'circleci': this.analyzeCircleCI(parsed, analysis); break; default: this.analyzeGeneric(parsed, analysis); } // Calculate complexity analysis.complexity = this.calculateComplexity(analysis); return analysis; } /** * Analyze GitHub Actions workflow */ analyzeGitHubActions(parsed, analysis) { const jobs = parsed.jobs || []; for (const job of jobs) { // Check for parallel jobs if (jobs.length > 1) { analysis.parallelism = true; } // Check steps for (const step of job.steps || []) { const stepStr = JSON.stringify(step).toLowerCase(); // Test detection if ( stepStr.includes('test') || stepStr.includes('jest') || stepStr.includes('pytest') || stepStr.includes('npm run test') ) { analysis.hasTests = true; } // Build detection if ( stepStr.includes('build') || stepStr.includes('compile') || stepStr.includes('npm run build') ) { analysis.hasBuild = true; } // Deploy detection if ( stepStr.includes('deploy') || stepStr.includes('publish') || stepStr.includes('release') ) { analysis.hasDeploy = true; } // Lint detection if ( stepStr.includes('lint') || stepStr.includes('eslint') || stepStr.includes('prettier') ) { analysis.hasLint = true; } // Security scan detection if ( stepStr.includes('security') || stepStr.includes('snyk') || stepStr.includes('codeql') || stepStr.includes('trivy') ) { analysis.hasSecurityScan = true; } // Cache detection if (step.uses?.includes('cache')) { analysis.caching = true; } // Artifacts detection if (step.uses?.includes('upload-artifact') || step.uses?.includes('download-artifact')) { analysis.artifacts = true; } } // Extract environments from job if (job.env) { for (const key of Object.keys(job.env)) { if (key.includes('ENV') || key.includes('ENVIRONMENT')) { const value = job.env[key]; if (!analysis.environments.includes(value)) { analysis.environments.push(value); } } } } // Extract secrets const jobStr = JSON.stringify(job); const secretMatches = jobStr.matchAll(/secrets\.(\w+)/g); for (const match of secretMatches) { if (!analysis.secrets.includes(match[1])) { analysis.secrets.push(match[1]); } } } } /** * Analyze GitLab CI configuration */ analyzeGitLabCI(parsed, analysis) { const stages = parsed.stages || []; const jobs = parsed.jobs || []; // Check stages for common patterns for (const stage of stages) { const stageLower = stage.toLowerCase(); if (stageLower.includes('test')) analysis.hasTests = true; if (stageLower.includes('build')) analysis.hasBuild = true; if (stageLower.includes('deploy')) analysis.hasDeploy = true; if (stageLower.includes('lint')) analysis.hasLint = true; if (stageLower.includes('security') || stageLower.includes('scan')) { analysis.hasSecurityScan = true; } } // Check jobs for (const job of jobs) { const jobStr = JSON.stringify(job).toLowerCase(); if (jobStr.includes('test')) analysis.hasTests = true; if (jobStr.includes('build')) analysis.hasBuild = true; if (jobStr.includes('deploy')) analysis.hasDeploy = true; // Extract environments if (job.environment) { analysis.environments.push(job.environment); } } // Extract variables/secrets if (parsed.variables) { for (const key of Object.keys(parsed.variables)) { if (key.includes('SECRET') || key.includes('TOKEN') || key.includes('KEY')) { analysis.secrets.push(key); } } } // Parallelism (multiple jobs in same stage) const jobsByStage = {}; for (const job of jobs) { const stage = job.stage || 'test'; jobsByStage[stage] = (jobsByStage[stage] || 0) + 1; } if (Object.values(jobsByStage).some((count) => count > 1)) { analysis.parallelism = true; } } /** * Analyze Jenkins pipeline */ analyzeJenkins(parsed, analysis) { const stages = parsed.stages || []; for (const stage of stages) { const nameLower = stage.name.toLowerCase(); if (nameLower.includes('test')) analysis.hasTests = true; if (nameLower.includes('build')) analysis.hasBuild = true; if (nameLower.includes('deploy')) analysis.hasDeploy = true; if (nameLower.includes('lint')) analysis.hasLint = true; if (nameLower.includes('security') || nameLower.includes('scan')) { analysis.hasSecurityScan = true; } } // Check environment for secrets if (parsed.environment) { for (const [key, value] of Object.entries(parsed.environment)) { if (value.includes('credentials(') || key.includes('SECRET') || key.includes('TOKEN')) { analysis.secrets.push(key); } } } // Check triggers if (parsed.triggers?.length > 0) { analysis.triggers = parsed.triggers; } } /** * Analyze CircleCI configuration */ analyzeCircleCI(parsed, analysis) { const jobs = parsed.jobs || []; const workflows = parsed.workflows || []; for (const job of jobs) { const nameLower = job.name.toLowerCase(); const stepsStr = JSON.stringify(job.steps || []).toLowerCase(); if (nameLower.includes('test') || stepsStr.includes('test')) { analysis.hasTests = true; } if (nameLower.includes('build') || stepsStr.includes('build')) { analysis.hasBuild = true; } if (nameLower.includes('deploy') || stepsStr.includes('deploy')) { analysis.hasDeploy = true; } // Check for caching if (stepsStr.includes('restore_cache') || stepsStr.includes('save_cache')) { analysis.caching = true; } // Check for artifacts if (stepsStr.includes('store_artifacts') || stepsStr.includes('persist_to_workspace')) { analysis.artifacts = true; } } // Check workflows for parallelism for (const workflow of workflows) { if (workflow.jobs?.length > 1) { analysis.parallelism = true; } } // Check orbs if (parsed.orbs) { for (const orbName of Object.keys(parsed.orbs)) { if (orbName.includes('security') || orbName.includes('snyk')) { analysis.hasSecurityScan = true; } } } } /** * Generic analysis for unknown providers */ analyzeGeneric(parsed, analysis) { const str = JSON.stringify(parsed).toLowerCase(); analysis.hasTests = str.includes('test'); analysis.hasBuild = str.includes('build'); analysis.hasDeploy = str.includes('deploy'); analysis.hasLint = str.includes('lint'); analysis.hasSecurityScan = str.includes('security') || str.includes('scan'); } /** * Calculate pipeline complexity */ calculateComplexity(analysis) { let score = 0; if (analysis.hasTests) score += 1; if (analysis.hasBuild) score += 1; if (analysis.hasDeploy) score += 2; if (analysis.hasLint) score += 1; if (analysis.hasSecurityScan) score += 2; if (analysis.parallelism) score += 1; if (analysis.caching) score += 1; if (analysis.artifacts) score += 1; if (analysis.environments.length > 1) score += 2; if (analysis.secrets.length > 3) score += 1; if (score <= 3) return 'simple'; if (score <= 6) return 'moderate'; if (score <= 9) return 'complex'; return 'enterprise'; } } /** * IntegrationSuggester - Suggests AIOS integration points */ class IntegrationSuggester { /** * Generate integration suggestions based on analysis */ suggest(analysis, provider) { const suggestions = []; // Add AIOS build step suggestions.push({ type: 'add_step', priority: 'high', title: 'Add AIOS Build Orchestration', description: 'Integrate AIOS build orchestrator for intelligent parallel builds', code: this.getAIOSBuildStep(provider), }); // Add test step if missing if (!analysis.hasTests) { suggestions.push({ type: 'add_step', priority: 'medium', title: 'Add Test Discovery', description: 'Use AIOS test discovery to automatically find and run tests', code: this.getTestStep(provider), }); } // Add lint step if missing if (!analysis.hasLint) { suggestions.push({ type: 'add_step', priority: 'low', title: 'Add Linting Step', description: 'Add code quality checks with ESLint/Prettier', code: this.getLintStep(provider), }); } // Add security scan if missing if (!analysis.hasSecurityScan) { suggestions.push({ type: 'add_step', priority: 'high', title: 'Add Security Scanning', description: 'Add AIOS PR Review AI for security analysis', code: this.getSecurityStep(provider), }); } // Add caching if missing if (!analysis.caching && provider === 'github-actions') { suggestions.push({ type: 'add_step', priority: 'medium', title: 'Add Dependency Caching', description: 'Cache node_modules to speed up builds', code: this.getCacheStep(provider), }); } // Add AIOS status reporting suggestions.push({ type: 'add_step', priority: 'low', title: 'Add AIOS Status Reporting', description: 'Report build status to AIOS dashboard', code: this.getStatusStep(provider), }); return suggestions; } /** * Get AIOS build step for provider */ getAIOSBuildStep(provider) { const steps = { 'github-actions': `- name: AIOS Build Orchestration run: npx aios build --parallel --smart-cache env: AIOS_CI: true`, 'gitlab-ci': `aios_build: stage: build script: - npx aios build --parallel --smart-cache variables: AIOS_CI: "true"`, jenkins: `stage('AIOS Build') { steps { sh 'npx aios build --parallel --smart-cache' } environment { AIOS_CI = 'true' } }`, circleci: `- run: name: AIOS Build Orchestration command: npx aios build --parallel --smart-cache environment: AIOS_CI: true`, }; return steps[provider] || steps['github-actions']; } /** * Get test step for provider */ getTestStep(provider) { const steps = { 'github-actions': `- name: Run Tests run: npx aios test --discover --coverage`, 'gitlab-ci': `test: stage: test script: - npx aios test --discover --coverage coverage: '/Coverage: \\d+\\.\\d+%/'`, jenkins: `stage('Test') { steps { sh 'npx aios test --discover --coverage' } }`, circleci: `- run: name: Run Tests command: npx aios test --discover --coverage`, }; return steps[provider] || steps['github-actions']; } /** * Get lint step for provider */ getLintStep(provider) { const steps = { 'github-actions': `- name: Lint Code run: npm run lint`, 'gitlab-ci': `lint: stage: test script: - npm run lint`, jenkins: `stage('Lint') { steps { sh 'npm run lint' } }`, circleci: `- run: name: Lint Code command: npm run lint`, }; return steps[provider] || steps['github-actions']; } /** * Get security step for provider */ getSecurityStep(provider) { const steps = { 'github-actions': `- name: Security Scan run: npx aios review --security-only continue-on-error: true`, 'gitlab-ci': `security_scan: stage: test script: - npx aios review --security-only allow_failure: true`, jenkins: `stage('Security Scan') { steps { sh 'npx aios review --security-only' } post { failure { echo 'Security issues found' } } }`, circleci: `- run: name: Security Scan command: npx aios review --security-only when: always`, }; return steps[provider] || steps['github-actions']; } /** * Get cache step for GitHub Actions */ getCacheStep(provider) { if (provider !== 'github-actions') return ''; return `- name: Cache node_modules uses: actions/cache@v4 with: path: node_modules key: \${{ runner.os }}-node-\${{ hashFiles('**/package-lock.json') }} restore-keys: | \${{ runner.os }}-node-`; } /** * Get status reporting step */ getStatusStep(provider) { const steps = { 'github-actions': `- name: Report to AIOS if: always() run: npx aios status --report-ci env: AIOS_BUILD_STATUS: \${{ job.status }}`, 'gitlab-ci': `report_status: stage: .post script: - npx aios status --report-ci when: always`, jenkins: `post { always { sh 'npx aios status --report-ci' } }`, circleci: `- run: name: Report to AIOS command: npx aios status --report-ci when: always`, }; return steps[provider] || steps['github-actions']; } } /** * CICDDiscovery - Main discovery engine */ class CICDDiscovery extends EventEmitter { constructor(config = {}) { super(); this.rootPath = config.rootPath || process.cwd(); this.detector = new ProviderDetector(this.rootPath); this.parser = new ConfigParser(); this.analyzer = new PipelineAnalyzer(); this.suggester = new IntegrationSuggester(); } /** * Scan project for CI/CD configurations */ async scan(options = {}) { this.emit('scan:start', { rootPath: this.rootPath }); const result = { providers: [], pipelines: [], analysis: {}, suggestions: [], summary: {}, }; try { // Detect providers const providers = await this.detector.detect(); result.providers = providers; this.emit('providers:detected', { count: providers.length, providers }); if (providers.length === 0) { result.summary = { hasCI: false, message: 'No CI/CD configuration found', recommendation: 'Consider adding GitHub Actions for automated builds', }; this.emit('scan:complete', result); return result; } // Parse and analyze each provider's configs for (const provider of providers) { for (const file of provider.files) { const filePath = path.join(this.rootPath, file); try { const content = fs.readFileSync(filePath, 'utf8'); const parsed = this.parser.parse(content, provider.id, path.basename(file)); const analysis = this.analyzer.analyze(parsed, provider.id); const pipeline = { provider: provider.id, providerName: provider.name, file, parsed, analysis, }; result.pipelines.push(pipeline); result.analysis[file] = analysis; // Generate suggestions const suggestions = this.suggester.suggest(analysis, provider.id); for (const suggestion of suggestions) { suggestion.file = file; suggestion.provider = provider.id; result.suggestions.push(suggestion); } this.emit('pipeline:analyzed', { file, analysis }); } catch (error) { this.emit('pipeline:error', { file, error: error.message }); } } } // Generate summary result.summary = this.generateSummary(result); this.emit('scan:complete', result); return result; } catch (error) { this.emit('scan:error', { error: error.message }); throw error; } } /** * Generate summary from scan results */ generateSummary(result) { const summary = { hasCI: result.providers.length > 0, providerCount: result.providers.length, pipelineCount: result.pipelines.length, providers: result.providers.map((p) => p.name), features: { hasTests: false, hasBuild: false, hasDeploy: false, hasLint: false, hasSecurityScan: false, }, complexity: 'none', suggestionCount: result.suggestions.length, highPrioritySuggestions: result.suggestions.filter((s) => s.priority === 'high').length, }; // Aggregate features for (const analysis of Object.values(result.analysis)) { if (analysis.hasTests) summary.features.hasTests = true; if (analysis.hasBuild) summary.features.hasBuild = true; if (analysis.hasDeploy) summary.features.hasDeploy = true; if (analysis.hasLint) summary.features.hasLint = true; if (analysis.hasSecurityScan) summary.features.hasSecurityScan = true; } // Determine overall complexity const complexities = Object.values(result.analysis).map((a) => a.complexity); if (complexities.includes('enterprise')) summary.complexity = 'enterprise'; else if (complexities.includes('complex')) summary.complexity = 'complex'; else if (complexities.includes('moderate')) summary.complexity = 'moderate'; else if (complexities.includes('simple')) summary.complexity = 'simple'; return summary; } /** * Get quick status of CI/CD */ async quickCheck() { const providers = await this.detector.detect(); return { hasCI: providers.length > 0, providers: providers.map((p) => ({ id: p.id, name: p.name, fileCount: p.files.length, })), }; } /** * Validate pipeline configuration */ async validate(filePath) { const fullPath = path.join(this.rootPath, filePath); if (!fs.existsSync(fullPath)) { return { valid: false, error: 'File not found' }; } // Detect provider from path let provider = null; for (const [id, config] of Object.entries(PROVIDERS)) { for (const pattern of config.configPatterns) { if (filePath.includes(pattern.replace('*', '')) || path.basename(filePath) === pattern) { provider = id; break; } } if (provider) break; } if (!provider) { return { valid: false, error: 'Unknown CI/CD provider' }; } try { const content = fs.readFileSync(fullPath, 'utf8'); const parsed = this.parser.parse(content, provider, path.basename(filePath)); // Basic validation const errors = []; const warnings = []; if (provider === 'github-actions') { if (!parsed.triggers || parsed.triggers.length === 0) { warnings.push('No triggers defined - workflow will never run automatically'); } if (!parsed.jobs || parsed.jobs.length === 0) { errors.push('No jobs defined'); } } return { valid: errors.length === 0, errors, warnings, parsed, }; } catch (error) { return { valid: false, error: error.message }; } } } // CLI interface if (require.main === module) { const args = process.argv.slice(2); const discovery = new CICDDiscovery(); discovery.on('providers:detected', ({ count }) => { console.log(`Found ${count} CI/CD provider(s)`); }); discovery.on('pipeline:analyzed', ({ file }) => { console.log(` Analyzed: ${file}`); }); if (args.includes('--quick')) { discovery.quickCheck().then((result) => { console.log(JSON.stringify(result, null, 2)); }); } else { discovery .scan() .then((result) => { console.log('\n--- Summary ---'); console.log(JSON.stringify(result.summary, null, 2)); if (args.includes('--suggestions')) { console.log('\n--- Suggestions ---'); for (const suggestion of result.suggestions) { console.log(`\n[${suggestion.priority.toUpperCase()}] ${suggestion.title}`); console.log(suggestion.description); console.log('```'); console.log(suggestion.code); console.log('```'); } } }) .catch((error) => { console.error('Error:', error.message); process.exit(1); }); } } module.exports = CICDDiscovery; module.exports.CICDDiscovery = CICDDiscovery; module.exports.ConfigParser = ConfigParser; module.exports.ProviderDetector = ProviderDetector; module.exports.PipelineAnalyzer = PipelineAnalyzer; module.exports.IntegrationSuggester = IntegrationSuggester; module.exports.PROVIDERS = PROVIDERS;