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
JavaScript
/**
* 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;
}
}