@friggframework/devtools 2.0.0-next.47 → 2.0.0-next.48

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.
Files changed (69) hide show
  1. package/frigg-cli/README.md +1290 -0
  2. package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
  3. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +548 -0
  4. package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
  5. package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
  6. package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
  7. package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
  8. package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
  9. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +366 -0
  10. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +304 -0
  11. package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
  12. package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
  13. package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
  14. package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
  15. package/frigg-cli/__tests__/utils/test-setup.js +287 -0
  16. package/frigg-cli/build-command/index.js +66 -0
  17. package/frigg-cli/db-setup-command/index.js +193 -0
  18. package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
  19. package/frigg-cli/deploy-command/index.js +302 -0
  20. package/frigg-cli/doctor-command/index.js +335 -0
  21. package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
  22. package/frigg-cli/generate-command/azure-generator.js +43 -0
  23. package/frigg-cli/generate-command/gcp-generator.js +47 -0
  24. package/frigg-cli/generate-command/index.js +332 -0
  25. package/frigg-cli/generate-command/terraform-generator.js +555 -0
  26. package/frigg-cli/generate-iam-command.js +118 -0
  27. package/frigg-cli/index.js +173 -0
  28. package/frigg-cli/index.test.js +158 -0
  29. package/frigg-cli/init-command/backend-first-handler.js +756 -0
  30. package/frigg-cli/init-command/index.js +93 -0
  31. package/frigg-cli/init-command/template-handler.js +143 -0
  32. package/frigg-cli/install-command/backend-js.js +33 -0
  33. package/frigg-cli/install-command/commit-changes.js +16 -0
  34. package/frigg-cli/install-command/environment-variables.js +127 -0
  35. package/frigg-cli/install-command/environment-variables.test.js +136 -0
  36. package/frigg-cli/install-command/index.js +54 -0
  37. package/frigg-cli/install-command/install-package.js +13 -0
  38. package/frigg-cli/install-command/integration-file.js +30 -0
  39. package/frigg-cli/install-command/logger.js +12 -0
  40. package/frigg-cli/install-command/template.js +90 -0
  41. package/frigg-cli/install-command/validate-package.js +75 -0
  42. package/frigg-cli/jest.config.js +124 -0
  43. package/frigg-cli/package.json +63 -0
  44. package/frigg-cli/repair-command/index.js +564 -0
  45. package/frigg-cli/start-command/index.js +149 -0
  46. package/frigg-cli/start-command/start-command.test.js +297 -0
  47. package/frigg-cli/test/init-command.test.js +180 -0
  48. package/frigg-cli/test/npm-registry.test.js +319 -0
  49. package/frigg-cli/ui-command/index.js +154 -0
  50. package/frigg-cli/utils/app-resolver.js +319 -0
  51. package/frigg-cli/utils/backend-path.js +25 -0
  52. package/frigg-cli/utils/database-validator.js +154 -0
  53. package/frigg-cli/utils/error-messages.js +257 -0
  54. package/frigg-cli/utils/npm-registry.js +167 -0
  55. package/frigg-cli/utils/process-manager.js +199 -0
  56. package/frigg-cli/utils/repo-detection.js +405 -0
  57. package/infrastructure/create-frigg-infrastructure.js +125 -12
  58. package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
  59. package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
  60. package/infrastructure/domains/shared/resource-discovery.js +31 -2
  61. package/infrastructure/domains/shared/utilities/base-definition-factory.js +1 -1
  62. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +109 -5
  63. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +310 -4
  64. package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
  65. package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
  66. package/infrastructure/infrastructure-composer.js +22 -0
  67. package/layers/prisma/.build-complete +3 -0
  68. package/package.json +18 -7
  69. package/management-ui/package-lock.json +0 -16517
@@ -0,0 +1,981 @@
1
+ # Specification: Deploy Dry-Run Mode
2
+
3
+ **Version**: 1.0.0
4
+ **Status**: Draft
5
+ **Created**: 2025-10-28
6
+ **Author**: Claude Code
7
+ **Related**: [SPEC-CLEANUP-COMMAND.md](../../devtools/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md)
8
+
9
+ ## Overview
10
+
11
+ Add a `--dry-run` flag to the `frigg deploy` command to preview deployment changes before executing them. This allows users to validate infrastructure changes, verify CloudFormation template generation, check AWS resource discovery results, and review estimated costs without actually deploying.
12
+
13
+ ## Business Context
14
+
15
+ ### Problem Statement
16
+
17
+ Currently, `frigg deploy` immediately executes deployments without giving users a chance to:
18
+ - Preview what CloudFormation changes will be made
19
+ - Validate generated serverless.yml configuration
20
+ - Review AWS resource discovery results (VPC, subnets, KMS keys)
21
+ - Check for potential issues before deployment
22
+ - Estimate costs of infrastructure changes
23
+ - Verify environment variable configuration
24
+
25
+ This leads to:
26
+ - Unexpected resource creation/modification
27
+ - Wasted time rolling back incorrect deployments
28
+ - Difficulty troubleshooting deployment failures
29
+ - Anxiety about deploying to production
30
+ - Lack of visibility into what the framework is doing
31
+
32
+ ### User Story
33
+
34
+ > As a developer deploying a Frigg application
35
+ > I want to preview deployment changes before executing them
36
+ > So that I can validate the infrastructure configuration and avoid costly mistakes
37
+ > While maintaining confidence in my deployments
38
+
39
+ ### Real-World Scenarios
40
+
41
+ **Scenario 1: First Production Deployment**
42
+ ```bash
43
+ # Developer wants to see what will be created
44
+ frigg deploy --stage prod --dry-run
45
+
46
+ # Output shows:
47
+ # - VPC configuration discovered
48
+ # - 12 Lambda functions to be created
49
+ # - API Gateway endpoints
50
+ # - Estimated monthly cost: $145
51
+ # - Change set preview with resource actions
52
+ ```
53
+
54
+ **Scenario 2: VPC Migration**
55
+ ```bash
56
+ # Changing VPC configuration after AWS resource discovery
57
+ frigg deploy --stage dev --dry-run
58
+
59
+ # Output shows:
60
+ # - VPC change detected (vpc-old → vpc-new)
61
+ # - All Lambda functions will be updated (recreation required)
62
+ # - Potential downtime: 3-5 minutes
63
+ # - Dependencies: 17 Lambda functions, 2 security groups
64
+ ```
65
+
66
+ **Scenario 3: Environment Variable Check**
67
+ ```bash
68
+ # Verify environment variables before deployment
69
+ frigg deploy --stage prod --dry-run
70
+
71
+ # Output shows:
72
+ # - ✓ All 8 required environment variables present
73
+ # - ⚠️ 2 optional variables missing: SENTRY_DSN, NEW_RELIC_KEY
74
+ # - Template preview generated successfully
75
+ ```
76
+
77
+ ## Requirements
78
+
79
+ ### Functional Requirements
80
+
81
+ #### FR-1: Command Interface
82
+
83
+ ```bash
84
+ # Dry-run mode (preview without deploying)
85
+ frigg deploy --dry-run
86
+ frigg deploy --stage prod --dry-run
87
+
88
+ # All existing flags work with dry-run
89
+ frigg deploy --dry-run --verbose
90
+ frigg deploy --dry-run --skip-env-validation
91
+ frigg deploy --dry-run --skip-doctor
92
+
93
+ # JSON output for CI/CD pipelines
94
+ frigg deploy --dry-run --output json
95
+
96
+ # Compare with specific CloudFormation stack
97
+ frigg deploy --dry-run --compare-stack my-app-prod
98
+ ```
99
+
100
+ #### FR-2: Dry-Run Execution Flow
101
+
102
+ **Phase 1: Pre-Flight Checks**
103
+ 1. Load app definition from `index.js`
104
+ 2. Validate app definition structure
105
+ 3. Check for required files (package.json, serverless.yml if exists)
106
+ 4. Extract environment variable configuration
107
+
108
+ **Phase 2: Environment Validation**
109
+ 1. Check for app-defined environment variables
110
+ 2. Report missing optional variables (warnings only)
111
+ 3. Check AWS credentials availability
112
+ 4. Verify AWS account access
113
+
114
+ **Phase 3: AWS Resource Discovery** (if applicable)
115
+ 1. Run AWS resource discovery if VPC/KMS/SSM enabled
116
+ 2. Display discovered resources:
117
+ - VPC ID and CIDR block
118
+ - Private subnet IDs
119
+ - Security group IDs
120
+ - KMS key ARN
121
+ - Route table IDs
122
+ 3. Show any discovery warnings or fallbacks
123
+
124
+ **Phase 4: Template Generation**
125
+ 1. Generate serverless.yml from app definition
126
+ 2. Apply AWS discovery results to template
127
+ 3. Resolve all environment variables
128
+ 4. Display generated template summary:
129
+ - Service name and stage
130
+ - Provider configuration (region, runtime, etc.)
131
+ - Function count and names
132
+ - API Gateway endpoints
133
+ - Custom resources
134
+ - Environment variables (with values masked)
135
+
136
+ **Phase 5: CloudFormation Change Set Preview**
137
+ 1. Create a CloudFormation change set (if stack exists)
138
+ 2. Display planned changes:
139
+ - Resources to be added (green)
140
+ - Resources to be modified (yellow)
141
+ - Resources to be removed (red)
142
+ - Resource replacements (requires recreation)
143
+ 3. Show change set details:
144
+ - Logical ID
145
+ - Physical ID (if exists)
146
+ - Resource type
147
+ - Action (Add, Modify, Remove, Replace)
148
+ - Replacement reason (if applicable)
149
+ 4. Estimate deployment impact:
150
+ - Estimated downtime
151
+ - Functions requiring cold start
152
+ - Breaking changes detected
153
+
154
+ **Phase 6: Cost Estimation** (optional, future enhancement)
155
+ 1. Estimate monthly infrastructure costs
156
+ 2. Show cost breakdown by resource type
157
+ 3. Compare with current stack costs (if exists)
158
+
159
+ **Phase 7: Summary Report**
160
+ 1. Display comprehensive summary:
161
+ - Stack name and region
162
+ - Change summary (X added, Y modified, Z removed)
163
+ - Warnings and recommendations
164
+ - Next steps to execute deployment
165
+ 2. Exit with status code:
166
+ - `0` = Dry-run successful, ready to deploy
167
+ - `1` = Validation errors, cannot deploy
168
+ - `2` = Warnings present, review before deploying
169
+
170
+ #### FR-3: Output Formats
171
+
172
+ **Console Output (Default)**
173
+ ```
174
+ 🔍 Frigg Deploy Dry-Run
175
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
176
+
177
+ 📋 App Configuration
178
+ Service: my-integration
179
+ Stage: production
180
+ Region: us-east-1
181
+ Runtime: nodejs20.x
182
+
183
+ 🔧 Environment Variables
184
+ ✓ 8 required variables present
185
+ ⚠️ 2 optional variables missing:
186
+ - SENTRY_DSN
187
+ - NEW_RELIC_KEY
188
+
189
+ 🌐 AWS Resource Discovery
190
+ ✓ VPC: vpc-12345678 (10.0.0.0/16)
191
+ ✓ Subnets: subnet-11111111, subnet-22222222
192
+ ✓ Security Group: sg-12345678
193
+ ✓ KMS Key: arn:aws:kms:us-east-1:123456789012:key/...
194
+
195
+ 📦 Generated Template Summary
196
+ Functions: 12
197
+ - health (256MB, 30s timeout)
198
+ - user (512MB, 30s timeout)
199
+ - integration-hubspot (1024MB, 60s timeout)
200
+ ...
201
+
202
+ API Endpoints: 15
203
+ - GET /health
204
+ - POST /api/integrations
205
+ - GET /api/integrations/{id}
206
+ ...
207
+
208
+ Custom Resources: 3
209
+ - MongoDB Connection
210
+ - WebSocket Connection Manager
211
+ - S3 Deployment Bucket
212
+
213
+ 🔄 CloudFormation Change Set Preview
214
+ Stack: my-integration-production
215
+ Change Set: frigg-dry-run-20251028-143022
216
+
217
+ Changes:
218
+ ✓ Add (5):
219
+ - HealthLambdaFunction (AWS::Lambda::Function)
220
+ - UserLambdaFunction (AWS::Lambda::Function)
221
+ - ApiGatewayRestApi (AWS::ApiGateway::RestApi)
222
+ - DeploymentBucket (AWS::S3::Bucket)
223
+ - LambdaExecutionRole (AWS::IAM::Role)
224
+
225
+ ⚠️ Modify (7):
226
+ - IntegrationLambdaFunction (AWS::Lambda::Function)
227
+ • VpcConfig.SubnetIds: [subnet-old1, subnet-old2] → [subnet-11111111, subnet-22222222]
228
+ • Environment.Variables.VPC_ID: vpc-old → vpc-12345678
229
+ - HealthLambdaFunction (AWS::Lambda::Function)
230
+ • VpcConfig.SecurityGroupIds: [sg-old] → [sg-12345678]
231
+ ...
232
+
233
+ 🔄 Replace (2):
234
+ - DatabaseSecurityGroup (AWS::EC2::SecurityGroup)
235
+ Reason: VpcId property change requires replacement
236
+ - PrivateSubnet1RouteTableAssociation (AWS::EC2::SubnetRouteTableAssociation)
237
+ Reason: SubnetId property change requires replacement
238
+
239
+ ⚠️ Remove (0)
240
+
241
+ 📊 Deployment Impact
242
+ Estimated Downtime: 2-3 minutes
243
+ Functions Affected: 12/12
244
+ Cold Starts Expected: All functions
245
+ Breaking Changes: None detected
246
+
247
+ 💰 Estimated Monthly Cost
248
+ Lambda Compute: ~$23
249
+ API Gateway: ~$18
250
+ CloudWatch Logs: ~$5
251
+ S3 Storage: ~$2
252
+ Total: ~$48/month
253
+
254
+ ✅ Dry-Run Summary
255
+ ✓ App definition valid
256
+ ✓ AWS resources discovered successfully
257
+ ✓ Template generated successfully
258
+ ✓ Change set created successfully
259
+ ⚠️ 2 optional environment variables missing
260
+ ⚠️ 2 resources will be replaced (potential downtime)
261
+
262
+ Next Steps:
263
+ To execute this deployment, run:
264
+ frigg deploy --stage production
265
+
266
+ To skip environment validation, run:
267
+ frigg deploy --stage production --skip-env-validation
268
+ ```
269
+
270
+ **JSON Output (for CI/CD)**
271
+ ```json
272
+ {
273
+ "dryRun": true,
274
+ "timestamp": "2025-10-28T14:30:22Z",
275
+ "app": {
276
+ "service": "my-integration",
277
+ "stage": "production",
278
+ "region": "us-east-1",
279
+ "runtime": "nodejs20.x"
280
+ },
281
+ "environment": {
282
+ "required": {
283
+ "present": ["AWS_REGION", "STAGE", "DB_URI", "ENCRYPTION_KEY", "JWT_SECRET", "API_KEY", "WEBHOOK_SECRET", "LOG_LEVEL"],
284
+ "missing": []
285
+ },
286
+ "optional": {
287
+ "present": [],
288
+ "missing": ["SENTRY_DSN", "NEW_RELIC_KEY"]
289
+ }
290
+ },
291
+ "discovery": {
292
+ "enabled": true,
293
+ "results": {
294
+ "vpc": {
295
+ "id": "vpc-12345678",
296
+ "cidr": "10.0.0.0/16"
297
+ },
298
+ "subnets": ["subnet-11111111", "subnet-22222222"],
299
+ "securityGroups": ["sg-12345678"],
300
+ "kmsKey": "arn:aws:kms:us-east-1:123456789012:key/..."
301
+ }
302
+ },
303
+ "template": {
304
+ "service": "my-integration-production",
305
+ "functions": {
306
+ "count": 12,
307
+ "names": ["health", "user", "integration-hubspot"]
308
+ },
309
+ "endpoints": {
310
+ "count": 15,
311
+ "methods": ["GET /health", "POST /api/integrations"]
312
+ }
313
+ },
314
+ "changeSet": {
315
+ "stackName": "my-integration-production",
316
+ "changeSetId": "arn:aws:cloudformation:...",
317
+ "status": "CREATE_COMPLETE",
318
+ "changes": [
319
+ {
320
+ "action": "Add",
321
+ "logicalId": "HealthLambdaFunction",
322
+ "resourceType": "AWS::Lambda::Function",
323
+ "replacement": null
324
+ },
325
+ {
326
+ "action": "Modify",
327
+ "logicalId": "IntegrationLambdaFunction",
328
+ "resourceType": "AWS::Lambda::Function",
329
+ "replacement": null,
330
+ "details": [
331
+ {
332
+ "target": "Properties",
333
+ "attribute": "VpcConfig.SubnetIds",
334
+ "changeSource": "DirectModification"
335
+ }
336
+ ]
337
+ }
338
+ ],
339
+ "summary": {
340
+ "add": 5,
341
+ "modify": 7,
342
+ "remove": 0,
343
+ "replace": 2
344
+ }
345
+ },
346
+ "impact": {
347
+ "downtime": "2-3 minutes",
348
+ "functionsAffected": 12,
349
+ "coldStarts": true,
350
+ "breakingChanges": false
351
+ },
352
+ "cost": {
353
+ "lambda": 23,
354
+ "apiGateway": 18,
355
+ "cloudWatch": 5,
356
+ "s3": 2,
357
+ "total": 48,
358
+ "currency": "USD",
359
+ "period": "monthly"
360
+ },
361
+ "validation": {
362
+ "success": true,
363
+ "errors": [],
364
+ "warnings": [
365
+ "2 optional environment variables missing",
366
+ "2 resources will be replaced"
367
+ ]
368
+ },
369
+ "exitCode": 2
370
+ }
371
+ ```
372
+
373
+ #### FR-4: Change Set Management
374
+
375
+ **Change Set Creation**:
376
+ ```javascript
377
+ // Create a temporary change set for preview
378
+ const changeSetName = `frigg-dry-run-${Date.now()}`;
379
+ const changeSet = await cloudFormation.createChangeSet({
380
+ StackName: stackName,
381
+ ChangeSetName: changeSetName,
382
+ TemplateBody: generatedTemplate,
383
+ ChangeSetType: stackExists ? 'UPDATE' : 'CREATE',
384
+ Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
385
+ Parameters: parameters,
386
+ Tags: tags,
387
+ });
388
+
389
+ // Wait for change set creation
390
+ await cloudFormation.waitFor('changeSetCreateComplete', {
391
+ StackName: stackName,
392
+ ChangeSetName: changeSetName,
393
+ });
394
+
395
+ // Retrieve change set details
396
+ const changeSetDetails = await cloudFormation.describeChangeSet({
397
+ StackName: stackName,
398
+ ChangeSetName: changeSetName,
399
+ });
400
+
401
+ // Clean up change set after preview
402
+ await cloudFormation.deleteChangeSet({
403
+ StackName: stackName,
404
+ ChangeSetName: changeSetName,
405
+ });
406
+ ```
407
+
408
+ **Change Set Analysis**:
409
+ ```javascript
410
+ function analyzeChangeSet(changeSet) {
411
+ const summary = {
412
+ add: 0,
413
+ modify: 0,
414
+ remove: 0,
415
+ replace: 0,
416
+ };
417
+
418
+ const criticalChanges = [];
419
+ const warnings = [];
420
+
421
+ for (const change of changeSet.Changes) {
422
+ const { Action, ResourceChange } = change;
423
+
424
+ // Count actions
425
+ if (Action === 'Add') summary.add++;
426
+ if (Action === 'Modify') summary.modify++;
427
+ if (Action === 'Remove') summary.remove++;
428
+ if (ResourceChange?.Replacement === 'True') summary.replace++;
429
+
430
+ // Detect critical changes
431
+ if (ResourceChange?.Replacement === 'True') {
432
+ criticalChanges.push({
433
+ logicalId: ResourceChange.LogicalResourceId,
434
+ resourceType: ResourceChange.ResourceType,
435
+ reason: 'Requires replacement',
436
+ });
437
+ }
438
+
439
+ // Detect VPC changes (high impact)
440
+ if (ResourceChange?.ResourceType === 'AWS::Lambda::Function') {
441
+ const vpcChange = ResourceChange.Details?.find(
442
+ (d) => d.Target?.Attribute === 'VpcConfig'
443
+ );
444
+ if (vpcChange) {
445
+ warnings.push({
446
+ logicalId: ResourceChange.LogicalResourceId,
447
+ message: 'VPC configuration change - function will be recreated',
448
+ });
449
+ }
450
+ }
451
+ }
452
+
453
+ return { summary, criticalChanges, warnings };
454
+ }
455
+ ```
456
+
457
+ #### FR-5: Integration with Existing Flags
458
+
459
+ All existing `frigg deploy` flags work with `--dry-run`:
460
+
461
+ **Environment Validation**:
462
+ ```bash
463
+ # Skip environment validation in dry-run
464
+ frigg deploy --dry-run --skip-env-validation
465
+ ```
466
+
467
+ **Health Check Skip**:
468
+ ```bash
469
+ # Skip post-deployment health check (not applicable in dry-run)
470
+ frigg deploy --dry-run --skip-doctor
471
+ ```
472
+
473
+ **Verbose Output**:
474
+ ```bash
475
+ # Show detailed logs during dry-run
476
+ frigg deploy --dry-run --verbose
477
+ ```
478
+
479
+ **Force Deployment**:
480
+ ```bash
481
+ # Force flag ignored in dry-run (cannot force a preview)
482
+ frigg deploy --dry-run --force
483
+ ```
484
+
485
+ ### Non-Functional Requirements
486
+
487
+ #### NFR-1: Performance
488
+
489
+ - Dry-run execution must complete within 30 seconds for small stacks (<20 resources)
490
+ - Dry-run execution must complete within 60 seconds for large stacks (50+ resources)
491
+ - AWS resource discovery should be cached for 5 minutes to speed up repeated dry-runs
492
+ - Change set creation timeout: 5 minutes (CloudFormation standard)
493
+
494
+ #### NFR-2: Safety
495
+
496
+ - **CRITICAL**: Dry-run must NEVER modify any AWS resources
497
+ - Change sets created for preview must be automatically deleted after display
498
+ - No side effects from running dry-run multiple times
499
+ - Failed dry-run must not leave CloudFormation in inconsistent state
500
+
501
+ #### NFR-3: Usability
502
+
503
+ - Output must be clear and actionable
504
+ - Warnings must be visually distinct from errors
505
+ - Critical changes (replacements) must be highlighted
506
+ - Next steps must be provided at end of output
507
+ - JSON output must be parseable by standard JSON tools
508
+
509
+ #### NFR-4: Compatibility
510
+
511
+ - Must work with all existing Frigg app definitions
512
+ - Must support both create and update operations
513
+ - Must work with AWS discovery enabled/disabled
514
+ - Must integrate with existing serverless.yml generation
515
+
516
+ ## Implementation Design
517
+
518
+ ### Architecture
519
+
520
+ ```
521
+ ┌─────────────────────────────────────────────────────────────┐
522
+ │ frigg deploy --dry-run │
523
+ └────────────────────┬────────────────────────────────────────┘
524
+
525
+
526
+ ┌─────────────────────────────────────────────────────────────┐
527
+ │ DryRunOrchestrator │
528
+ │ - Coordinates dry-run workflow │
529
+ │ - Manages phase execution │
530
+ │ - Collects results │
531
+ └────────────┬────────────────────────────────────────────────┘
532
+
533
+ ├──▶ PreFlightChecker
534
+ │ - Load app definition
535
+ │ - Validate structure
536
+ │ - Check required files
537
+
538
+ ├──▶ EnvironmentValidator
539
+ │ - Check environment variables
540
+ │ - Validate AWS credentials
541
+ │ - Verify account access
542
+
543
+ ├──▶ AWSDiscoveryRunner (if enabled)
544
+ │ - Discover VPC resources
545
+ │ - Find KMS keys
546
+ │ - Locate subnets/security groups
547
+
548
+ ├──▶ TemplateGenerator
549
+ │ - Generate serverless.yml
550
+ │ - Apply discovery results
551
+ │ - Resolve environment variables
552
+
553
+ ├──▶ ChangeSetCreator
554
+ │ - Create CloudFormation change set
555
+ │ - Wait for completion
556
+ │ - Retrieve change details
557
+ │ - Analyze changes
558
+ │ - Delete change set (cleanup)
559
+
560
+ ├──▶ CostEstimator (future)
561
+ │ - Estimate resource costs
562
+ │ - Compare with current costs
563
+ │ - Show cost breakdown
564
+
565
+ └──▶ DryRunReporter
566
+ - Format output
567
+ - Display summary
568
+ - Provide next steps
569
+ - Return exit code
570
+ ```
571
+
572
+ ### File Structure
573
+
574
+ ```
575
+ packages/frigg-cli/deploy-command/
576
+ ├── index.js # Main deploy command (modified)
577
+ ├── dry-run/
578
+ │ ├── orchestrator.js # DryRunOrchestrator
579
+ │ ├── pre-flight-checker.js # PreFlightChecker
580
+ │ ├── environment-validator.js # EnvironmentValidator
581
+ │ ├── template-generator.js # TemplateGenerator (uses existing)
582
+ │ ├── change-set-creator.js # ChangeSetCreator
583
+ │ ├── change-set-analyzer.js # ChangeSetAnalyzer
584
+ │ ├── dry-run-reporter.js # DryRunReporter
585
+ │ └── __tests__/
586
+ │ ├── orchestrator.test.js
587
+ │ ├── change-set-creator.test.js
588
+ │ └── dry-run-reporter.test.js
589
+ └── SPEC-DEPLOY-DRY-RUN.md # This specification
590
+ ```
591
+
592
+ ### Key Classes
593
+
594
+ #### DryRunOrchestrator
595
+
596
+ ```javascript
597
+ class DryRunOrchestrator {
598
+ constructor({ appDefinition, options }) {
599
+ this.appDefinition = appDefinition;
600
+ this.options = options;
601
+ this.results = {};
602
+ }
603
+
604
+ async execute() {
605
+ // Phase 1: Pre-flight checks
606
+ this.results.preFlight = await this.runPreFlightChecks();
607
+
608
+ // Phase 2: Environment validation
609
+ this.results.environment = await this.validateEnvironment();
610
+
611
+ // Phase 3: AWS discovery (if enabled)
612
+ if (this.shouldRunDiscovery()) {
613
+ this.results.discovery = await this.runDiscovery();
614
+ }
615
+
616
+ // Phase 4: Template generation
617
+ this.results.template = await this.generateTemplate();
618
+
619
+ // Phase 5: Change set preview
620
+ this.results.changeSet = await this.createChangeSetPreview();
621
+
622
+ // Phase 6: Cost estimation (future)
623
+ // this.results.cost = await this.estimateCosts();
624
+
625
+ // Phase 7: Report
626
+ return this.generateReport();
627
+ }
628
+
629
+ shouldRunDiscovery() {
630
+ return (
631
+ this.appDefinition.vpc?.enable === true ||
632
+ this.appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true ||
633
+ this.appDefinition.ssm?.enable === true
634
+ );
635
+ }
636
+ }
637
+ ```
638
+
639
+ #### ChangeSetCreator
640
+
641
+ ```javascript
642
+ class ChangeSetCreator {
643
+ constructor({ cloudFormation, stackName, region }) {
644
+ this.cloudFormation = cloudFormation;
645
+ this.stackName = stackName;
646
+ this.region = region;
647
+ }
648
+
649
+ async createPreview(template, parameters, tags) {
650
+ // Check if stack exists
651
+ const stackExists = await this.checkStackExists();
652
+
653
+ // Create change set
654
+ const changeSetName = `frigg-dry-run-${Date.now()}`;
655
+ const changeSet = await this.cloudFormation.createChangeSet({
656
+ StackName: this.stackName,
657
+ ChangeSetName: changeSetName,
658
+ TemplateBody: JSON.stringify(template),
659
+ ChangeSetType: stackExists ? 'UPDATE' : 'CREATE',
660
+ Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
661
+ Parameters: parameters,
662
+ Tags: tags,
663
+ });
664
+
665
+ // Wait for change set creation
666
+ await this.waitForChangeSet(changeSetName);
667
+
668
+ // Get change set details
669
+ const details = await this.getChangeSetDetails(changeSetName);
670
+
671
+ // Clean up
672
+ await this.deleteChangeSet(changeSetName);
673
+
674
+ return details;
675
+ }
676
+
677
+ async checkStackExists() {
678
+ try {
679
+ await this.cloudFormation.describeStacks({
680
+ StackName: this.stackName,
681
+ });
682
+ return true;
683
+ } catch (error) {
684
+ if (error.code === 'ValidationError') {
685
+ return false;
686
+ }
687
+ throw error;
688
+ }
689
+ }
690
+
691
+ async waitForChangeSet(changeSetName, maxWaitTime = 300000) {
692
+ const startTime = Date.now();
693
+
694
+ while (Date.now() - startTime < maxWaitTime) {
695
+ const { Status, StatusReason } = await this.cloudFormation.describeChangeSet({
696
+ StackName: this.stackName,
697
+ ChangeSetName: changeSetName,
698
+ });
699
+
700
+ if (Status === 'CREATE_COMPLETE') {
701
+ return;
702
+ }
703
+
704
+ if (Status === 'FAILED') {
705
+ // "No updates are to be performed" is expected for no-op changes
706
+ if (StatusReason?.includes('No updates are to be performed')) {
707
+ return;
708
+ }
709
+ throw new Error(`Change set creation failed: ${StatusReason}`);
710
+ }
711
+
712
+ await new Promise((resolve) => setTimeout(resolve, 2000));
713
+ }
714
+
715
+ throw new Error('Change set creation timeout');
716
+ }
717
+
718
+ async deleteChangeSet(changeSetName) {
719
+ try {
720
+ await this.cloudFormation.deleteChangeSet({
721
+ StackName: this.stackName,
722
+ ChangeSetName: changeSetName,
723
+ });
724
+ } catch (error) {
725
+ console.warn(`Warning: Could not delete change set ${changeSetName}:`, error.message);
726
+ }
727
+ }
728
+ }
729
+ ```
730
+
731
+ ### Integration with Existing Code
732
+
733
+ **Modified: `packages/frigg-cli/deploy-command/index.js`**
734
+
735
+ ```javascript
736
+ async function deployCommand(options) {
737
+ // Parse command-line options
738
+ const { stage, dryRun, verbose, skipEnvValidation, skipDoctor, output } = options;
739
+
740
+ // Load app definition
741
+ const appDefinition = loadAppDefinition();
742
+ if (!appDefinition) {
743
+ console.error('❌ Could not load app definition from index.js');
744
+ process.exit(1);
745
+ }
746
+
747
+ // DRY-RUN MODE
748
+ if (dryRun) {
749
+ console.log('🔍 Running deployment dry-run...\n');
750
+
751
+ const orchestrator = new DryRunOrchestrator({
752
+ appDefinition,
753
+ options: { stage, verbose, skipEnvValidation, output },
754
+ });
755
+
756
+ const results = await orchestrator.execute();
757
+
758
+ // Display results
759
+ const reporter = new DryRunReporter({ format: output || 'console' });
760
+ reporter.display(results);
761
+
762
+ // Exit with appropriate code
763
+ process.exit(results.exitCode);
764
+ }
765
+
766
+ // NORMAL DEPLOYMENT (existing code)
767
+ // ... existing deployment logic ...
768
+ }
769
+ ```
770
+
771
+ ## Testing Strategy
772
+
773
+ ### Unit Tests
774
+
775
+ **Change Set Creator Tests**:
776
+ ```javascript
777
+ describe('ChangeSetCreator', () => {
778
+ it('should create change set for new stack', async () => {
779
+ const creator = new ChangeSetCreator({
780
+ cloudFormation: mockCF,
781
+ stackName: 'test-stack',
782
+ region: 'us-east-1',
783
+ });
784
+
785
+ mockCF.describeStacks.mockRejectedValueOnce(
786
+ new Error('Stack does not exist')
787
+ );
788
+ mockCF.createChangeSet.mockResolvedValueOnce({ Id: 'cs-123' });
789
+ mockCF.describeChangeSet.mockResolvedValueOnce({
790
+ Status: 'CREATE_COMPLETE',
791
+ Changes: [],
792
+ });
793
+
794
+ const result = await creator.createPreview(template, [], []);
795
+
796
+ expect(result.Changes).toBeDefined();
797
+ expect(mockCF.createChangeSet).toHaveBeenCalledWith(
798
+ expect.objectContaining({
799
+ ChangeSetType: 'CREATE',
800
+ })
801
+ );
802
+ });
803
+
804
+ it('should clean up change set after preview', async () => {
805
+ const creator = new ChangeSetCreator({
806
+ cloudFormation: mockCF,
807
+ stackName: 'test-stack',
808
+ region: 'us-east-1',
809
+ });
810
+
811
+ await creator.createPreview(template, [], []);
812
+
813
+ expect(mockCF.deleteChangeSet).toHaveBeenCalled();
814
+ });
815
+ });
816
+ ```
817
+
818
+ **Dry-Run Orchestrator Tests**:
819
+ ```javascript
820
+ describe('DryRunOrchestrator', () => {
821
+ it('should execute all phases in order', async () => {
822
+ const orchestrator = new DryRunOrchestrator({
823
+ appDefinition: mockAppDef,
824
+ options: { stage: 'dev' },
825
+ });
826
+
827
+ const results = await orchestrator.execute();
828
+
829
+ expect(results.preFlight).toBeDefined();
830
+ expect(results.environment).toBeDefined();
831
+ expect(results.template).toBeDefined();
832
+ expect(results.changeSet).toBeDefined();
833
+ });
834
+
835
+ it('should skip discovery when not enabled', async () => {
836
+ const orchestrator = new DryRunOrchestrator({
837
+ appDefinition: { vpc: { enable: false } },
838
+ options: { stage: 'dev' },
839
+ });
840
+
841
+ const results = await orchestrator.execute();
842
+
843
+ expect(results.discovery).toBeUndefined();
844
+ });
845
+ });
846
+ ```
847
+
848
+ ### Integration Tests
849
+
850
+ **End-to-End Dry-Run Test**:
851
+ ```javascript
852
+ describe('frigg deploy --dry-run', () => {
853
+ it('should preview deployment without modifying resources', async () => {
854
+ // Create test app definition
855
+ const appDef = {
856
+ name: 'test-app',
857
+ provider: 'aws',
858
+ vpc: { enable: true },
859
+ };
860
+
861
+ // Run dry-run
862
+ const result = await runCommand('frigg deploy --dry-run --stage dev');
863
+
864
+ expect(result.exitCode).toBe(0);
865
+ expect(result.stdout).toContain('Dry-Run Summary');
866
+ expect(result.stdout).toContain('Change Set Preview');
867
+
868
+ // Verify no resources were created
869
+ const stacks = await cloudFormation.listStacks();
870
+ expect(stacks).not.toContain('test-app-dev');
871
+ });
872
+ });
873
+ ```
874
+
875
+ ## Comparison with SPEC-CLEANUP-COMMAND
876
+
877
+ This spec follows similar patterns to [SPEC-CLEANUP-COMMAND.md](../../devtools/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md):
878
+
879
+ ### Similarities
880
+
881
+ 1. **Dry-Run First**: Both commands default to preview mode
882
+ - `frigg cleanup --orphaned` → dry-run by default
883
+ - `frigg deploy --dry-run` → explicit dry-run flag
884
+
885
+ 2. **Safety Features**:
886
+ - Both create preview without modifying resources
887
+ - Both require explicit execution flag (`--execute` for cleanup)
888
+ - Both provide detailed change summaries
889
+
890
+ 3. **Output Formats**:
891
+ - Console output (default, human-readable)
892
+ - JSON output (`--output json` for automation)
893
+
894
+ 4. **Change Analysis**:
895
+ - Both analyze resource changes before execution
896
+ - Both detect critical changes (replacements)
897
+ - Both provide warnings and recommendations
898
+
899
+ 5. **Cleanup After Preview**:
900
+ - Cleanup command: No cleanup needed (just listing)
901
+ - Deploy command: Delete change set after preview
902
+
903
+ ### Differences
904
+
905
+ | Feature | Deploy Dry-Run | Cleanup Orphaned |
906
+ |---------|----------------|------------------|
907
+ | **Default Behavior** | Requires `--dry-run` flag | Dry-run by default |
908
+ | **Execution** | Run deploy without flag | Requires `--execute` |
909
+ | **AWS Modification** | Creates/modifies resources | Deletes resources |
910
+ | **Change Set** | Creates temporary change set | N/A (uses describe APIs) |
911
+ | **Cost Impact** | Shows future costs | Shows cost savings |
912
+ | **Dependencies** | Not checked (CloudFormation handles) | Explicitly checked before delete |
913
+ | **Confirmation** | Not needed (explicit deploy) | Required before deletion |
914
+
915
+ ## Success Criteria
916
+
917
+ 1. **Functional**:
918
+ - ✅ Dry-run completes without errors for valid app definitions
919
+ - ✅ All phases execute in correct order
920
+ - ✅ Change set preview displays accurately
921
+ - ✅ No AWS resources modified during dry-run
922
+ - ✅ Change sets cleaned up after preview
923
+
924
+ 2. **Usability**:
925
+ - ✅ Output is clear and actionable
926
+ - ✅ Warnings visually distinct from errors
927
+ - ✅ Next steps provided at end of output
928
+ - ✅ JSON output parseable by standard tools
929
+
930
+ 3. **Performance**:
931
+ - ✅ Dry-run completes within 30s for small stacks
932
+ - ✅ Dry-run completes within 60s for large stacks
933
+
934
+ 4. **Safety**:
935
+ - ✅ Zero side effects from running dry-run
936
+ - ✅ Failed dry-run doesn't leave inconsistent state
937
+ - ✅ Change sets always cleaned up (even on error)
938
+
939
+ ## Future Enhancements
940
+
941
+ ### Phase 2: Cost Estimation
942
+ - Integrate with AWS Cost Explorer API
943
+ - Show estimated monthly costs by resource
944
+ - Compare with current stack costs
945
+ - Alert on significant cost increases
946
+
947
+ ### Phase 3: Drift Detection
948
+ - Compare deployed stack with app definition
949
+ - Detect manual changes in AWS console
950
+ - Suggest `frigg repair` for drift correction
951
+
952
+ ### Phase 4: Multi-Stack Preview
953
+ - Preview changes across multiple stacks
954
+ - Show cross-stack dependencies
955
+ - Validate stack outputs/imports
956
+
957
+ ### Phase 5: Interactive Mode
958
+ - Prompt user to proceed with deployment
959
+ - Allow selective deployment of changes
960
+ - Confirm critical changes before execution
961
+
962
+ ## References
963
+
964
+ - **Related Spec**: [SPEC-CLEANUP-COMMAND.md](../../devtools/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md)
965
+ - **CloudFormation Change Sets**: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-changesets.html
966
+ - **Serverless Framework**: https://www.serverless.com/
967
+ - **AWS SDK v3**: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/
968
+
969
+ ## Approval
970
+
971
+ | Role | Name | Date | Signature |
972
+ |------|------|------|-----------|
973
+ | Product Owner | TBD | | |
974
+ | Tech Lead | TBD | | |
975
+ | Developer | Claude Code | 2025-10-28 | ✓ |
976
+
977
+ ## Change Log
978
+
979
+ | Version | Date | Author | Changes |
980
+ |---------|------|--------|---------|
981
+ | 1.0.0 | 2025-10-28 | Claude Code | Initial specification |