UNPKG

task-master-neo-sdlc

Version:

Enhanced task management system with Neo SDLC agents and MCP tools for comprehensive, AI-driven software development lifecycle management.

608 lines (526 loc) 13.9 kB
/** * CI/CD Integration for TDD Framework * * Provides integration with CI/CD systems for automated testing. */ import { log } from '../../../utils/logging.js'; import fs from 'fs'; import path from 'path'; import { execSync } from 'child_process'; /** * Supported CI/CD systems */ export const CI_CD_SYSTEMS = { GITHUB_ACTIONS: { name: 'GitHub Actions', configPath: '.github/workflows', configFile: 'test.yml', detectFn: (projectRoot) => fs.existsSync(path.join(projectRoot, '.github')), templateFn: createGitHubActionsConfig }, GITLAB_CI: { name: 'GitLab CI', configPath: '', configFile: '.gitlab-ci.yml', detectFn: (projectRoot) => fs.existsSync(path.join(projectRoot, '.gitlab-ci.yml')), templateFn: createGitLabCIConfig }, JENKINS: { name: 'Jenkins', configPath: '', configFile: 'Jenkinsfile', detectFn: (projectRoot) => fs.existsSync(path.join(projectRoot, 'Jenkinsfile')), templateFn: createJenkinsConfig }, CIRCLE_CI: { name: 'CircleCI', configPath: '.circleci', configFile: 'config.yml', detectFn: (projectRoot) => fs.existsSync(path.join(projectRoot, '.circleci')), templateFn: createCircleCIConfig }, TRAVIS_CI: { name: 'Travis CI', configPath: '', configFile: '.travis.yml', detectFn: (projectRoot) => fs.existsSync(path.join(projectRoot, '.travis.yml')), templateFn: createTravisCIConfig } }; /** * Detect CI/CD system in a project * @param {string} projectRoot - Project root directory * @returns {string} Detected CI/CD system name */ export function detectCICDSystem(projectRoot) { for (const [key, system] of Object.entries(CI_CD_SYSTEMS)) { if (system.detectFn(projectRoot)) { return system.name; } } return null; } /** * Create CI/CD configuration for a project * @param {Object} options - Configuration options * @param {string} options.ciSystem - CI/CD system name * @param {string} options.projectRoot - Project root directory * @param {Array<string>} options.testFrameworks - Test frameworks to configure * @param {Object} options.testConfig - Test configuration * @returns {Promise<Object>} Created configuration */ export async function createCICDConfig(options) { const { ciSystem, projectRoot, testFrameworks, testConfig } = options; log.info(`Creating CI/CD configuration for ${ciSystem}`); try { const system = Object.values(CI_CD_SYSTEMS).find(s => s.name === ciSystem); if (!system) { throw new Error(`Unknown CI/CD system: ${ciSystem}`); } // Create config directory if it doesn't exist if (system.configPath) { const configDir = path.join(projectRoot, system.configPath); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } } // Generate config content const configContent = system.templateFn(testFrameworks, testConfig); // Write config file const configPath = path.join(projectRoot, system.configPath, system.configFile); fs.writeFileSync(configPath, configContent, 'utf8'); log.info(`CI/CD configuration created at ${configPath}`); return { ciSystem, configPath, configContent }; } catch (error) { log.error(`Error creating CI/CD configuration: ${error.message}`); throw error; } } /** * Create GitHub Actions configuration * @param {Array<string>} testFrameworks - Test frameworks to configure * @param {Object} testConfig - Test configuration * @returns {string} GitHub Actions configuration */ function createGitHubActionsConfig(testFrameworks, testConfig) { const hasJest = testFrameworks.includes('jest'); const hasCypress = testFrameworks.includes('cypress'); const hasPlaywright = testFrameworks.includes('playwright'); let config = `name: Test on: push: branches: [ main, master ] pull_request: branches: [ main, master ] jobs: `; // Add unit tests job if (hasJest) { config += ` unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Use Node.js uses: actions/setup-node@v3 with: node-version: '18.x' cache: 'npm' - name: Install dependencies run: npm ci - name: Run unit tests run: npm test `; // Add coverage reporting if (testConfig?.coverage) { config += ` - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: token: \${{ secrets.CODECOV_TOKEN }} `; } } // Add E2E tests job if (hasCypress || hasPlaywright) { config += ` e2e-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Use Node.js uses: actions/setup-node@v3 with: node-version: '18.x' cache: 'npm' - name: Install dependencies run: npm ci `; if (hasCypress) { config += ` - name: Cypress run uses: cypress-io/github-action@v5 with: build: npm run build start: npm start `; } if (hasPlaywright) { config += ` - name: Install Playwright browsers run: npx playwright install --with-deps - name: Run Playwright tests run: npx playwright test - name: Upload Playwright report if: always() uses: actions/upload-artifact@v3 with: name: playwright-report path: playwright-report/ retention-days: 30 `; } } return config; } /** * Create GitLab CI configuration * @param {Array<string>} testFrameworks - Test frameworks to configure * @param {Object} testConfig - Test configuration * @returns {string} GitLab CI configuration */ function createGitLabCIConfig(testFrameworks, testConfig) { const hasJest = testFrameworks.includes('jest'); const hasCypress = testFrameworks.includes('cypress'); const hasPlaywright = testFrameworks.includes('playwright'); let config = `image: node:18 stages: - test cache: paths: - node_modules/ `; // Add unit tests job if (hasJest) { config += `unit-tests: stage: test script: - npm ci - npm test `; // Add coverage reporting if (testConfig?.coverage) { config += ` artifacts: paths: - coverage/ expire_in: 1 week `; } } // Add E2E tests job if (hasCypress || hasPlaywright) { config += `e2e-tests: stage: test script: - npm ci `; if (hasCypress) { config += ` - npm run build - npm start & - npx cypress run `; } if (hasPlaywright) { config += ` - npx playwright install --with-deps - npx playwright test artifacts: paths: - playwright-report/ expire_in: 1 week `; } } return config; } /** * Create Jenkins configuration * @param {Array<string>} testFrameworks - Test frameworks to configure * @param {Object} testConfig - Test configuration * @returns {string} Jenkins configuration */ function createJenkinsConfig(testFrameworks, testConfig) { const hasJest = testFrameworks.includes('jest'); const hasCypress = testFrameworks.includes('cypress'); const hasPlaywright = testFrameworks.includes('playwright'); let config = `pipeline { agent { docker { image 'node:18' } } stages { `; // Add unit tests stage if (hasJest) { config += ` stage('Unit Tests') { steps { sh 'npm ci' sh 'npm test' } `; // Add coverage reporting if (testConfig?.coverage) { config += ` post { always { junit 'coverage/junit.xml' publishHTML target: [ allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'coverage', reportFiles: 'index.html', reportName: 'Coverage Report' ] } } `; } config += ` } `; } // Add E2E tests stage if (hasCypress || hasPlaywright) { config += ` stage('E2E Tests') { steps { sh 'npm ci' `; if (hasCypress) { config += ` sh 'npm run build' sh 'npm start &' sh 'npx cypress run' `; } if (hasPlaywright) { config += ` sh 'npx playwright install --with-deps' sh 'npx playwright test' `; } config += ` } post { always { `; if (hasPlaywright) { config += ` archiveArtifacts artifacts: 'playwright-report/**', fingerprint: true `; } config += ` } } } `; } config += ` } } `; return config; } /** * Create CircleCI configuration * @param {Array<string>} testFrameworks - Test frameworks to configure * @param {Object} testConfig - Test configuration * @returns {string} CircleCI configuration */ function createCircleCIConfig(testFrameworks, testConfig) { const hasJest = testFrameworks.includes('jest'); const hasCypress = testFrameworks.includes('cypress'); const hasPlaywright = testFrameworks.includes('playwright'); let config = `version: 2.1 orbs: node: circleci/node@5.1.0 `; if (hasCypress) { config += ` cypress: cypress-io/cypress@3 `; } config += ` jobs: `; // Add unit tests job if (hasJest) { config += ` unit-tests: docker: - image: cimg/node:18.16 steps: - checkout - node/install-packages - run: name: Run unit tests command: npm test `; // Add coverage reporting if (testConfig?.coverage) { config += ` - store_artifacts: path: coverage `; } } // Add E2E tests job if (hasCypress || hasPlaywright) { config += ` e2e-tests: docker: - image: cimg/node:18.16-browsers steps: - checkout - node/install-packages `; if (hasCypress) { config += ` - run: name: Start application command: npm start background: true - cypress/run `; } if (hasPlaywright) { config += ` - run: name: Install Playwright browsers command: npx playwright install --with-deps - run: name: Run Playwright tests command: npx playwright test - store_artifacts: path: playwright-report `; } } // Add workflow config += ` workflows: test: jobs: `; if (hasJest) { config += ` - unit-tests `; } if (hasCypress || hasPlaywright) { config += ` - e2e-tests `; } return config; } /** * Create Travis CI configuration * @param {Array<string>} testFrameworks - Test frameworks to configure * @param {Object} testConfig - Test configuration * @returns {string} Travis CI configuration */ function createTravisCIConfig(testFrameworks, testConfig) { const hasJest = testFrameworks.includes('jest'); const hasCypress = testFrameworks.includes('cypress'); const hasPlaywright = testFrameworks.includes('playwright'); let config = `language: node_js node_js: - 18 cache: directories: - node_modules `; // Add before install config += `before_install: - npm ci `; // Add scripts config += `script: `; if (hasJest) { config += ` - npm test `; } if (hasCypress) { config += ` - npm run build - npm start & - npx cypress run `; } if (hasPlaywright) { config += ` - npx playwright install --with-deps - npx playwright test `; } // Add after success if (testConfig?.coverage) { config += ` after_success: - npx codecov `; } return config; } /** * Trigger CI/CD pipeline * @param {Object} options - Trigger options * @param {string} options.ciSystem - CI/CD system name * @param {string} options.projectRoot - Project root directory * @param {string} options.branch - Branch to trigger * @returns {Promise<Object>} Trigger result */ export async function triggerCICDPipeline(options) { const { ciSystem, projectRoot, branch = 'main' } = options; log.info(`Triggering CI/CD pipeline for ${ciSystem} on branch ${branch}`); try { // Check if git is initialized if (!fs.existsSync(path.join(projectRoot, '.git'))) { throw new Error('Git repository not initialized'); } // Commit changes execSync('git add .', { cwd: projectRoot }); execSync('git commit -m "chore: update tests [ci skip]"', { cwd: projectRoot }); // Push changes execSync(`git push origin ${branch}`, { cwd: projectRoot }); log.info(`CI/CD pipeline triggered for ${ciSystem} on branch ${branch}`); return { ciSystem, branch, triggered: true }; } catch (error) { log.error(`Error triggering CI/CD pipeline: ${error.message}`); throw error; } } /** * Get CI/CD pipeline status * @param {Object} options - Status options * @param {string} options.ciSystem - CI/CD system name * @param {string} options.projectRoot - Project root directory * @param {string} options.branch - Branch to check * @returns {Promise<Object>} Pipeline status */ export async function getCICDPipelineStatus(options) { const { ciSystem, projectRoot, branch = 'main' } = options; log.info(`Getting CI/CD pipeline status for ${ciSystem} on branch ${branch}`); try { // This would typically use the CI/CD system's API // For now, we'll return a mock status return { ciSystem, branch, status: 'running', url: 'https://example.com/pipeline/123', jobs: [ { name: 'unit-tests', status: 'success', duration: 45 }, { name: 'e2e-tests', status: 'running', duration: null } ] }; } catch (error) { log.error(`Error getting CI/CD pipeline status: ${error.message}`); throw error; } }