pi-cicd 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +56 -0
- package/docs/API.md +61 -0
- package/docs/COMMANDS.md +138 -0
- package/docs/CONFIG.md +123 -0
- package/docs/GUIDE.md +171 -0
- package/docs/PATTERNS.md +49 -0
- package/docs/QUICKSTART.md +99 -0
- package/install.mjs +34 -0
- package/package.json +26 -26
- package/skills/intelligent-deploy/SKILL.md +229 -0
- package/src/deploy/canary-deploy.ts +211 -0
- package/src/deploy/landing-queue.ts +222 -0
- package/src/headless/answer-injector.ts +2 -1
- package/src/headless/orchestrator.ts +2 -1
- package/src/index.ts +52 -0
- package/src/workflow/deployment-workflow.ts +153 -0
- package/AGENTS.md +0 -25
- package/tsconfig.json +0 -19
package/install.mjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Install script for <PKG-NAME>
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as os from "node:os";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
|
|
10
|
+
const home = os.homedir();
|
|
11
|
+
const agentDir = path.join(home, ".pi", "agent");
|
|
12
|
+
const pkgName = path.basename(process.cwd());
|
|
13
|
+
const configPath = path.join(agentDir, `${pkgName}.json`);
|
|
14
|
+
|
|
15
|
+
// Create config directory
|
|
16
|
+
fs.mkdirSync(agentDir, { recursive: true });
|
|
17
|
+
|
|
18
|
+
// Check if config already exists
|
|
19
|
+
if (!fs.existsSync(configPath)) {
|
|
20
|
+
const defaultConfig = {
|
|
21
|
+
enabled: true
|
|
22
|
+
};
|
|
23
|
+
fs.writeFileSync(configPath, `${JSON.stringify(defaultConfig, null, 2)}\n`, "utf-8");
|
|
24
|
+
console.log(`Created default ${pkgName} config: ${configPath}`);
|
|
25
|
+
} else {
|
|
26
|
+
console.log(`${pkgName} config already exists: ${configPath}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(`\nInstall the published package in Pi with:`);
|
|
30
|
+
console.log(` pi install npm:@baphuongna/${pkgName}`);
|
|
31
|
+
console.log(`\nFor local development from a cloned repo:`);
|
|
32
|
+
console.log(` pi install .`);
|
|
33
|
+
console.log(`\nVerify installation:`);
|
|
34
|
+
console.log(` pi list\n`);
|
package/package.json
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cicd",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"author": "baphuongna",
|
|
6
|
-
"license": "MIT",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Extension for Pi coding agent",
|
|
7
5
|
"type": "module",
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"module": "./index.ts",
|
|
8
|
+
"types": "./index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./index.ts",
|
|
12
|
+
"types": "./index.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
16
15
|
"files": [
|
|
17
16
|
"*.ts",
|
|
17
|
+
"*.mjs",
|
|
18
18
|
"src/**/*.ts",
|
|
19
|
+
"skills/**/*",
|
|
19
20
|
"README.md",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
21
|
+
"docs/",
|
|
22
|
+
"CHANGELOG.md",
|
|
23
|
+
"LICENSE"
|
|
22
24
|
],
|
|
23
|
-
"scripts": {
|
|
24
|
-
"check": "npm run typecheck && npm test",
|
|
25
|
-
"typecheck": "tsc --noEmit",
|
|
26
|
-
"test": "node --experimental-strip-types --test --test-concurrency=1 --test-timeout=30000 test/unit/*.test.ts"
|
|
27
|
-
},
|
|
28
25
|
"pi": {
|
|
29
26
|
"extensions": [
|
|
30
27
|
"./index.ts"
|
|
31
28
|
]
|
|
32
29
|
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"pi",
|
|
32
|
+
"coding-agent",
|
|
33
|
+
"extension"
|
|
34
|
+
],
|
|
35
|
+
"author": "BaphuongNA",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": "https://github.com/baphuongna/pi-cicd",
|
|
33
38
|
"peerDependencies": {
|
|
34
|
-
"
|
|
35
|
-
},
|
|
36
|
-
"peerDependenciesMeta": {
|
|
37
|
-
"@mariozechner/pi-coding-agent": {
|
|
38
|
-
"optional": true
|
|
39
|
-
}
|
|
39
|
+
"typescript": "^5.0.0"
|
|
40
40
|
}
|
|
41
|
-
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: intelligent-deploy
|
|
3
|
+
description: Canary deployment with monitoring and landing queue management
|
|
4
|
+
triggers:
|
|
5
|
+
- deploy
|
|
6
|
+
- canary
|
|
7
|
+
- rollout
|
|
8
|
+
- landing queue
|
|
9
|
+
- production
|
|
10
|
+
requirements:
|
|
11
|
+
tools: [bash]
|
|
12
|
+
context: [deployment target]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Intelligent Deploy Skill
|
|
16
|
+
|
|
17
|
+
## Objective
|
|
18
|
+
Execute safe deployments with canary monitoring, automatic rollback, and landing queue management.
|
|
19
|
+
|
|
20
|
+
## When to Use
|
|
21
|
+
- When user asks to "deploy" or "ship to production"
|
|
22
|
+
- When running CI/CD pipelines
|
|
23
|
+
- When managing multiple deployments
|
|
24
|
+
- When requiring gradual rollouts with monitoring
|
|
25
|
+
|
|
26
|
+
## Workflow
|
|
27
|
+
|
|
28
|
+
### Step 1: Canary Deployment
|
|
29
|
+
```typescript
|
|
30
|
+
import { CanaryDeploy } from '../../src/deploy/canary-deploy';
|
|
31
|
+
|
|
32
|
+
const canary = new CanaryDeploy({
|
|
33
|
+
initialPercentage: 10,
|
|
34
|
+
incrementPercentage: 10,
|
|
35
|
+
stepInterval: 60000, // 1 minute
|
|
36
|
+
totalDuration: 300000, // 5 minutes
|
|
37
|
+
metrics: {
|
|
38
|
+
successRate: { min: 95 },
|
|
39
|
+
latency: { max: 500 },
|
|
40
|
+
errorRate: { max: 5 },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const result = await canary.deploy({
|
|
45
|
+
name: 'production',
|
|
46
|
+
url: 'https://api.example.com',
|
|
47
|
+
healthy: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log(canary.formatReport(result));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Step 2: Landing Queue
|
|
54
|
+
```typescript
|
|
55
|
+
import { LandingQueue } from '../../src/deploy/landing-queue';
|
|
56
|
+
|
|
57
|
+
const queue = new LandingQueue();
|
|
58
|
+
|
|
59
|
+
// Add to queue
|
|
60
|
+
queue.enqueue('v1.2.0', 'production', 'New feature release');
|
|
61
|
+
queue.enqueue('v1.2.1', 'production', 'Bug fix');
|
|
62
|
+
|
|
63
|
+
// Process queue
|
|
64
|
+
while (true) {
|
|
65
|
+
const next = await queue.startNext();
|
|
66
|
+
if (!next) break;
|
|
67
|
+
|
|
68
|
+
// Deploy
|
|
69
|
+
const success = await deploy(next);
|
|
70
|
+
|
|
71
|
+
// Mark complete
|
|
72
|
+
queue.complete(next.id, success);
|
|
73
|
+
|
|
74
|
+
if (!success) break; // Stop on failure
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Step 3: Monitor and Rollback
|
|
79
|
+
```typescript
|
|
80
|
+
// Automatic rollback on issues
|
|
81
|
+
if (result.rolledBack) {
|
|
82
|
+
console.log('Deployment rolled back due to issues');
|
|
83
|
+
await canary.rollback(target);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Canary Configuration
|
|
88
|
+
|
|
89
|
+
| Parameter | Default | Description |
|
|
90
|
+
|-----------|---------|-------------|
|
|
91
|
+
| initialPercentage | 10% | Starting traffic |
|
|
92
|
+
| incrementPercentage | 10% | Traffic increase per step |
|
|
93
|
+
| stepInterval | 1 min | Time between increments |
|
|
94
|
+
| totalDuration | 5 min | Total deployment time |
|
|
95
|
+
|
|
96
|
+
### Metrics Thresholds
|
|
97
|
+
|
|
98
|
+
| Metric | Threshold | Action |
|
|
99
|
+
|--------|-----------|--------|
|
|
100
|
+
| Success Rate | > 95% | Continue |
|
|
101
|
+
| Latency | < 500ms | Continue |
|
|
102
|
+
| Error Rate | < 5% | Continue |
|
|
103
|
+
|
|
104
|
+
If metrics drop below thresholds:
|
|
105
|
+
- Warning at threshold
|
|
106
|
+
- Auto-rollback at critical level (90% success, 10% errors)
|
|
107
|
+
|
|
108
|
+
## Landing Queue Commands
|
|
109
|
+
|
|
110
|
+
### Enqueue Deployment
|
|
111
|
+
```typescript
|
|
112
|
+
queue.enqueue('v1.2.0', 'production', 'Feature release');
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Process Queue
|
|
116
|
+
```typescript
|
|
117
|
+
while (const next = queue.startNext()) {
|
|
118
|
+
await deploy(next);
|
|
119
|
+
queue.complete(next.id, success);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### View Status
|
|
124
|
+
```typescript
|
|
125
|
+
console.log(queue.formatReport());
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Examples
|
|
129
|
+
|
|
130
|
+
### Deploy with Canary
|
|
131
|
+
```
|
|
132
|
+
User: Deploy v1.2.0 to production with canary
|
|
133
|
+
Agent:
|
|
134
|
+
const result = await canary.deploy({ name: 'production', url: '...', healthy: true });
|
|
135
|
+
if (result.success) {
|
|
136
|
+
console.log('Deployment successful!');
|
|
137
|
+
} else {
|
|
138
|
+
console.log('Rolled back - check issues');
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Process Landing Queue
|
|
143
|
+
```
|
|
144
|
+
User: Queue these deployments: v1.2.0, v1.2.1, v1.2.2
|
|
145
|
+
Agent:
|
|
146
|
+
queue.enqueue('v1.2.0', 'production');
|
|
147
|
+
queue.enqueue('v1.2.1', 'production');
|
|
148
|
+
queue.enqueue('v1.2.2', 'staging');
|
|
149
|
+
|
|
150
|
+
const result = await queue.processAll();
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### View Queue Status
|
|
154
|
+
```
|
|
155
|
+
User: Show me the current deployment queue
|
|
156
|
+
Agent:
|
|
157
|
+
console.log(queue.formatReport());
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Output Format
|
|
161
|
+
|
|
162
|
+
### Canary Report
|
|
163
|
+
```markdown
|
|
164
|
+
## Canary Deployment Report
|
|
165
|
+
**Status:** ✅ SUCCESS
|
|
166
|
+
**Final Traffic:** 100%
|
|
167
|
+
**Rolled Back:** No
|
|
168
|
+
|
|
169
|
+
### Metrics History
|
|
170
|
+
| Time | Success | Latency | Error Rate |
|
|
171
|
+
|------|---------|---------|------------|
|
|
172
|
+
| 0min | 98.5% | 120ms | 1.2% |
|
|
173
|
+
| 1min | 99.1% | 115ms | 0.8% |
|
|
174
|
+
| 2min | 99.3% | 110ms | 0.5% |
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Landing Queue Report
|
|
178
|
+
```markdown
|
|
179
|
+
## Landing Queue Report
|
|
180
|
+
**Total:** 5 | **Pending:** 2 | **Deploying:** 1 | **Deployed:** 2 | **Failed:** 0
|
|
181
|
+
|
|
182
|
+
### Currently Deploying
|
|
183
|
+
**v1.2.1** -> production
|
|
184
|
+
Status: deploying
|
|
185
|
+
|
|
186
|
+
### Queue
|
|
187
|
+
| # | Version | Environment | Message |
|
|
188
|
+
|---|--------|------------|---------|
|
|
189
|
+
| 1 | v1.2.2 | production | Hotfix |
|
|
190
|
+
| 2 | v1.2.3 | staging | New feature |
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Integration
|
|
194
|
+
|
|
195
|
+
### With pi-pipeline
|
|
196
|
+
```typescript
|
|
197
|
+
// Run as part of ship workflow
|
|
198
|
+
const gates = new QualityGates();
|
|
199
|
+
const gatesResult = await gates.run('pre-push');
|
|
200
|
+
|
|
201
|
+
if (gatesResult.passed) {
|
|
202
|
+
const result = await canary.deploy(target);
|
|
203
|
+
if (!result.success) {
|
|
204
|
+
throw new Error('Deployment failed');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### With pi-audit
|
|
210
|
+
```typescript
|
|
211
|
+
// Security scan before deploy
|
|
212
|
+
const shield = new AgentShield();
|
|
213
|
+
const scan = shield.scan(deploymentCode);
|
|
214
|
+
|
|
215
|
+
if (!scan.passed) {
|
|
216
|
+
console.log('Security issues must be fixed');
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### With pi-recollect
|
|
222
|
+
```typescript
|
|
223
|
+
// Remember deployment
|
|
224
|
+
await memory.remember(
|
|
225
|
+
'deployment',
|
|
226
|
+
`Deployed ${version} to ${environment}`,
|
|
227
|
+
'observation'
|
|
228
|
+
);
|
|
229
|
+
```
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canary Deployment with Monitoring
|
|
3
|
+
* Gradual rollout with automatic rollback
|
|
4
|
+
* Based on gstack /canary pattern
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface CanaryConfig {
|
|
8
|
+
/** Initial traffic percentage for canary */
|
|
9
|
+
initialPercentage: number;
|
|
10
|
+
/** Increment per step */
|
|
11
|
+
incrementPercentage: number;
|
|
12
|
+
/** Time between increments (ms) */
|
|
13
|
+
stepInterval: number;
|
|
14
|
+
/** Total duration before full rollout (ms) */
|
|
15
|
+
totalDuration: number;
|
|
16
|
+
/** Metrics to monitor */
|
|
17
|
+
metrics: {
|
|
18
|
+
successRate: { min: number };
|
|
19
|
+
latency: { max: number };
|
|
20
|
+
errorRate: { max: number };
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CanaryMetrics {
|
|
25
|
+
timestamp: number;
|
|
26
|
+
successRate: number;
|
|
27
|
+
latency: number;
|
|
28
|
+
errorRate: number;
|
|
29
|
+
requests: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CanaryResult {
|
|
33
|
+
success: boolean;
|
|
34
|
+
finalPercentage: number;
|
|
35
|
+
metrics: CanaryMetrics[];
|
|
36
|
+
issues: string[];
|
|
37
|
+
rolledBack: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface DeployTarget {
|
|
41
|
+
name: string;
|
|
42
|
+
url: string;
|
|
43
|
+
healthy: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Canary Deployment Manager
|
|
48
|
+
*/
|
|
49
|
+
export class CanaryDeploy {
|
|
50
|
+
private config: CanaryConfig;
|
|
51
|
+
private metricsHistory: CanaryMetrics[] = [];
|
|
52
|
+
|
|
53
|
+
constructor(config?: Partial<CanaryConfig>) {
|
|
54
|
+
this.config = {
|
|
55
|
+
initialPercentage: config?.initialPercentage ?? 10,
|
|
56
|
+
incrementPercentage: config?.incrementPercentage ?? 10,
|
|
57
|
+
stepInterval: config?.stepInterval ?? 60000, // 1 minute
|
|
58
|
+
totalDuration: config?.totalDuration ?? 300000, // 5 minutes
|
|
59
|
+
metrics: config?.metrics ?? {
|
|
60
|
+
successRate: { min: 95 },
|
|
61
|
+
latency: { max: 500 },
|
|
62
|
+
errorRate: { max: 5 },
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Execute canary deployment
|
|
69
|
+
*/
|
|
70
|
+
async deploy(target: DeployTarget): Promise<CanaryResult> {
|
|
71
|
+
console.log(`Starting canary deployment to ${target.name}`);
|
|
72
|
+
console.log(`Initial: ${this.config.initialPercentage}% traffic`);
|
|
73
|
+
|
|
74
|
+
const issues: string[] = [];
|
|
75
|
+
let currentPercentage = this.config.initialPercentage;
|
|
76
|
+
let rolledBack = false;
|
|
77
|
+
|
|
78
|
+
const startTime = Date.now();
|
|
79
|
+
|
|
80
|
+
while (Date.now() - startTime < this.config.totalDuration) {
|
|
81
|
+
// Get current metrics
|
|
82
|
+
const metrics = await this.getMetrics(target);
|
|
83
|
+
this.metricsHistory.push(metrics);
|
|
84
|
+
|
|
85
|
+
// Check for issues
|
|
86
|
+
const detectedIssues = this.checkMetrics(metrics);
|
|
87
|
+
if (detectedIssues.length > 0) {
|
|
88
|
+
issues.push(...detectedIssues);
|
|
89
|
+
console.log(`⚠️ Issues detected: ${detectedIssues.join(', ')}`);
|
|
90
|
+
|
|
91
|
+
// Auto-rollback on critical issues
|
|
92
|
+
if (metrics.successRate < 90 || metrics.errorRate > 10) {
|
|
93
|
+
console.log('🔴 Critical issues - rolling back!');
|
|
94
|
+
rolledBack = true;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Print status
|
|
100
|
+
console.log(`[${Math.round((Date.now() - startTime) / 1000)}s] ` +
|
|
101
|
+
`Traffic: ${currentPercentage}% | ` +
|
|
102
|
+
`Success: ${metrics.successRate.toFixed(1)}% | ` +
|
|
103
|
+
`Latency: ${metrics.latency.toFixed(0)}ms`);
|
|
104
|
+
|
|
105
|
+
// Wait for next step
|
|
106
|
+
await this.delay(this.config.stepInterval);
|
|
107
|
+
|
|
108
|
+
// Increment traffic
|
|
109
|
+
currentPercentage = Math.min(100, currentPercentage + this.config.incrementPercentage);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
success: !rolledBack,
|
|
114
|
+
finalPercentage: rolledBack ? 0 : currentPercentage,
|
|
115
|
+
metrics: this.metricsHistory,
|
|
116
|
+
issues,
|
|
117
|
+
rolledBack,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get current metrics from target
|
|
123
|
+
*/
|
|
124
|
+
private async getMetrics(target: DeployTarget): Promise<CanaryMetrics> {
|
|
125
|
+
// Simulate metrics collection
|
|
126
|
+
// In production: call monitoring API
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
timestamp: Date.now(),
|
|
130
|
+
successRate: 95 + Math.random() * 5,
|
|
131
|
+
latency: 50 + Math.random() * 100,
|
|
132
|
+
errorRate: Math.random() * 3,
|
|
133
|
+
requests: Math.floor(100 + Math.random() * 900),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check metrics against thresholds
|
|
139
|
+
*/
|
|
140
|
+
private checkMetrics(metrics: CanaryMetrics): string[] {
|
|
141
|
+
const issues: string[] = [];
|
|
142
|
+
|
|
143
|
+
if (metrics.successRate < this.config.metrics.successRate.min) {
|
|
144
|
+
issues.push(`Low success rate: ${metrics.successRate.toFixed(1)}%`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (metrics.latency > this.config.metrics.latency.max) {
|
|
148
|
+
issues.push(`High latency: ${metrics.latency.toFixed(0)}ms`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (metrics.errorRate > this.config.metrics.errorRate.max) {
|
|
152
|
+
issues.push(`High error rate: ${metrics.errorRate.toFixed(1)}%`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return issues;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Rollback deployment
|
|
160
|
+
*/
|
|
161
|
+
async rollback(target: DeployTarget): Promise<void> {
|
|
162
|
+
console.log(`Rolling back ${target.name}`);
|
|
163
|
+
// In production: call rollback API
|
|
164
|
+
await this.delay(1000);
|
|
165
|
+
console.log('Rollback complete');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get deployment history
|
|
170
|
+
*/
|
|
171
|
+
getHistory(): CanaryMetrics[] {
|
|
172
|
+
return [...this.metricsHistory];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Generate deployment report
|
|
177
|
+
*/
|
|
178
|
+
formatReport(result: CanaryResult): string {
|
|
179
|
+
const lines: string[] = [];
|
|
180
|
+
|
|
181
|
+
lines.push('## Canary Deployment Report\n');
|
|
182
|
+
lines.push(`**Status:** ${result.success ? '✅ SUCCESS' : '❌ FAILED'}`);
|
|
183
|
+
lines.push(`**Final Traffic:** ${result.finalPercentage}%`);
|
|
184
|
+
lines.push(`**Rolled Back:** ${result.rolledBack ? 'Yes' : 'No'}\n`);
|
|
185
|
+
|
|
186
|
+
if (result.metrics.length > 0) {
|
|
187
|
+
lines.push('### Metrics History\n');
|
|
188
|
+
lines.push('| Time | Success | Latency | Error Rate |');
|
|
189
|
+
lines.push('|------|---------|---------|------------|');
|
|
190
|
+
|
|
191
|
+
result.metrics.forEach((m, i) => {
|
|
192
|
+
lines.push(`| ${i * 1}min | ${m.successRate.toFixed(1)}% | ${m.latency.toFixed(0)}ms | ${m.errorRate.toFixed(1)}% |`);
|
|
193
|
+
});
|
|
194
|
+
lines.push('');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (result.issues.length > 0) {
|
|
198
|
+
lines.push('### Issues Detected\n');
|
|
199
|
+
for (const issue of result.issues) {
|
|
200
|
+
lines.push(`- ${issue}`);
|
|
201
|
+
}
|
|
202
|
+
lines.push('');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return lines.join('\n');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private delay(ms: number): Promise<void> {
|
|
209
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
210
|
+
}
|
|
211
|
+
}
|