@friggframework/devtools 2.0.0--canary.474.884529c.0 ā 2.0.0--canary.474.988ec0b.0
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/frigg-cli/deploy-command/index.js +148 -29
- package/frigg-cli/doctor-command/index.js +249 -0
- package/frigg-cli/index.js +24 -1
- package/frigg-cli/repair-command/index.js +341 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +146 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +229 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +180 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
- package/package.json +6 -6
|
@@ -2,6 +2,9 @@ const { spawn } = require('child_process');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
|
|
5
|
+
// Import doctor command for post-deployment health check
|
|
6
|
+
const { doctorCommand } = require('../doctor-command');
|
|
7
|
+
|
|
5
8
|
// Configuration constants
|
|
6
9
|
const PATHS = {
|
|
7
10
|
APP_DEFINITION: 'index.js',
|
|
@@ -134,41 +137,134 @@ function validateAndBuildEnvironment(appDefinition, options) {
|
|
|
134
137
|
* Executes the serverless deployment command
|
|
135
138
|
* @param {Object} environment - Environment variables to pass to serverless
|
|
136
139
|
* @param {Object} options - Deploy command options
|
|
140
|
+
* @returns {Promise<number>} Exit code
|
|
137
141
|
*/
|
|
138
142
|
function executeServerlessDeployment(environment, options) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const serverlessArgs = [
|
|
142
|
-
'deploy',
|
|
143
|
-
'--config',
|
|
144
|
-
PATHS.INFRASTRUCTURE,
|
|
145
|
-
'--stage',
|
|
146
|
-
options.stage,
|
|
147
|
-
];
|
|
148
|
-
|
|
149
|
-
// Add --force flag if force option is true
|
|
150
|
-
if (options.force === true) {
|
|
151
|
-
serverlessArgs.push('--force');
|
|
152
|
-
}
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
console.log('š Deploying serverless application...');
|
|
153
145
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
146
|
+
const serverlessArgs = [
|
|
147
|
+
'deploy',
|
|
148
|
+
'--config',
|
|
149
|
+
PATHS.INFRASTRUCTURE,
|
|
150
|
+
'--stage',
|
|
151
|
+
options.stage,
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
// Add --force flag if force option is true
|
|
155
|
+
if (options.force === true) {
|
|
156
|
+
serverlessArgs.push('--force');
|
|
157
|
+
}
|
|
162
158
|
|
|
163
|
-
|
|
164
|
-
|
|
159
|
+
const childProcess = spawn(COMMANDS.SERVERLESS, serverlessArgs, {
|
|
160
|
+
cwd: path.resolve(process.cwd()),
|
|
161
|
+
stdio: 'inherit',
|
|
162
|
+
env: {
|
|
163
|
+
...environment,
|
|
164
|
+
SLS_STAGE: options.stage, // Set stage for resource discovery
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
childProcess.on('error', (error) => {
|
|
169
|
+
console.error(`Error executing command: ${error.message}`);
|
|
170
|
+
reject(error);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
childProcess.on('close', (code) => {
|
|
174
|
+
if (code !== 0) {
|
|
175
|
+
console.log(`Child process exited with code ${code}`);
|
|
176
|
+
resolve(code);
|
|
177
|
+
} else {
|
|
178
|
+
resolve(0);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
165
181
|
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get stack name from app definition
|
|
186
|
+
* @param {Object} appDefinition - App definition
|
|
187
|
+
* @param {Object} options - Deploy options
|
|
188
|
+
* @returns {string|null} Stack name
|
|
189
|
+
*/
|
|
190
|
+
function getStackName(appDefinition, options) {
|
|
191
|
+
// Try to get from app definition
|
|
192
|
+
if (appDefinition?.name) {
|
|
193
|
+
const stage = options.stage || 'dev';
|
|
194
|
+
return `${appDefinition.name}-${stage}`;
|
|
195
|
+
}
|
|
166
196
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
197
|
+
// Try to get from infrastructure.js
|
|
198
|
+
const infraPath = path.join(process.cwd(), PATHS.INFRASTRUCTURE);
|
|
199
|
+
if (fs.existsSync(infraPath)) {
|
|
200
|
+
try {
|
|
201
|
+
const infraModule = require(infraPath);
|
|
202
|
+
if (infraModule.service) {
|
|
203
|
+
const stage = options.stage || 'dev';
|
|
204
|
+
return `${infraModule.service}-${stage}`;
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
// Ignore errors reading infrastructure file
|
|
170
208
|
}
|
|
171
|
-
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Run post-deployment health check
|
|
216
|
+
* @param {string} stackName - CloudFormation stack name
|
|
217
|
+
* @param {Object} options - Deploy options
|
|
218
|
+
*/
|
|
219
|
+
async function runPostDeploymentHealthCheck(stackName, options) {
|
|
220
|
+
console.log('\n' + 'ā'.repeat(80));
|
|
221
|
+
console.log('Running post-deployment health check...');
|
|
222
|
+
console.log('ā'.repeat(80));
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
// Run doctor command (will exit process on its own)
|
|
226
|
+
// Note: We need to catch the exit to prevent deploy from exiting
|
|
227
|
+
const originalExit = process.exit;
|
|
228
|
+
let doctorExitCode = 0;
|
|
229
|
+
|
|
230
|
+
// Temporarily override process.exit to capture exit code
|
|
231
|
+
process.exit = (code) => {
|
|
232
|
+
doctorExitCode = code || 0;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
await doctorCommand(stackName, {
|
|
237
|
+
region: options.region,
|
|
238
|
+
format: 'console',
|
|
239
|
+
verbose: options.verbose,
|
|
240
|
+
});
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.log(`\nā ļø Health check encountered an error: ${error.message}`);
|
|
243
|
+
if (options.verbose) {
|
|
244
|
+
console.error(error.stack);
|
|
245
|
+
}
|
|
246
|
+
} finally {
|
|
247
|
+
// Restore original process.exit
|
|
248
|
+
process.exit = originalExit;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Inform user about health check results
|
|
252
|
+
if (doctorExitCode === 0) {
|
|
253
|
+
console.log('\nā Post-deployment health check: PASSED');
|
|
254
|
+
} else if (doctorExitCode === 2) {
|
|
255
|
+
console.log('\nā ļø Post-deployment health check: DEGRADED');
|
|
256
|
+
console.log(' Run "frigg repair" to fix detected issues');
|
|
257
|
+
} else {
|
|
258
|
+
console.log('\nā Post-deployment health check: FAILED');
|
|
259
|
+
console.log(' Run "frigg doctor" for detailed report');
|
|
260
|
+
console.log(' Run "frigg repair" to fix detected issues');
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.log(`\nā ļø Post-deployment health check failed: ${error.message}`);
|
|
264
|
+
if (options.verbose) {
|
|
265
|
+
console.error(error.stack);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
172
268
|
}
|
|
173
269
|
|
|
174
270
|
async function deployCommand(options) {
|
|
@@ -177,7 +273,30 @@ async function deployCommand(options) {
|
|
|
177
273
|
const appDefinition = loadAppDefinition();
|
|
178
274
|
const environment = validateAndBuildEnvironment(appDefinition, options);
|
|
179
275
|
|
|
180
|
-
|
|
276
|
+
// Execute deployment
|
|
277
|
+
const exitCode = await executeServerlessDeployment(environment, options);
|
|
278
|
+
|
|
279
|
+
// Check if deployment was successful
|
|
280
|
+
if (exitCode !== 0) {
|
|
281
|
+
console.error(`\nā Deployment failed with exit code ${exitCode}`);
|
|
282
|
+
process.exit(exitCode);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
console.log('\nā Deployment completed successfully!');
|
|
286
|
+
|
|
287
|
+
// Run post-deployment health check (unless --skip-doctor)
|
|
288
|
+
if (!options.skipDoctor) {
|
|
289
|
+
const stackName = getStackName(appDefinition, options);
|
|
290
|
+
|
|
291
|
+
if (stackName) {
|
|
292
|
+
await runPostDeploymentHealthCheck(stackName, options);
|
|
293
|
+
} else {
|
|
294
|
+
console.log('\nā ļø Could not determine stack name - skipping health check');
|
|
295
|
+
console.log(' Run "frigg doctor <stack-name>" manually to check stack health');
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
console.log('\nāļø Skipping post-deployment health check (--skip-doctor)');
|
|
299
|
+
}
|
|
181
300
|
}
|
|
182
301
|
|
|
183
302
|
module.exports = { deployCommand };
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frigg Doctor Command
|
|
3
|
+
*
|
|
4
|
+
* Performs comprehensive health check on deployed CloudFormation stack
|
|
5
|
+
* and reports issues like property drift, orphaned resources, and missing resources.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* frigg doctor <stack-name>
|
|
9
|
+
* frigg doctor my-app-prod --region us-east-1
|
|
10
|
+
* frigg doctor my-app-prod --format json --output report.json
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
|
|
16
|
+
// Domain and Application Layer
|
|
17
|
+
const StackIdentifier = require('@friggframework/devtools/infrastructure/domains/health/domain/value-objects/stack-identifier');
|
|
18
|
+
const RunHealthCheckUseCase = require('@friggframework/devtools/infrastructure/domains/health/application/use-cases/run-health-check-use-case');
|
|
19
|
+
|
|
20
|
+
// Infrastructure Layer - AWS Adapters
|
|
21
|
+
const AWSStackRepository = require('@friggframework/devtools/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository');
|
|
22
|
+
const AWSResourceDetector = require('@friggframework/devtools/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector');
|
|
23
|
+
|
|
24
|
+
// Domain Services
|
|
25
|
+
const MismatchAnalyzer = require('@friggframework/devtools/infrastructure/domains/health/domain/services/mismatch-analyzer');
|
|
26
|
+
const HealthScoreCalculator = require('@friggframework/devtools/infrastructure/domains/health/domain/services/health-score-calculator');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Format health report for console output
|
|
30
|
+
* @param {StackHealthReport} report - Health check report
|
|
31
|
+
* @param {Object} options - Formatting options
|
|
32
|
+
* @returns {string} Formatted output
|
|
33
|
+
*/
|
|
34
|
+
function formatConsoleOutput(report, options = {}) {
|
|
35
|
+
const lines = [];
|
|
36
|
+
const summary = report.getSummary();
|
|
37
|
+
|
|
38
|
+
// Header
|
|
39
|
+
lines.push('');
|
|
40
|
+
lines.push('ā'.repeat(80));
|
|
41
|
+
lines.push(` FRIGG DOCTOR - Stack Health Report`);
|
|
42
|
+
lines.push('ā'.repeat(80));
|
|
43
|
+
lines.push('');
|
|
44
|
+
|
|
45
|
+
// Stack Information
|
|
46
|
+
lines.push(`Stack: ${summary.stackName}`);
|
|
47
|
+
lines.push(`Region: ${summary.region}`);
|
|
48
|
+
lines.push(`Timestamp: ${summary.timestamp}`);
|
|
49
|
+
lines.push('');
|
|
50
|
+
|
|
51
|
+
// Health Score
|
|
52
|
+
const scoreIcon = report.healthScore.isHealthy() ? 'ā' : report.healthScore.isUnhealthy() ? 'ā' : 'ā ';
|
|
53
|
+
const scoreColor = report.healthScore.isHealthy() ? '' : '';
|
|
54
|
+
lines.push(`Health Score: ${scoreIcon} ${summary.healthScore}/100 (${summary.qualitativeAssessment})`);
|
|
55
|
+
lines.push('');
|
|
56
|
+
|
|
57
|
+
// Summary Statistics
|
|
58
|
+
lines.push('ā'.repeat(80));
|
|
59
|
+
lines.push('Resources:');
|
|
60
|
+
lines.push(` Total: ${summary.resourceCount}`);
|
|
61
|
+
lines.push(` In Stack: ${report.getResourcesInStack().length}`);
|
|
62
|
+
lines.push(` Drifted: ${summary.driftedResourceCount}`);
|
|
63
|
+
lines.push(` Orphaned: ${summary.orphanedResourceCount}`);
|
|
64
|
+
lines.push(` Missing: ${summary.missingResourceCount}`);
|
|
65
|
+
lines.push('');
|
|
66
|
+
|
|
67
|
+
lines.push('Issues:');
|
|
68
|
+
lines.push(` Total: ${summary.issueCount}`);
|
|
69
|
+
lines.push(` Critical: ${summary.criticalIssueCount}`);
|
|
70
|
+
lines.push(` Warnings: ${summary.warningCount}`);
|
|
71
|
+
lines.push('');
|
|
72
|
+
|
|
73
|
+
// Issues Detail
|
|
74
|
+
if (report.issues.length > 0) {
|
|
75
|
+
lines.push('ā'.repeat(80));
|
|
76
|
+
lines.push('Issue Details:');
|
|
77
|
+
lines.push('');
|
|
78
|
+
|
|
79
|
+
// Group issues by type
|
|
80
|
+
const criticalIssues = report.getCriticalIssues();
|
|
81
|
+
const warnings = report.getWarnings();
|
|
82
|
+
|
|
83
|
+
if (criticalIssues.length > 0) {
|
|
84
|
+
lines.push(' CRITICAL ISSUES:');
|
|
85
|
+
criticalIssues.forEach((issue, idx) => {
|
|
86
|
+
lines.push(` ${idx + 1}. [${issue.type}] ${issue.description}`);
|
|
87
|
+
lines.push(` Resource: ${issue.resourceType} (${issue.resourceId})`);
|
|
88
|
+
if (issue.resolution) {
|
|
89
|
+
lines.push(` Fix: ${issue.resolution}`);
|
|
90
|
+
}
|
|
91
|
+
lines.push('');
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (warnings.length > 0) {
|
|
96
|
+
lines.push(' WARNINGS:');
|
|
97
|
+
warnings.forEach((issue, idx) => {
|
|
98
|
+
lines.push(` ${idx + 1}. [${issue.type}] ${issue.description}`);
|
|
99
|
+
lines.push(` Resource: ${issue.resourceType} (${issue.resourceId})`);
|
|
100
|
+
if (issue.resolution) {
|
|
101
|
+
lines.push(` Fix: ${issue.resolution}`);
|
|
102
|
+
}
|
|
103
|
+
lines.push('');
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
lines.push('ā'.repeat(80));
|
|
108
|
+
lines.push('ā No issues detected - stack is healthy!');
|
|
109
|
+
lines.push('');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Recommendations
|
|
113
|
+
if (report.issues.length > 0) {
|
|
114
|
+
lines.push('ā'.repeat(80));
|
|
115
|
+
lines.push('Recommended Actions:');
|
|
116
|
+
lines.push('');
|
|
117
|
+
|
|
118
|
+
if (report.getOrphanedResourceCount() > 0) {
|
|
119
|
+
lines.push(` ⢠Import ${report.getOrphanedResourceCount()} orphaned resource(s):`);
|
|
120
|
+
lines.push(` $ frigg repair --import <stack-name>`);
|
|
121
|
+
lines.push('');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (report.getDriftedResourceCount() > 0) {
|
|
125
|
+
lines.push(` ⢠Reconcile property drift for ${report.getDriftedResourceCount()} resource(s):`);
|
|
126
|
+
lines.push(` $ frigg repair --reconcile <stack-name>`);
|
|
127
|
+
lines.push('');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (report.getMissingResourceCount() > 0) {
|
|
131
|
+
lines.push(` ⢠Investigate ${report.getMissingResourceCount()} missing resource(s) and redeploy:`);
|
|
132
|
+
lines.push(` $ frigg deploy --stage <stage>`);
|
|
133
|
+
lines.push('');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
lines.push('ā'.repeat(80));
|
|
138
|
+
lines.push('');
|
|
139
|
+
|
|
140
|
+
return lines.join('\n');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Format health report as JSON
|
|
145
|
+
* @param {StackHealthReport} report - Health check report
|
|
146
|
+
* @returns {string} JSON output
|
|
147
|
+
*/
|
|
148
|
+
function formatJsonOutput(report) {
|
|
149
|
+
return JSON.stringify(report.toJSON(), null, 2);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Write output to file
|
|
154
|
+
* @param {string} content - Content to write
|
|
155
|
+
* @param {string} filePath - Output file path
|
|
156
|
+
*/
|
|
157
|
+
function writeOutputFile(content, filePath) {
|
|
158
|
+
try {
|
|
159
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
160
|
+
console.log(`\nā Report saved to: ${filePath}`);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error(`\nā Failed to write output file: ${error.message}`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Execute health check
|
|
169
|
+
* @param {string} stackName - CloudFormation stack name
|
|
170
|
+
* @param {Object} options - Command options
|
|
171
|
+
*/
|
|
172
|
+
async function doctorCommand(stackName, options = {}) {
|
|
173
|
+
try {
|
|
174
|
+
// Validate required parameter
|
|
175
|
+
if (!stackName) {
|
|
176
|
+
console.error('Error: Stack name is required');
|
|
177
|
+
console.log('Usage: frigg doctor <stack-name> [options]');
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Extract options with defaults
|
|
182
|
+
const region = options.region || process.env.AWS_REGION || 'us-east-1';
|
|
183
|
+
const format = options.format || 'console';
|
|
184
|
+
const verbose = options.verbose || false;
|
|
185
|
+
|
|
186
|
+
if (verbose) {
|
|
187
|
+
console.log(`\nš Running health check on stack: ${stackName} (${region})`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 1. Create stack identifier
|
|
191
|
+
const stackIdentifier = new StackIdentifier({ stackName, region });
|
|
192
|
+
|
|
193
|
+
// 2. Wire up infrastructure layer (AWS adapters)
|
|
194
|
+
const stackRepository = new AWSStackRepository({ region });
|
|
195
|
+
const resourceDetector = new AWSResourceDetector({ region });
|
|
196
|
+
|
|
197
|
+
// 3. Wire up domain services
|
|
198
|
+
const mismatchAnalyzer = new MismatchAnalyzer();
|
|
199
|
+
const healthScoreCalculator = new HealthScoreCalculator();
|
|
200
|
+
|
|
201
|
+
// 4. Create and execute use case
|
|
202
|
+
const runHealthCheckUseCase = new RunHealthCheckUseCase({
|
|
203
|
+
stackRepository,
|
|
204
|
+
resourceDetector,
|
|
205
|
+
mismatchAnalyzer,
|
|
206
|
+
healthScoreCalculator,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const report = await runHealthCheckUseCase.execute({ stackIdentifier });
|
|
210
|
+
|
|
211
|
+
// 5. Format and output results
|
|
212
|
+
if (format === 'json') {
|
|
213
|
+
const jsonOutput = formatJsonOutput(report);
|
|
214
|
+
|
|
215
|
+
if (options.output) {
|
|
216
|
+
writeOutputFile(jsonOutput, options.output);
|
|
217
|
+
} else {
|
|
218
|
+
console.log(jsonOutput);
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
const consoleOutput = formatConsoleOutput(report, options);
|
|
222
|
+
console.log(consoleOutput);
|
|
223
|
+
|
|
224
|
+
if (options.output) {
|
|
225
|
+
writeOutputFile(consoleOutput, options.output);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 6. Exit with appropriate code
|
|
230
|
+
// Exit code 0 = healthy, 1 = unhealthy, 2 = degraded
|
|
231
|
+
if (report.healthScore.isUnhealthy()) {
|
|
232
|
+
process.exit(1);
|
|
233
|
+
} else if (report.healthScore.isDegraded()) {
|
|
234
|
+
process.exit(2);
|
|
235
|
+
} else {
|
|
236
|
+
process.exit(0);
|
|
237
|
+
}
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error(`\nā Health check failed: ${error.message}`);
|
|
240
|
+
|
|
241
|
+
if (options.verbose && error.stack) {
|
|
242
|
+
console.error(`\nStack trace:\n${error.stack}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
module.exports = { doctorCommand };
|
package/frigg-cli/index.js
CHANGED
|
@@ -9,6 +9,8 @@ const { deployCommand } = require('./deploy-command');
|
|
|
9
9
|
const { generateIamCommand } = require('./generate-iam-command');
|
|
10
10
|
const { uiCommand } = require('./ui-command');
|
|
11
11
|
const { dbSetupCommand } = require('./db-setup-command');
|
|
12
|
+
const { doctorCommand } = require('./doctor-command');
|
|
13
|
+
const { repairCommand } = require('./repair-command');
|
|
12
14
|
|
|
13
15
|
const program = new Command();
|
|
14
16
|
|
|
@@ -46,6 +48,7 @@ program
|
|
|
46
48
|
.option('-s, --stage <stage>', 'deployment stage', 'dev')
|
|
47
49
|
.option('-v, --verbose', 'enable verbose output')
|
|
48
50
|
.option('-f, --force', 'force deployment (bypasses caching for layers and functions)')
|
|
51
|
+
.option('--skip-doctor', 'skip post-deployment health check')
|
|
49
52
|
.action(deployCommand);
|
|
50
53
|
|
|
51
54
|
program
|
|
@@ -71,6 +74,26 @@ program
|
|
|
71
74
|
.option('-v, --verbose', 'enable verbose output')
|
|
72
75
|
.action(dbSetupCommand);
|
|
73
76
|
|
|
77
|
+
program
|
|
78
|
+
.command('doctor <stackName>')
|
|
79
|
+
.description('Run health check on deployed CloudFormation stack')
|
|
80
|
+
.option('-r, --region <region>', 'AWS region (defaults to AWS_REGION env var or us-east-1)')
|
|
81
|
+
.option('-f, --format <format>', 'output format (console or json)', 'console')
|
|
82
|
+
.option('-o, --output <path>', 'save report to file')
|
|
83
|
+
.option('-v, --verbose', 'enable verbose output')
|
|
84
|
+
.action(doctorCommand);
|
|
85
|
+
|
|
86
|
+
program
|
|
87
|
+
.command('repair <stackName>')
|
|
88
|
+
.description('Repair infrastructure issues (import orphaned resources, reconcile property drift)')
|
|
89
|
+
.option('-r, --region <region>', 'AWS region (defaults to AWS_REGION env var or us-east-1)')
|
|
90
|
+
.option('--import', 'import orphaned resources into stack')
|
|
91
|
+
.option('--reconcile', 'reconcile property drift')
|
|
92
|
+
.option('--mode <mode>', 'reconciliation mode (template or resource)', 'template')
|
|
93
|
+
.option('-y, --yes', 'skip confirmation prompts')
|
|
94
|
+
.option('-v, --verbose', 'enable verbose output')
|
|
95
|
+
.action(repairCommand);
|
|
96
|
+
|
|
74
97
|
program.parse(process.argv);
|
|
75
98
|
|
|
76
|
-
module.exports = { initCommand, installCommand, startCommand, buildCommand, deployCommand, generateIamCommand, uiCommand, dbSetupCommand };
|
|
99
|
+
module.exports = { initCommand, installCommand, startCommand, buildCommand, deployCommand, generateIamCommand, uiCommand, dbSetupCommand, doctorCommand, repairCommand };
|