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 deploymentVia 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