UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

1,361 lines (1,253 loc) 37.1 kB
import { useState, useEffect, useCallback, useMemo } from "react"; import { DeploymentPipeline, DeploymentExecution, EnvironmentType, DeploymentOptions, StageExecution, TestResult, PipelineStageConfig, } from "./types"; /** * CI/CD Pipeline Manager * Handles deployment pipeline orchestration and execution */ export class PipelineManager { private pipelines: Map<string, DeploymentPipeline> = new Map(); private executions: Map<string, DeploymentExecution> = new Map(); private activeExecutions: Set<string> = new Set(); constructor() { this.loadDefaultPipelines(); } /** * Load default pipeline configurations */ private loadDefaultPipelines(): void { // AgentC Starter Kit Main Pipeline const mainPipeline: DeploymentPipeline = { id: "agentc-main-pipeline", name: "AgentC Main Pipeline", description: "Main deployment pipeline for AgentC Starter Kit", sourceRepository: { provider: "github", url: "https://github.com/agentc/starter-kit", branch: "main", credentials: { type: "token", token: "github_token_placeholder", }, buildCommand: "npm run build", testCommand: "npm test", outputDirectory: "dist", webhookEnabled: true, webhookSecret: "webhook_secret", }, targetEnvironments: ["development", "testing", "staging", "production"], stages: [ { id: "build", name: "Build", type: "build", description: "Build the application", order: 1, dependsOn: [], commands: ["npm ci", "npm run lint", "npm run build"], environment: { NODE_ENV: "production", CI: "true", }, allowFailure: false, timeout: 15, parallel: false, artifacts: [ { name: "build-output", path: "dist/", type: "build", retention: 30, storage: { provider: "s3", bucket: "agentc-artifacts", path: "builds/", encryption: true, }, }, ], tests: [], }, { id: "test", name: "Test Suite", type: "test", description: "Run comprehensive test suite", order: 2, dependsOn: ["build"], commands: [ "npm run test:unit", "npm run test:integration", "npm run test:e2e", ], environment: { NODE_ENV: "test", CI: "true", }, allowFailure: false, timeout: 30, parallel: true, parallelGroup: "testing", artifacts: [ { name: "test-results", path: "test-results/", type: "test_report", retention: 30, storage: { provider: "s3", bucket: "agentc-artifacts", path: "test-results/", encryption: true, }, }, { name: "coverage-report", path: "coverage/", type: "coverage", retention: 30, storage: { provider: "s3", bucket: "agentc-artifacts", path: "coverage/", encryption: true, }, }, ], tests: [ { type: "unit", command: "npm run test:unit", reportPath: "test-results/unit.xml", coverageEnabled: true, coverageThreshold: 80, parallel: true, parallelism: 4, }, { type: "integration", command: "npm run test:integration", reportPath: "test-results/integration.xml", coverageEnabled: false, parallel: false, }, { type: "e2e", command: "npm run test:e2e", reportPath: "test-results/e2e.xml", coverageEnabled: false, parallel: false, }, ], }, { id: "security-scan", name: "Security Scan", type: "security_scan", description: "Security vulnerability scanning", order: 3, dependsOn: ["build"], commands: ["npm audit", "npm run security:scan"], environment: { NODE_ENV: "production", }, allowFailure: false, timeout: 10, parallel: true, parallelGroup: "security", artifacts: [ { name: "security-report", path: "security-report.json", type: "test_report", retention: 90, storage: { provider: "s3", bucket: "agentc-artifacts", path: "security/", encryption: true, }, }, ], tests: [ { type: "security", command: "npm run security:scan", reportPath: "security-report.json", coverageEnabled: false, parallel: false, }, ], }, { id: "staging-deploy", name: "Deploy to Staging", type: "staging_deploy", description: "Deploy to staging environment", order: 4, dependsOn: ["test", "security-scan"], commands: ["npm run deploy:staging"], environment: { NODE_ENV: "staging", ENVIRONMENT: "staging", }, allowFailure: false, timeout: 20, parallel: false, artifacts: [], tests: [ { type: "smoke", command: "npm run test:smoke:staging", reportPath: "test-results/smoke-staging.xml", coverageEnabled: false, parallel: false, }, ], }, { id: "staging-test", name: "Staging Tests", type: "integration_test", description: "Run tests against staging environment", order: 5, dependsOn: ["staging-deploy"], commands: ["npm run test:staging"], environment: { NODE_ENV: "staging", TEST_ENV: "staging", }, allowFailure: false, timeout: 25, parallel: false, artifacts: [ { name: "staging-test-results", path: "test-results/staging/", type: "test_report", retention: 30, storage: { provider: "s3", bucket: "agentc-artifacts", path: "staging-tests/", encryption: true, }, }, ], tests: [ { type: "integration", command: "npm run test:staging:integration", reportPath: "test-results/staging/integration.xml", coverageEnabled: false, parallel: false, }, { type: "performance", command: "npm run test:staging:performance", reportPath: "test-results/staging/performance.xml", coverageEnabled: false, parallel: false, }, ], }, { id: "approval", name: "Production Approval", type: "approval", description: "Manual approval for production deployment", order: 6, dependsOn: ["staging-test"], commands: [], environment: {}, allowFailure: false, timeout: 1440, // 24 hours parallel: false, artifacts: [], tests: [], }, { id: "production-deploy", name: "Deploy to Production", type: "production_deploy", description: "Deploy to production environment", order: 7, dependsOn: ["approval"], commands: ["npm run deploy:production"], environment: { NODE_ENV: "production", ENVIRONMENT: "production", }, allowFailure: false, timeout: 30, parallel: false, artifacts: [], tests: [ { type: "smoke", command: "npm run test:smoke:production", reportPath: "test-results/smoke-production.xml", coverageEnabled: false, parallel: false, }, ], }, { id: "post-deploy-test", name: "Post-Deploy Tests", type: "post_deploy_test", description: "Verify production deployment", order: 8, dependsOn: ["production-deploy"], commands: [ "npm run test:production:health", "npm run test:production:regression", ], environment: { NODE_ENV: "production", TEST_ENV: "production", }, allowFailure: true, timeout: 20, parallel: false, artifacts: [ { name: "production-test-results", path: "test-results/production/", type: "test_report", retention: 90, storage: { provider: "s3", bucket: "agentc-artifacts", path: "production-tests/", encryption: true, }, }, ], tests: [ { type: "smoke", command: "npm run test:production:health", reportPath: "test-results/production/health.xml", coverageEnabled: false, parallel: false, }, { type: "regression", command: "npm run test:production:regression", reportPath: "test-results/production/regression.xml", coverageEnabled: false, parallel: false, }, ], }, ], triggers: [ { type: "webhook", webhookEvents: ["push", "pull_request"], branches: ["main", "develop"], }, { type: "schedule", schedule: "0 2 * * *", // Daily at 2 AM timezone: "UTC", branches: ["main"], }, ], approvalRequired: true, approvers: ["tech-lead", "devops-engineer", "product-manager"], allowParallel: false, allowManualRun: true, active: true, createdBy: "system", createdAt: new Date(), updatedAt: new Date(), }; this.pipelines.set(mainPipeline.id, mainPipeline); // Feature Branch Pipeline const featurePipeline: DeploymentPipeline = { id: "agentc-feature-pipeline", name: "AgentC Feature Pipeline", description: "Pipeline for feature branch deployments", sourceRepository: { provider: "github", url: "https://github.com/agentc/starter-kit", branch: "*", credentials: { type: "token", token: "github_token_placeholder", }, buildCommand: "npm run build", testCommand: "npm test", outputDirectory: "dist", webhookEnabled: true, webhookSecret: "webhook_secret", }, targetEnvironments: ["development", "testing"], stages: [ { id: "build", name: "Build", type: "build", description: "Build the application", order: 1, dependsOn: [], commands: ["npm ci", "npm run lint", "npm run build"], environment: { NODE_ENV: "development", CI: "true", }, allowFailure: false, timeout: 15, parallel: false, artifacts: [ { name: "build-output", path: "dist/", type: "build", retention: 7, storage: { provider: "s3", bucket: "agentc-artifacts", path: "feature-builds/", encryption: true, }, }, ], tests: [], }, { id: "test", name: "Quick Tests", type: "test", description: "Run essential tests", order: 2, dependsOn: ["build"], commands: ["npm run test:unit", "npm run test:lint"], environment: { NODE_ENV: "test", CI: "true", }, allowFailure: false, timeout: 15, parallel: true, artifacts: [ { name: "test-results", path: "test-results/", type: "test_report", retention: 7, storage: { provider: "s3", bucket: "agentc-artifacts", path: "feature-test-results/", encryption: true, }, }, ], tests: [ { type: "unit", command: "npm run test:unit", reportPath: "test-results/unit.xml", coverageEnabled: true, coverageThreshold: 70, parallel: true, parallelism: 2, }, ], }, ], triggers: [ { type: "webhook", webhookEvents: ["push", "pull_request"], branches: ["feature/*", "develop"], }, ], approvalRequired: false, approvers: [], allowParallel: true, allowManualRun: true, active: true, createdBy: "system", createdAt: new Date(), updatedAt: new Date(), }; this.pipelines.set(featurePipeline.id, featurePipeline); } /** * Get pipeline by ID */ getPipeline(id: string): DeploymentPipeline | undefined { return this.pipelines.get(id); } /** * Get all pipelines */ getAllPipelines(): DeploymentPipeline[] { return Array.from(this.pipelines.values()); } /** * Create new pipeline */ createPipeline( pipeline: Omit<DeploymentPipeline, "id" | "createdAt" | "updatedAt"> ): DeploymentPipeline { const newPipeline: DeploymentPipeline = { ...pipeline, id: this.generateId(), createdAt: new Date(), updatedAt: new Date(), }; this.pipelines.set(newPipeline.id, newPipeline); return newPipeline; } /** * Update pipeline */ updatePipeline( id: string, updates: Partial<DeploymentPipeline> ): DeploymentPipeline { const pipeline = this.pipelines.get(id); if (!pipeline) { throw new Error(`Pipeline ${id} not found`); } const updatedPipeline: DeploymentPipeline = { ...pipeline, ...updates, updatedAt: new Date(), }; this.pipelines.set(id, updatedPipeline); return updatedPipeline; } /** * Delete pipeline */ deletePipeline(id: string): boolean { return this.pipelines.delete(id); } /** * Start deployment execution */ async startDeployment( pipelineId: string, environment: EnvironmentType, options: DeploymentOptions = {} ): Promise<DeploymentExecution> { const pipeline = this.pipelines.get(pipelineId); if (!pipeline) { throw new Error(`Pipeline ${pipelineId} not found`); } if (!pipeline.active) { throw new Error(`Pipeline ${pipelineId} is not active`); } if (!pipeline.targetEnvironments.includes(environment)) { throw new Error( `Environment ${environment} not supported by pipeline ${pipelineId}` ); } const execution: DeploymentExecution = { id: this.generateId(), pipelineId, environment, version: options.version || "latest", commitHash: options.commitHash || "HEAD", branch: options.branch || pipeline.sourceRepository.branch, tag: options.tag, status: "pending", startedAt: new Date(), stages: [], success: false, logs: [], triggeredBy: "user", triggerType: "manual", metadata: {}, }; this.executions.set(execution.id, execution); this.activeExecutions.add(execution.id); // Start execution asynchronously this.executeDeployment(execution.id).catch((error) => { console.error(`Deployment execution failed: ${error.message}`); }); return execution; } /** * Get deployment execution by ID */ getDeployment(id: string): DeploymentExecution | undefined { return this.executions.get(id); } /** * Get all deployment executions */ getAllDeployments(): DeploymentExecution[] { return Array.from(this.executions.values()); } /** * Cancel deployment execution */ async cancelDeployment(id: string): Promise<boolean> { const execution = this.executions.get(id); if (!execution) { return false; } if ( execution.status === "success" || execution.status === "failed" || execution.status === "cancelled" ) { return false; } execution.status = "cancelled"; execution.completedAt = new Date(); execution.duration = Math.floor( (execution.completedAt.getTime() - execution.startedAt.getTime()) / 1000 ); this.activeExecutions.delete(id); return true; } /** * Rollback deployment */ async rollbackDeployment( id: string, targetVersion?: string ): Promise<DeploymentExecution> { const execution = this.executions.get(id); if (!execution) { throw new Error(`Deployment ${id} not found`); } const pipeline = this.pipelines.get(execution.pipelineId); if (!pipeline) { throw new Error(`Pipeline ${execution.pipelineId} not found`); } // Create rollback execution const rollbackExecution: DeploymentExecution = { id: this.generateId(), pipelineId: execution.pipelineId, environment: execution.environment, version: targetVersion || "previous", commitHash: "rollback", branch: execution.branch, status: "pending", startedAt: new Date(), stages: [], success: false, logs: [ { timestamp: new Date(), level: "info", message: `Rolling back deployment ${id}`, component: "rollback", }, ], rollbackTo: id, rollbackReason: "Manual rollback", triggeredBy: "user", triggerType: "manual", metadata: { rollback: true, originalDeployment: id, }, }; this.executions.set(rollbackExecution.id, rollbackExecution); this.activeExecutions.add(rollbackExecution.id); // Execute rollback this.executeRollback(rollbackExecution.id).catch((error) => { console.error(`Rollback execution failed: ${error.message}`); }); return rollbackExecution; } /** * Execute deployment */ private async executeDeployment(executionId: string): Promise<void> { const execution = this.executions.get(executionId); if (!execution) { throw new Error(`Execution ${executionId} not found`); } const pipeline = this.pipelines.get(execution.pipelineId); if (!pipeline) { throw new Error(`Pipeline ${execution.pipelineId} not found`); } try { execution.status = "in_progress"; execution.logs.push({ timestamp: new Date(), level: "info", message: `Starting deployment for ${execution.environment}`, component: "deployment", }); // Sort stages by order const sortedStages = [...pipeline.stages].sort( (a, b) => a.order - b.order ); for (const stageConfig of sortedStages) { if (execution.status === "cancelled") { break; } // Check dependencies const dependenciesMet = this.checkStageDependencies( stageConfig, execution.stages ); if (!dependenciesMet) { execution.logs.push({ timestamp: new Date(), level: "warn", message: `Skipping stage ${stageConfig.name} - dependencies not met`, stage: stageConfig.id, }); continue; } await this.executeStage(execution, stageConfig); // Check if stage failed and not allowed to fail const stageExecution = execution.stages.find( (s) => s.stageId === stageConfig.id ); if ( stageExecution && !stageExecution.success && !stageConfig.allowFailure ) { execution.status = "failed"; execution.success = false; break; } } // Complete execution if (execution.status !== "failed" && execution.status !== "cancelled") { execution.status = "success"; execution.success = true; } execution.completedAt = new Date(); execution.duration = Math.floor( (execution.completedAt.getTime() - execution.startedAt.getTime()) / 1000 ); this.activeExecutions.delete(executionId); execution.logs.push({ timestamp: new Date(), level: "info", message: `Deployment ${ execution.success ? "completed successfully" : "failed" }`, component: "deployment", }); } catch (error) { execution.status = "failed"; execution.success = false; execution.completedAt = new Date(); execution.duration = Math.floor( (execution.completedAt.getTime() - execution.startedAt.getTime()) / 1000 ); execution.errorMessage = error instanceof Error ? error.message : "Unknown error"; this.activeExecutions.delete(executionId); execution.logs.push({ timestamp: new Date(), level: "error", message: `Deployment failed: ${execution.errorMessage}`, component: "deployment", }); } } /** * Execute single stage */ private async executeStage( execution: DeploymentExecution, stageConfig: PipelineStageConfig ): Promise<void> { const stageExecution: StageExecution = { stageId: stageConfig.id, name: stageConfig.name, status: "in_progress", startedAt: new Date(), logs: [], artifacts: [], success: false, testResults: [], }; execution.stages.push(stageExecution); try { stageExecution.logs.push({ timestamp: new Date(), level: "info", message: `Starting stage: ${stageConfig.name}`, stage: stageConfig.id, }); // Execute commands for (const command of stageConfig.commands) { stageExecution.logs.push({ timestamp: new Date(), level: "info", message: `Executing: ${command}`, stage: stageConfig.id, }); // Simulate command execution await this.simulateCommandExecution(command, stageExecution); } // Execute tests for (const testConfig of stageConfig.tests) { const testResult = await this.executeTest(testConfig, stageExecution); stageExecution.testResults.push(testResult); } // Process artifacts for (const artifactConfig of stageConfig.artifacts) { const artifact = await this.processArtifact( artifactConfig, stageExecution ); stageExecution.artifacts.push(artifact); } stageExecution.status = "success"; stageExecution.success = true; stageExecution.completedAt = new Date(); stageExecution.duration = Math.floor( (stageExecution.completedAt.getTime() - stageExecution.startedAt.getTime()) / 1000 ); stageExecution.logs.push({ timestamp: new Date(), level: "info", message: `Stage completed successfully: ${stageConfig.name}`, stage: stageConfig.id, }); } catch (error) { stageExecution.status = "failed"; stageExecution.success = false; stageExecution.completedAt = new Date(); stageExecution.duration = Math.floor( (stageExecution.completedAt.getTime() - stageExecution.startedAt.getTime()) / 1000 ); stageExecution.errorMessage = error instanceof Error ? error.message : "Unknown error"; stageExecution.logs.push({ timestamp: new Date(), level: "error", message: `Stage failed: ${stageExecution.errorMessage}`, stage: stageConfig.id, }); throw error; } } /** * Check stage dependencies */ private checkStageDependencies( stageConfig: PipelineStageConfig, completedStages: StageExecution[] ): boolean { if (!stageConfig.dependsOn || stageConfig.dependsOn.length === 0) { return true; } return stageConfig.dependsOn.every((depId: string) => { const depStage = completedStages.find((s) => s.stageId === depId); return depStage && depStage.success; }); } /** * Simulate command execution */ private async simulateCommandExecution( command: string, stageExecution: StageExecution ): Promise<void> { // Simulate execution time const executionTime = Math.random() * 5000 + 1000; // 1-6 seconds await new Promise((resolve) => setTimeout(resolve, executionTime)); // Simulate potential failure if (Math.random() < 0.05) { // 5% failure rate throw new Error(`Command failed: ${command}`); } stageExecution.logs.push({ timestamp: new Date(), level: "info", message: `Command completed: ${command}`, stage: stageExecution.stageId, }); } /** * Execute test */ private async executeTest( testConfig: TestConfig, stageExecution: StageExecution ): Promise<TestResult> { // Simulate test execution const executionTime = Math.random() * 10000 + 2000; // 2-12 seconds await new Promise((resolve) => setTimeout(resolve, executionTime)); const testCount = Math.floor(Math.random() * 50) + 10; // 10-60 tests const passedCount = Math.floor(testCount * (0.8 + Math.random() * 0.2)); // 80-100% pass rate const failedCount = testCount - passedCount; const testResult: TestResult = { name: testConfig.type, type: testConfig.type, status: failedCount === 0 ? "passed" : "failed", duration: executionTime, testCount, passedCount, failedCount, skippedCount: 0, reportUrl: `https://test-reports.agentc.dev/${stageExecution.stageId}/${testConfig.type}`, }; if (testConfig.coverageEnabled) { testResult.coverage = { percentage: 75 + Math.random() * 20, // 75-95% lines: { total: 1000, covered: 850, percentage: 85, }, functions: { total: 200, covered: 180, percentage: 90, }, branches: { total: 400, covered: 320, percentage: 80, }, statements: { total: 1200, covered: 1020, percentage: 85, }, }; } stageExecution.logs.push({ timestamp: new Date(), level: testResult.status === "passed" ? "info" : "error", message: `Test ${testResult.status}: ${testConfig.type} (${passedCount}/${testCount} passed)`, stage: stageExecution.stageId, }); return testResult; } /** * Process artifact */ private async processArtifact( artifactConfig: ArtifactConfig, stageExecution: StageExecution ): Promise<ExecutionArtifact> { // Simulate artifact processing await new Promise((resolve) => setTimeout(resolve, 2000)); const artifact: ExecutionArtifact = { name: artifactConfig.name, url: `https://artifacts.agentc.dev/${stageExecution.stageId}/${artifactConfig.name}`, size: Math.floor(Math.random() * 100000000) + 1000000, // 1-100MB type: artifactConfig.type, hash: `sha256:${Math.random().toString(36).substring(2, 15)}`, }; stageExecution.logs.push({ timestamp: new Date(), level: "info", message: `Artifact stored: ${artifact.name} (${Math.floor( artifact.size / 1024 / 1024 )}MB)`, stage: stageExecution.stageId, }); return artifact; } /** * Execute rollback */ private async executeRollback(executionId: string): Promise<void> { const execution = this.executions.get(executionId); if (!execution) { throw new Error(`Rollback execution ${executionId} not found`); } try { execution.status = "in_progress"; // Simulate rollback execution await new Promise((resolve) => setTimeout(resolve, 10000)); // 10 seconds execution.status = "success"; execution.success = true; execution.completedAt = new Date(); execution.duration = Math.floor( (execution.completedAt.getTime() - execution.startedAt.getTime()) / 1000 ); this.activeExecutions.delete(executionId); execution.logs.push({ timestamp: new Date(), level: "info", message: "Rollback completed successfully", component: "rollback", }); } catch (error) { execution.status = "failed"; execution.success = false; execution.completedAt = new Date(); execution.duration = Math.floor( (execution.completedAt.getTime() - execution.startedAt.getTime()) / 1000 ); execution.errorMessage = error instanceof Error ? error.message : "Rollback failed"; this.activeExecutions.delete(executionId); execution.logs.push({ timestamp: new Date(), level: "error", message: `Rollback failed: ${execution.errorMessage}`, component: "rollback", }); } } /** * Generate unique ID */ private generateId(): string { return `deploy_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Get active deployments */ getActiveDeployments(): DeploymentExecution[] { return Array.from(this.activeExecutions) .map((id) => this.executions.get(id)) .filter( (execution): execution is DeploymentExecution => execution !== undefined ); } /** * Get deployment history for environment */ getDeploymentHistory( environment: EnvironmentType, limit = 50 ): DeploymentExecution[] { return Array.from(this.executions.values()) .filter((execution) => execution.environment === environment) .sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime()) .slice(0, limit); } /** * Get deployment statistics */ getDeploymentStats(environment?: EnvironmentType): { total: number; successful: number; failed: number; successRate: number; averageDuration: number; } { const executions = environment ? Array.from(this.executions.values()).filter( (e) => e.environment === environment ) : Array.from(this.executions.values()); const total = executions.length; const successful = executions.filter((e) => e.success).length; const failed = executions.filter( (e) => !e.success && e.status !== "cancelled" ).length; const completedExecutions = executions.filter( (e) => e.duration !== undefined ); const averageDuration = completedExecutions.length > 0 ? completedExecutions.reduce((sum, e) => sum + (e.duration || 0), 0) / completedExecutions.length : 0; return { total, successful, failed, successRate: total > 0 ? (successful / total) * 100 : 0, averageDuration, }; } } // Export singleton instance export const pipelineManager = new PipelineManager(); /** * React Hook for Pipeline Management */ export const usePipelineManager = () => { const [pipelines, setPipelines] = useState<DeploymentPipeline[]>([]); const [deployments, setDeployments] = useState<DeploymentExecution[]>([]); const [activeDeployments, setActiveDeployments] = useState< DeploymentExecution[] >([]); const [isLoading, setIsLoading] = useState(false); const loadPipelines = useCallback(async () => { setIsLoading(true); try { const allPipelines = pipelineManager.getAllPipelines(); setPipelines(allPipelines); } catch (error) { console.error("Failed to load pipelines:", error); } finally { setIsLoading(false); } }, []); const loadDeployments = useCallback(async () => { try { const allDeployments = pipelineManager.getAllDeployments(); setDeployments(allDeployments); const active = pipelineManager.getActiveDeployments(); setActiveDeployments(active); } catch (error) { console.error("Failed to load deployments:", error); } }, []); const startDeployment = useCallback( async ( pipelineId: string, environment: EnvironmentType, options?: DeploymentOptions ) => { try { const execution = await pipelineManager.startDeployment( pipelineId, environment, options ); setDeployments((prev) => [execution, ...prev]); setActiveDeployments((prev) => [execution, ...prev]); return execution; } catch (error) { console.error("Failed to start deployment:", error); throw error; } }, [] ); const cancelDeployment = useCallback( async (deploymentId: string) => { try { const success = await pipelineManager.cancelDeployment(deploymentId); if (success) { await loadDeployments(); } return success; } catch (error) { console.error("Failed to cancel deployment:", error); throw error; } }, [loadDeployments] ); const rollbackDeployment = useCallback( async (deploymentId: string, targetVersion?: string) => { try { const execution = await pipelineManager.rollbackDeployment( deploymentId, targetVersion ); setDeployments((prev) => [execution, ...prev]); setActiveDeployments((prev) => [execution, ...prev]); return execution; } catch (error) { console.error("Failed to rollback deployment:", error); throw error; } }, [] ); const getDeploymentStats = useCallback((environment?: EnvironmentType) => { return pipelineManager.getDeploymentStats(environment); }, []); useEffect(() => { loadPipelines(); loadDeployments(); }, [loadPipelines, loadDeployments]); // Poll for updates on active deployments useEffect(() => { if (activeDeployments.length === 0) return; const interval = setInterval(() => { loadDeployments(); }, 5000); // Poll every 5 seconds return () => clearInterval(interval); }, [activeDeployments.length, loadDeployments]); return useMemo( () => ({ pipelines, deployments, activeDeployments, isLoading, startDeployment, cancelDeployment, rollbackDeployment, getDeploymentStats, loadPipelines, loadDeployments, }), [ pipelines, deployments, activeDeployments, isLoading, startDeployment, cancelDeployment, rollbackDeployment, getDeploymentStats, loadPipelines, loadDeployments, ] ); }; // Helper interface for test config (used in this module) interface TestConfig { type: string; command: string; reportPath?: string; coverageEnabled: boolean; coverageThreshold?: number; parallel: boolean; parallelism?: number; } // Helper interface for artifact config (used in this module) interface ArtifactConfig { name: string; path: string; type: string; retention: number; storage: { provider: string; bucket?: string; path?: string; encryption: boolean; }; } // Helper interface for execution artifact (used in this module) interface ExecutionArtifact { name: string; url: string; size: number; type: string; hash: string; } export interface PipelineStats { total: number; successful: number; failed: number; successRate: number; averageDuration: number; } export interface DeploymentMetrics { environment: EnvironmentType; deploymentsCount: number; successRate: number; averageDuration: number; lastDeployment?: Date; } export interface PipelineManagerOptions { maxConcurrentDeployments?: number; defaultTimeout?: number; retryAttempts?: number; }