@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
1,361 lines (1,253 loc) • 37.1 kB
text/typescript
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;
}