Tutorial: Creating Custom Workflows

Build your own orchestrated workflows that coordinate multiple agents for complex tasks.

What You'll Build#

  • Custom "deployment" workflow
  • Multi-phase orchestration
  • Agent coordination
  • Quality gates and checkpoints
  • Automated rollback handling

Prerequisites#

  • Bootspring Pro or higher
  • Understanding of agents and workflows
  • Project with existing deployment setup

Understanding Custom Workflows#

Custom workflows allow you to:

  • Orchestrate complex multi-step processes
  • Coordinate multiple agents
  • Add project-specific automation
  • Enforce quality gates at each phase

Step 1: Define the Workflow#

Create a workflow definition:

1// bootspring.config.js 2module.exports = { 3 project: { 4 name: 'my-saas', 5 type: 'web', 6 framework: 'nextjs', 7 }, 8 9 workflows: { 10 custom: [ 11 { 12 name: 'deployment', 13 displayName: 'Production Deployment', 14 description: 'Safe deployment with quality gates and rollback', 15 16 phases: [ 17 { 18 name: 'pre-deploy', 19 displayName: 'Pre-Deployment Checks', 20 description: 'Verify readiness for deployment', 21 agents: ['testing-expert', 'security-expert'], 22 required: true, 23 }, 24 { 25 name: 'staging', 26 displayName: 'Staging Deployment', 27 description: 'Deploy to staging environment', 28 agents: ['devops-expert'], 29 required: true, 30 }, 31 { 32 name: 'validation', 33 displayName: 'Staging Validation', 34 description: 'Validate staging deployment', 35 agents: ['testing-expert', 'performance-expert'], 36 required: true, 37 }, 38 { 39 name: 'production', 40 displayName: 'Production Deployment', 41 description: 'Deploy to production', 42 agents: ['devops-expert'], 43 required: true, 44 requiresApproval: true, 45 }, 46 { 47 name: 'monitoring', 48 displayName: 'Post-Deploy Monitoring', 49 description: 'Monitor for issues', 50 agents: ['devops-expert'], 51 required: true, 52 }, 53 ], 54 55 rollback: { 56 enabled: true, 57 automatic: false, 58 triggers: ['error_rate_spike', 'health_check_failure'], 59 }, 60 }, 61 ], 62 }, 63};

Step 2: Create Workflow Definition File#

Create a detailed workflow specification:

1// workflows/deployment.ts 2export interface WorkflowPhase { 3 name: string; 4 displayName: string; 5 description: string; 6 agents: string[]; 7 required: boolean; 8 requiresApproval?: boolean; 9 timeout?: number; 10 retries?: number; 11} 12 13export interface WorkflowDefinition { 14 name: string; 15 displayName: string; 16 description: string; 17 phases: WorkflowPhase[]; 18 hooks?: WorkflowHooks; 19 rollback?: RollbackConfig; 20} 21 22export const deploymentWorkflow: WorkflowDefinition = { 23 name: 'deployment', 24 displayName: 'Production Deployment', 25 description: 'Orchestrated deployment with quality gates and rollback capability', 26 27 phases: [ 28 { 29 name: 'pre-deploy', 30 displayName: 'Pre-Deployment Checks', 31 description: 'Verify codebase is ready for deployment', 32 agents: ['testing-expert', 'security-expert'], 33 required: true, 34 timeout: 600000, // 10 minutes 35 36 steps: [ 37 { 38 name: 'run_tests', 39 description: 'Execute full test suite', 40 command: 'npm run test:all', 41 failOnError: true, 42 }, 43 { 44 name: 'security_scan', 45 description: 'Run security vulnerability scan', 46 command: 'npm audit --audit-level=high', 47 failOnError: true, 48 }, 49 { 50 name: 'lint_check', 51 description: 'Verify code quality', 52 command: 'npm run lint', 53 failOnError: false, 54 }, 55 { 56 name: 'type_check', 57 description: 'TypeScript type verification', 58 command: 'npx tsc --noEmit', 59 failOnError: true, 60 }, 61 { 62 name: 'build_test', 63 description: 'Verify build succeeds', 64 command: 'npm run build', 65 failOnError: true, 66 }, 67 ], 68 }, 69 70 { 71 name: 'staging', 72 displayName: 'Staging Deployment', 73 description: 'Deploy to staging environment', 74 agents: ['devops-expert'], 75 required: true, 76 timeout: 300000, // 5 minutes 77 78 steps: [ 79 { 80 name: 'deploy_staging', 81 description: 'Deploy to Vercel staging', 82 command: 'vercel --env staging', 83 captureOutput: true, 84 }, 85 { 86 name: 'run_migrations', 87 description: 'Run database migrations', 88 command: 'npx prisma migrate deploy', 89 env: 'staging', 90 failOnError: true, 91 }, 92 { 93 name: 'health_check', 94 description: 'Verify staging is healthy', 95 action: 'http_health_check', 96 url: '${STAGING_URL}/api/health', 97 retries: 3, 98 retryDelay: 5000, 99 }, 100 ], 101 }, 102 103 { 104 name: 'validation', 105 displayName: 'Staging Validation', 106 description: 'Validate the staging deployment', 107 agents: ['testing-expert', 'performance-expert'], 108 required: true, 109 timeout: 900000, // 15 minutes 110 111 steps: [ 112 { 113 name: 'e2e_tests', 114 description: 'Run E2E tests against staging', 115 command: 'npm run test:e2e', 116 env: { 117 BASE_URL: '${STAGING_URL}', 118 }, 119 failOnError: true, 120 }, 121 { 122 name: 'smoke_tests', 123 description: 'Run critical path smoke tests', 124 command: 'npm run test:smoke', 125 env: { 126 BASE_URL: '${STAGING_URL}', 127 }, 128 failOnError: true, 129 }, 130 { 131 name: 'performance_check', 132 description: 'Run performance benchmarks', 133 command: 'npm run test:perf', 134 failOnError: false, 135 warnThreshold: { 136 lcp: 2500, 137 fid: 100, 138 cls: 0.1, 139 }, 140 }, 141 ], 142 }, 143 144 { 145 name: 'production', 146 displayName: 'Production Deployment', 147 description: 'Deploy to production environment', 148 agents: ['devops-expert'], 149 required: true, 150 requiresApproval: true, 151 timeout: 300000, // 5 minutes 152 153 steps: [ 154 { 155 name: 'create_backup', 156 description: 'Create database backup', 157 command: 'npm run db:backup', 158 captureOutput: true, 159 }, 160 { 161 name: 'deploy_production', 162 description: 'Deploy to Vercel production', 163 command: 'vercel --prod', 164 captureOutput: true, 165 }, 166 { 167 name: 'run_migrations', 168 description: 'Run production migrations', 169 command: 'npx prisma migrate deploy', 170 env: 'production', 171 failOnError: true, 172 }, 173 { 174 name: 'health_check', 175 description: 'Verify production is healthy', 176 action: 'http_health_check', 177 url: '${PRODUCTION_URL}/api/health', 178 retries: 5, 179 retryDelay: 5000, 180 }, 181 ], 182 }, 183 184 { 185 name: 'monitoring', 186 displayName: 'Post-Deploy Monitoring', 187 description: 'Monitor production for issues', 188 agents: ['devops-expert'], 189 required: true, 190 timeout: 1800000, // 30 minutes 191 192 steps: [ 193 { 194 name: 'monitor_errors', 195 description: 'Watch error rates for 5 minutes', 196 action: 'monitor_metrics', 197 duration: 300000, 198 thresholds: { 199 errorRate: 0.01, // 1% 200 p99Latency: 5000, // 5s 201 }, 202 triggerRollback: true, 203 }, 204 { 205 name: 'notify_team', 206 description: 'Send deployment notification', 207 action: 'send_notification', 208 channels: ['slack', 'email'], 209 message: 'Production deployment completed successfully', 210 }, 211 ], 212 }, 213 ], 214 215 hooks: { 216 onStart: async (context) => { 217 console.log(`Starting deployment workflow for ${context.project}`); 218 await notifySlack(`🚀 Starting deployment for ${context.project}`); 219 }, 220 221 onPhaseStart: async (phase, context) => { 222 console.log(`Starting phase: ${phase.displayName}`); 223 await updateDeploymentStatus(context.deploymentId, phase.name, 'running'); 224 }, 225 226 onPhaseComplete: async (phase, result, context) => { 227 console.log(`Phase ${phase.displayName} completed: ${result.status}`); 228 await updateDeploymentStatus(context.deploymentId, phase.name, result.status); 229 }, 230 231 onApprovalRequired: async (phase, context) => { 232 await notifySlack(`⏸️ Approval required for ${phase.displayName}`); 233 return waitForApproval(context.deploymentId, phase.name); 234 }, 235 236 onComplete: async (result, context) => { 237 await notifySlack(`✅ Deployment completed: ${result.status}`); 238 await recordDeployment(context); 239 }, 240 241 onError: async (error, phase, context) => { 242 await notifySlack(`❌ Deployment failed in ${phase.displayName}: ${error.message}`); 243 await recordDeploymentFailure(context, error); 244 }, 245 }, 246 247 rollback: { 248 enabled: true, 249 automatic: false, 250 triggers: [ 251 { 252 type: 'error_rate_spike', 253 threshold: 0.05, // 5% error rate 254 duration: 60000, // Over 1 minute 255 }, 256 { 257 type: 'health_check_failure', 258 consecutiveFailures: 3, 259 }, 260 { 261 type: 'latency_spike', 262 p99Threshold: 10000, // 10s 263 duration: 120000, // Over 2 minutes 264 }, 265 ], 266 steps: [ 267 { 268 name: 'revert_deployment', 269 description: 'Revert to previous deployment', 270 command: 'vercel rollback', 271 }, 272 { 273 name: 'restore_database', 274 description: 'Restore database from backup if needed', 275 condition: 'migrations_applied', 276 command: 'npm run db:restore -- --backup=${BACKUP_ID}', 277 }, 278 { 279 name: 'notify_rollback', 280 description: 'Notify team of rollback', 281 action: 'send_notification', 282 channels: ['slack', 'pagerduty'], 283 message: '⚠️ Production rollback initiated', 284 }, 285 ], 286 }, 287};

Step 3: Create Workflow Executor#

Build the workflow execution engine:

1// lib/workflow/executor.ts 2import { WorkflowDefinition, WorkflowPhase } from './types'; 3import { invokeAgent } from '@/lib/bootspring'; 4 5interface WorkflowContext { 6 workflowId: string; 7 projectName: string; 8 startedAt: Date; 9 environment: Record<string, string>; 10 results: Map<string, PhaseResult>; 11} 12 13interface PhaseResult { 14 status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped'; 15 startedAt?: Date; 16 completedAt?: Date; 17 output?: any; 18 error?: Error; 19} 20 21export class WorkflowExecutor { 22 private workflow: WorkflowDefinition; 23 private context: WorkflowContext; 24 25 constructor(workflow: WorkflowDefinition) { 26 this.workflow = workflow; 27 this.context = { 28 workflowId: generateId(), 29 projectName: process.env.PROJECT_NAME || 'unknown', 30 startedAt: new Date(), 31 environment: {}, 32 results: new Map(), 33 }; 34 } 35 36 async execute(): Promise<WorkflowResult> { 37 // Call onStart hook 38 await this.workflow.hooks?.onStart?.(this.context); 39 40 try { 41 for (const phase of this.workflow.phases) { 42 const result = await this.executePhase(phase); 43 this.context.results.set(phase.name, result); 44 45 if (result.status === 'failed' && phase.required) { 46 throw new Error(`Required phase ${phase.name} failed`); 47 } 48 } 49 50 const result = { 51 status: 'completed', 52 phases: Object.fromEntries(this.context.results), 53 duration: Date.now() - this.context.startedAt.getTime(), 54 }; 55 56 await this.workflow.hooks?.onComplete?.(result, this.context); 57 return result; 58 59 } catch (error) { 60 const currentPhase = this.getCurrentPhase(); 61 await this.workflow.hooks?.onError?.(error, currentPhase, this.context); 62 63 if (this.workflow.rollback?.enabled) { 64 await this.executeRollback(error); 65 } 66 67 throw error; 68 } 69 } 70 71 private async executePhase(phase: WorkflowPhase): Promise<PhaseResult> { 72 const result: PhaseResult = { 73 status: 'running', 74 startedAt: new Date(), 75 }; 76 77 // Call onPhaseStart hook 78 await this.workflow.hooks?.onPhaseStart?.(phase, this.context); 79 80 // Check if approval is required 81 if (phase.requiresApproval) { 82 const approved = await this.workflow.hooks?.onApprovalRequired?.( 83 phase, 84 this.context 85 ); 86 if (!approved) { 87 result.status = 'skipped'; 88 result.completedAt = new Date(); 89 return result; 90 } 91 } 92 93 try { 94 // Execute phase steps 95 for (const step of phase.steps || []) { 96 await this.executeStep(step, phase); 97 } 98 99 // Invoke phase agents 100 for (const agentName of phase.agents) { 101 await this.invokePhaseAgent(agentName, phase); 102 } 103 104 result.status = 'completed'; 105 result.completedAt = new Date(); 106 107 } catch (error) { 108 result.status = 'failed'; 109 result.error = error; 110 result.completedAt = new Date(); 111 } 112 113 // Call onPhaseComplete hook 114 await this.workflow.hooks?.onPhaseComplete?.(phase, result, this.context); 115 116 return result; 117 } 118 119 private async executeStep(step: WorkflowStep, phase: WorkflowPhase) { 120 console.log(`Executing step: ${step.name}`); 121 122 if (step.command) { 123 const { execSync } = require('child_process'); 124 const env = { 125 ...process.env, 126 ...this.resolveEnvVariables(step.env || {}), 127 }; 128 129 try { 130 const output = execSync(step.command, { 131 encoding: 'utf-8', 132 env, 133 timeout: phase.timeout, 134 }); 135 136 if (step.captureOutput) { 137 this.context.environment[`${step.name}_output`] = output; 138 } 139 } catch (error) { 140 if (step.failOnError) { 141 throw error; 142 } 143 console.warn(`Step ${step.name} failed but continuing: ${error.message}`); 144 } 145 } 146 147 if (step.action) { 148 await this.executeAction(step); 149 } 150 } 151 152 private async executeAction(step: WorkflowStep) { 153 switch (step.action) { 154 case 'http_health_check': 155 await this.healthCheck(step.url, step.retries, step.retryDelay); 156 break; 157 158 case 'monitor_metrics': 159 await this.monitorMetrics(step.duration, step.thresholds); 160 break; 161 162 case 'send_notification': 163 await this.sendNotification(step.channels, step.message); 164 break; 165 166 default: 167 console.warn(`Unknown action: ${step.action}`); 168 } 169 } 170 171 private async invokePhaseAgent(agentName: string, phase: WorkflowPhase) { 172 const prompt = ` 173You are assisting with the "${phase.displayName}" phase of the deployment workflow. 174 175Phase Description: ${phase.description} 176 177Current Context: 178- Workflow ID: ${this.context.workflowId} 179- Project: ${this.context.projectName} 180- Phase: ${phase.name} 181 182Please review the current state and provide guidance or perform necessary checks. 183`; 184 185 const result = await invokeAgent(agentName, { prompt }); 186 console.log(`Agent ${agentName} response:`, result); 187 return result; 188 } 189 190 private async healthCheck( 191 url: string, 192 retries: number = 3, 193 retryDelay: number = 5000 194 ) { 195 const resolvedUrl = this.resolveEnvVariables({ url }).url; 196 197 for (let i = 0; i < retries; i++) { 198 try { 199 const response = await fetch(resolvedUrl); 200 if (response.ok) { 201 return true; 202 } 203 } catch (error) { 204 console.log(`Health check attempt ${i + 1} failed`); 205 } 206 207 if (i < retries - 1) { 208 await new Promise((resolve) => setTimeout(resolve, retryDelay)); 209 } 210 } 211 212 throw new Error(`Health check failed after ${retries} attempts`); 213 } 214 215 private async monitorMetrics(duration: number, thresholds: any) { 216 const startTime = Date.now(); 217 218 while (Date.now() - startTime < duration) { 219 const metrics = await this.fetchMetrics(); 220 221 if (metrics.errorRate > thresholds.errorRate) { 222 throw new Error(`Error rate threshold exceeded: ${metrics.errorRate}`); 223 } 224 225 if (metrics.p99Latency > thresholds.p99Latency) { 226 throw new Error(`Latency threshold exceeded: ${metrics.p99Latency}ms`); 227 } 228 229 await new Promise((resolve) => setTimeout(resolve, 10000)); // Check every 10s 230 } 231 } 232 233 private async executeRollback(error: Error) { 234 console.log('Initiating rollback...'); 235 236 for (const step of this.workflow.rollback?.steps || []) { 237 try { 238 await this.executeStep(step, { name: 'rollback' } as WorkflowPhase); 239 } catch (rollbackError) { 240 console.error(`Rollback step ${step.name} failed:`, rollbackError); 241 } 242 } 243 } 244 245 private resolveEnvVariables(obj: Record<string, any>): Record<string, any> { 246 const result: Record<string, any> = {}; 247 248 for (const [key, value] of Object.entries(obj)) { 249 if (typeof value === 'string') { 250 result[key] = value.replace(/\$\{(\w+)\}/g, (_, name) => { 251 return this.context.environment[name] || process.env[name] || ''; 252 }); 253 } else { 254 result[key] = value; 255 } 256 } 257 258 return result; 259 } 260}

Step 4: Create Workflow CLI Commands#

Add CLI support for the workflow:

1// lib/cli/workflow.ts 2import { deploymentWorkflow } from '@/workflows/deployment'; 3import { WorkflowExecutor } from '@/lib/workflow/executor'; 4 5export async function runWorkflow( 6 workflowName: string, 7 options: { phase?: string; dryRun?: boolean } 8) { 9 const workflows = { 10 deployment: deploymentWorkflow, 11 // Add more custom workflows here 12 }; 13 14 const workflow = workflows[workflowName]; 15 if (!workflow) { 16 throw new Error(`Unknown workflow: ${workflowName}`); 17 } 18 19 console.log(`Starting workflow: ${workflow.displayName}`); 20 console.log(`Description: ${workflow.description}`); 21 console.log(''); 22 23 if (options.dryRun) { 24 console.log('DRY RUN - showing workflow phases:'); 25 for (const phase of workflow.phases) { 26 console.log(` ${phase.name}: ${phase.displayName}`); 27 console.log(` Agents: ${phase.agents.join(', ')}`); 28 console.log(` Required: ${phase.required}`); 29 console.log(` Approval: ${phase.requiresApproval || false}`); 30 console.log(''); 31 } 32 return; 33 } 34 35 const executor = new WorkflowExecutor(workflow); 36 37 if (options.phase) { 38 // Execute specific phase only 39 const phase = workflow.phases.find((p) => p.name === options.phase); 40 if (!phase) { 41 throw new Error(`Unknown phase: ${options.phase}`); 42 } 43 await executor.executePhase(phase); 44 } else { 45 // Execute full workflow 46 await executor.execute(); 47 } 48}

Step 5: Add Workflow Hooks#

Create reusable hooks for notifications and tracking:

1// lib/workflow/hooks.ts 2import { WebClient } from '@slack/web-api'; 3 4const slack = new WebClient(process.env.SLACK_TOKEN); 5const SLACK_CHANNEL = process.env.SLACK_DEPLOY_CHANNEL || '#deployments'; 6 7export async function notifySlack(message: string, options?: { 8 color?: string; 9 fields?: Array<{ title: string; value: string }>; 10}) { 11 try { 12 await slack.chat.postMessage({ 13 channel: SLACK_CHANNEL, 14 text: message, 15 attachments: options?.fields ? [{ 16 color: options.color || '#36a64f', 17 fields: options.fields, 18 }] : undefined, 19 }); 20 } catch (error) { 21 console.error('Failed to send Slack notification:', error); 22 } 23} 24 25export async function updateDeploymentStatus( 26 deploymentId: string, 27 phase: string, 28 status: string 29) { 30 // Update deployment status in database 31 const { prisma } = await import('@/lib/prisma'); 32 33 await prisma.deployment.update({ 34 where: { id: deploymentId }, 35 data: { 36 currentPhase: phase, 37 status, 38 updatedAt: new Date(), 39 }, 40 }); 41} 42 43export async function recordDeployment(context: WorkflowContext) { 44 const { prisma } = await import('@/lib/prisma'); 45 46 await prisma.deployment.create({ 47 data: { 48 id: context.workflowId, 49 projectName: context.projectName, 50 startedAt: context.startedAt, 51 completedAt: new Date(), 52 status: 'completed', 53 phases: JSON.stringify(Object.fromEntries(context.results)), 54 }, 55 }); 56} 57 58export async function waitForApproval( 59 deploymentId: string, 60 phase: string 61): Promise<boolean> { 62 // Send approval request 63 await notifySlack(`⏸️ Deployment ${deploymentId} requires approval for ${phase}`, { 64 color: '#FFA500', 65 fields: [ 66 { title: 'Deployment', value: deploymentId }, 67 { title: 'Phase', value: phase }, 68 { title: 'Action', value: 'Reply with "approve" or "reject"' }, 69 ], 70 }); 71 72 // In production, you'd implement a proper approval flow 73 // This is a simplified example 74 return new Promise((resolve) => { 75 // Wait for approval via webhook, Slack interaction, or manual input 76 const timeout = setTimeout(() => resolve(false), 3600000); // 1 hour timeout 77 78 // Listen for approval event 79 process.once(`approval:${deploymentId}:${phase}`, (approved) => { 80 clearTimeout(timeout); 81 resolve(approved); 82 }); 83 }); 84}

Step 6: Create Workflow Dashboard#

Build a dashboard to monitor workflows:

1// app/api/workflows/route.ts 2import { auth } from '@clerk/nextjs/server'; 3import { prisma } from '@/lib/prisma'; 4import { NextRequest, NextResponse } from 'next/server'; 5 6export async function GET(request: NextRequest) { 7 const { userId } = await auth(); 8 if (!userId) { 9 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); 10 } 11 12 const deployments = await prisma.deployment.findMany({ 13 orderBy: { startedAt: 'desc' }, 14 take: 20, 15 }); 16 17 return NextResponse.json(deployments); 18}
1// components/WorkflowDashboard.tsx 2'use client'; 3 4import { useState, useEffect } from 'react'; 5import { formatDistanceToNow } from 'date-fns'; 6 7interface Deployment { 8 id: string; 9 projectName: string; 10 currentPhase: string; 11 status: string; 12 startedAt: string; 13 completedAt?: string; 14} 15 16export function WorkflowDashboard() { 17 const [deployments, setDeployments] = useState<Deployment[]>([]); 18 19 useEffect(() => { 20 fetch('/api/workflows') 21 .then((res) => res.json()) 22 .then(setDeployments); 23 }, []); 24 25 const statusColors = { 26 running: 'bg-blue-100 text-blue-800', 27 completed: 'bg-green-100 text-green-800', 28 failed: 'bg-red-100 text-red-800', 29 pending: 'bg-gray-100 text-gray-800', 30 }; 31 32 return ( 33 <div className="space-y-4"> 34 <h2 className="text-xl font-semibold">Recent Deployments</h2> 35 36 <div className="space-y-2"> 37 {deployments.map((deployment) => ( 38 <div 39 key={deployment.id} 40 className="p-4 bg-white rounded-lg border border-gray-200" 41 > 42 <div className="flex items-center justify-between"> 43 <div> 44 <span className="font-medium">{deployment.projectName}</span> 45 <span className="ml-2 text-sm text-gray-500"> 46 {formatDistanceToNow(new Date(deployment.startedAt), { 47 addSuffix: true, 48 })} 49 </span> 50 </div> 51 <div className="flex items-center gap-2"> 52 <span className="text-sm text-gray-500"> 53 {deployment.currentPhase} 54 </span> 55 <span 56 className={`px-2 py-1 text-xs rounded-full ${ 57 statusColors[deployment.status] 58 }`} 59 > 60 {deployment.status} 61 </span> 62 </div> 63 </div> 64 </div> 65 ))} 66 </div> 67 </div> 68 ); 69}

Step 7: Use the Custom Workflow#

Via CLI#

1# Start the full workflow 2bootspring workflow start deployment 3 4# Dry run to see phases 5bootspring workflow start deployment --dry-run 6 7# Run specific phase 8bootspring workflow start deployment --phase staging 9 10# Check workflow status 11bootspring workflow status deployment

Via MCP#

In Claude or your AI assistant:

Start the deployment workflow for production

Programmatically#

1// scripts/deploy.ts 2import { runWorkflow } from '@/lib/cli/workflow'; 3 4async function main() { 5 try { 6 await runWorkflow('deployment', {}); 7 console.log('Deployment completed successfully!'); 8 } catch (error) { 9 console.error('Deployment failed:', error); 10 process.exit(1); 11 } 12} 13 14main();

Step 8: Test the Workflow#

Create tests for your workflow:

1// __tests__/workflows/deployment.test.ts 2import { describe, it, expect, vi } from 'vitest'; 3import { deploymentWorkflow } from '@/workflows/deployment'; 4import { WorkflowExecutor } from '@/lib/workflow/executor'; 5 6describe('Deployment Workflow', () => { 7 it('has all required phases', () => { 8 const phases = deploymentWorkflow.phases.map((p) => p.name); 9 10 expect(phases).toContain('pre-deploy'); 11 expect(phases).toContain('staging'); 12 expect(phases).toContain('validation'); 13 expect(phases).toContain('production'); 14 expect(phases).toContain('monitoring'); 15 }); 16 17 it('requires approval for production phase', () => { 18 const productionPhase = deploymentWorkflow.phases.find( 19 (p) => p.name === 'production' 20 ); 21 22 expect(productionPhase?.requiresApproval).toBe(true); 23 }); 24 25 it('has rollback configuration', () => { 26 expect(deploymentWorkflow.rollback?.enabled).toBe(true); 27 expect(deploymentWorkflow.rollback?.triggers.length).toBeGreaterThan(0); 28 }); 29 30 it('executes phases in order', async () => { 31 const executor = new WorkflowExecutor(deploymentWorkflow); 32 const phasesExecuted: string[] = []; 33 34 vi.spyOn(executor, 'executePhase').mockImplementation(async (phase) => { 35 phasesExecuted.push(phase.name); 36 return { status: 'completed' }; 37 }); 38 39 await executor.execute(); 40 41 expect(phasesExecuted).toEqual([ 42 'pre-deploy', 43 'staging', 44 'validation', 45 'production', 46 'monitoring', 47 ]); 48 }); 49});

Verification Checklist#

  • Workflow configuration added
  • Workflow definition file created
  • Executor implemented
  • Hooks configured (Slack, tracking)
  • CLI commands working
  • Dashboard displays workflows
  • Rollback configured
  • Tests passing

What You Learned#

  • Defining multi-phase workflows
  • Building workflow executors
  • Implementing hooks and notifications
  • Creating approval gates
  • Configuring automatic rollback
  • Testing workflows

Next Steps#