@friggframework/devtools 2.0.0-next.44 → 2.0.0-next.46
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/infrastructure/ARCHITECTURE.md +487 -0
- package/infrastructure/HEALTH.md +468 -0
- package/infrastructure/README.md +51 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/__tests__/template-generation.test.js +687 -0
- package/infrastructure/create-frigg-infrastructure.js +1 -1
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
- package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
- package/infrastructure/domains/database/aurora-builder.js +809 -0
- package/infrastructure/domains/database/aurora-builder.test.js +950 -0
- package/infrastructure/domains/database/aurora-discovery.js +87 -0
- package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
- package/infrastructure/domains/database/aurora-resolver.js +210 -0
- package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
- package/infrastructure/domains/database/migration-builder.js +633 -0
- package/infrastructure/domains/database/migration-builder.test.js +294 -0
- package/infrastructure/domains/database/migration-resolver.js +163 -0
- package/infrastructure/domains/database/migration-resolver.test.js +337 -0
- package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
- package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
- package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
- package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
- package/infrastructure/domains/health/application/ports/index.js +26 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
- package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -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 +535 -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 +213 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
- package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
- package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
- package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
- package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
- package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
- package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
- package/infrastructure/domains/health/domain/entities/issue.js +299 -0
- package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
- package/infrastructure/domains/health/domain/entities/resource.js +159 -0
- package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
- package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
- package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
- package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
- package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
- package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
- package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
- package/infrastructure/domains/integration/integration-builder.js +397 -0
- package/infrastructure/domains/integration/integration-builder.test.js +593 -0
- package/infrastructure/domains/integration/integration-resolver.js +170 -0
- package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
- package/infrastructure/domains/integration/websocket-builder.js +69 -0
- package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
- package/infrastructure/domains/networking/vpc-builder.js +1829 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
- package/infrastructure/domains/networking/vpc-discovery.js +177 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
- package/infrastructure/domains/networking/vpc-resolver.js +324 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
- package/infrastructure/domains/parameters/ssm-builder.js +79 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
- package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
- package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
- package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
- package/infrastructure/domains/security/kms-builder.js +366 -0
- package/infrastructure/domains/security/kms-builder.test.js +374 -0
- package/infrastructure/domains/security/kms-discovery.js +80 -0
- package/infrastructure/domains/security/kms-discovery.test.js +177 -0
- package/infrastructure/domains/security/kms-resolver.js +96 -0
- package/infrastructure/domains/security/kms-resolver.test.js +216 -0
- package/infrastructure/domains/shared/base-builder.js +112 -0
- package/infrastructure/domains/shared/base-resolver.js +186 -0
- package/infrastructure/domains/shared/base-resolver.test.js +305 -0
- package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
- package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
- package/infrastructure/domains/shared/environment-builder.js +119 -0
- package/infrastructure/domains/shared/environment-builder.test.js +247 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -0
- package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
- package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
- package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
- package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
- package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
- package/infrastructure/domains/shared/resource-discovery.js +192 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
- package/infrastructure/domains/shared/types/app-definition.js +205 -0
- package/infrastructure/domains/shared/types/discovery-result.js +106 -0
- package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
- package/infrastructure/domains/shared/types/index.js +46 -0
- package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
- package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +380 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
- package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +87 -0
- package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
- package/infrastructure/scripts/build-prisma-layer.js +553 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
- package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
- package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
- package/layers/prisma/nodejs/package.json +8 -0
- package/management-ui/server/utils/cliIntegration.js +1 -1
- package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
- package/package.json +11 -11
- package/frigg-cli/.eslintrc.js +0 -141
- package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
- package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
- package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
- package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
- package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
- package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
- package/frigg-cli/__tests__/utils/test-setup.js +0 -287
- package/frigg-cli/build-command/index.js +0 -65
- package/frigg-cli/db-setup-command/index.js +0 -193
- package/frigg-cli/deploy-command/index.js +0 -175
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
- package/frigg-cli/generate-command/azure-generator.js +0 -43
- package/frigg-cli/generate-command/gcp-generator.js +0 -47
- package/frigg-cli/generate-command/index.js +0 -332
- package/frigg-cli/generate-command/terraform-generator.js +0 -555
- package/frigg-cli/generate-iam-command.js +0 -118
- package/frigg-cli/index.js +0 -75
- package/frigg-cli/index.test.js +0 -158
- package/frigg-cli/init-command/backend-first-handler.js +0 -756
- package/frigg-cli/init-command/index.js +0 -93
- package/frigg-cli/init-command/template-handler.js +0 -143
- package/frigg-cli/install-command/backend-js.js +0 -33
- package/frigg-cli/install-command/commit-changes.js +0 -16
- package/frigg-cli/install-command/environment-variables.js +0 -127
- package/frigg-cli/install-command/environment-variables.test.js +0 -136
- package/frigg-cli/install-command/index.js +0 -54
- package/frigg-cli/install-command/install-package.js +0 -13
- package/frigg-cli/install-command/integration-file.js +0 -30
- package/frigg-cli/install-command/logger.js +0 -12
- package/frigg-cli/install-command/template.js +0 -90
- package/frigg-cli/install-command/validate-package.js +0 -75
- package/frigg-cli/jest.config.js +0 -124
- package/frigg-cli/package.json +0 -54
- package/frigg-cli/start-command/index.js +0 -149
- package/frigg-cli/start-command/start-command.test.js +0 -297
- package/frigg-cli/test/init-command.test.js +0 -180
- package/frigg-cli/test/npm-registry.test.js +0 -319
- package/frigg-cli/ui-command/index.js +0 -154
- package/frigg-cli/utils/app-resolver.js +0 -319
- package/frigg-cli/utils/backend-path.js +0 -25
- package/frigg-cli/utils/database-validator.js +0 -161
- package/frigg-cli/utils/error-messages.js +0 -257
- package/frigg-cli/utils/npm-registry.js +0 -167
- package/frigg-cli/utils/prisma-runner.js +0 -280
- package/frigg-cli/utils/process-manager.js +0 -199
- package/frigg-cli/utils/repo-detection.js +0 -405
- package/infrastructure/aws-discovery.js +0 -1176
- package/infrastructure/aws-discovery.test.js +0 -1220
- package/infrastructure/serverless-template.js +0 -2074
- /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
- /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
- /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
- /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
- /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
- /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
- /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for BuilderOrchestrator
|
|
3
|
+
*
|
|
4
|
+
* Tests orchestration, dependency resolution, and parallel execution
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { BuilderOrchestrator } = require('./builder-orchestrator');
|
|
8
|
+
const { InfrastructureBuilder, ValidationResult } = require('./base-builder');
|
|
9
|
+
|
|
10
|
+
// Mock builders for testing
|
|
11
|
+
class MockBuilderA extends InfrastructureBuilder {
|
|
12
|
+
getName() { return 'MockBuilderA'; }
|
|
13
|
+
shouldExecute(appDef) { return appDef.featureA === true; }
|
|
14
|
+
validate(appDef) { return new ValidationResult(); }
|
|
15
|
+
async build(appDef, discovered) {
|
|
16
|
+
return {
|
|
17
|
+
resources: { ResourceA: { Type: 'Test' } },
|
|
18
|
+
iamStatements: [{ Effect: 'Allow', Action: 'test:A' }],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class MockBuilderB extends InfrastructureBuilder {
|
|
24
|
+
getName() { return 'MockBuilderB'; }
|
|
25
|
+
shouldExecute(appDef) { return appDef.featureB === true; }
|
|
26
|
+
validate(appDef) { return new ValidationResult(); }
|
|
27
|
+
getDependencies() { return ['MockBuilderA']; } // Depends on A
|
|
28
|
+
async build(appDef, discovered) {
|
|
29
|
+
return {
|
|
30
|
+
resources: { ResourceB: { Type: 'Test' } },
|
|
31
|
+
environment: { VAR_B: 'value-b' },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class MockBuilderC extends InfrastructureBuilder {
|
|
37
|
+
getName() { return 'MockBuilderC'; }
|
|
38
|
+
shouldExecute() { return false; } // Never executes
|
|
39
|
+
validate() { return new ValidationResult(); }
|
|
40
|
+
async build() {
|
|
41
|
+
throw new Error('Should not be called');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
jest.mock('./resource-discovery', () => ({
|
|
46
|
+
gatherDiscoveredResources: jest.fn().mockResolvedValue({ discovered: true }),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
jest.mock('./environment-builder', () => ({
|
|
50
|
+
getAppEnvironmentVars: jest.fn().mockReturnValue({ ENV_VAR: 'value' }),
|
|
51
|
+
buildEnvironment: jest.fn().mockReturnValue({ ENV_VAR: 'value' }),
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
describe('BuilderOrchestrator', () => {
|
|
55
|
+
let orchestrator;
|
|
56
|
+
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
jest.clearAllMocks();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('registerBuilder()', () => {
|
|
62
|
+
it('should register builders', () => {
|
|
63
|
+
orchestrator = new BuilderOrchestrator();
|
|
64
|
+
const builder = new MockBuilderA();
|
|
65
|
+
|
|
66
|
+
orchestrator.registerBuilder(builder);
|
|
67
|
+
|
|
68
|
+
expect(orchestrator.builders.has('MockBuilderA')).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should register multiple builders via constructor', () => {
|
|
72
|
+
orchestrator = new BuilderOrchestrator([
|
|
73
|
+
new MockBuilderA(),
|
|
74
|
+
new MockBuilderB(),
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
expect(orchestrator.builders.size).toBe(2);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('validateAll()', () => {
|
|
82
|
+
it('should validate all applicable builders', async () => {
|
|
83
|
+
orchestrator = new BuilderOrchestrator([
|
|
84
|
+
new MockBuilderA(),
|
|
85
|
+
new MockBuilderB(),
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
const appDef = { featureA: true, featureB: true };
|
|
89
|
+
|
|
90
|
+
await expect(orchestrator.validateAll(appDef)).resolves.toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should skip builders that shouldNotExecute', async () => {
|
|
94
|
+
orchestrator = new BuilderOrchestrator([
|
|
95
|
+
new MockBuilderA(),
|
|
96
|
+
new MockBuilderC(), // Should not execute
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
const appDef = { featureA: true };
|
|
100
|
+
|
|
101
|
+
const results = await orchestrator.validateAll(appDef);
|
|
102
|
+
|
|
103
|
+
// Should only validate MockBuilderA
|
|
104
|
+
expect(results).toHaveLength(1);
|
|
105
|
+
expect(results[0].builder).toBe('MockBuilderA');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should throw error if validation fails', async () => {
|
|
109
|
+
class FailingBuilder extends InfrastructureBuilder {
|
|
110
|
+
getName() { return 'FailingBuilder'; }
|
|
111
|
+
shouldExecute() { return true; }
|
|
112
|
+
validate() {
|
|
113
|
+
const result = new ValidationResult();
|
|
114
|
+
result.addError('Test error');
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
orchestrator = new BuilderOrchestrator([new FailingBuilder()]);
|
|
120
|
+
|
|
121
|
+
await expect(orchestrator.validateAll({})).rejects.toThrow('Infrastructure validation failed');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('resolveBuildOrder()', () => {
|
|
126
|
+
it('should resolve dependencies correctly', () => {
|
|
127
|
+
orchestrator = new BuilderOrchestrator([
|
|
128
|
+
new MockBuilderB(), // Depends on A
|
|
129
|
+
new MockBuilderA(), // No dependencies
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
const appDef = { featureA: true, featureB: true };
|
|
133
|
+
const order = orchestrator.resolveBuildOrder(appDef);
|
|
134
|
+
|
|
135
|
+
// A should come before B
|
|
136
|
+
expect(order).toEqual(['MockBuilderA', 'MockBuilderB']);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should handle builders with no dependencies', () => {
|
|
140
|
+
orchestrator = new BuilderOrchestrator([
|
|
141
|
+
new MockBuilderA(),
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
const appDef = { featureA: true };
|
|
145
|
+
const order = orchestrator.resolveBuildOrder(appDef);
|
|
146
|
+
|
|
147
|
+
expect(order).toEqual(['MockBuilderA']);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should skip builders that should not execute', () => {
|
|
151
|
+
orchestrator = new BuilderOrchestrator([
|
|
152
|
+
new MockBuilderA(),
|
|
153
|
+
new MockBuilderC(), // Should not execute
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
const appDef = { featureA: true };
|
|
157
|
+
const order = orchestrator.resolveBuildOrder(appDef);
|
|
158
|
+
|
|
159
|
+
expect(order).toEqual(['MockBuilderA']);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('buildAll()', () => {
|
|
164
|
+
it('should build all infrastructure and merge results', async () => {
|
|
165
|
+
orchestrator = new BuilderOrchestrator([
|
|
166
|
+
new MockBuilderA(),
|
|
167
|
+
new MockBuilderB(),
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
const appDef = { featureA: true, featureB: true };
|
|
171
|
+
|
|
172
|
+
const result = await orchestrator.buildAll(appDef);
|
|
173
|
+
|
|
174
|
+
expect(result.merged).toBeDefined();
|
|
175
|
+
expect(result.merged.resources).toMatchObject({
|
|
176
|
+
ResourceA: { Type: 'Test' },
|
|
177
|
+
ResourceB: { Type: 'Test' },
|
|
178
|
+
});
|
|
179
|
+
expect(result.merged.iamStatements).toHaveLength(1);
|
|
180
|
+
expect(result.merged.environment).toMatchObject({ VAR_B: 'value-b' });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should skip builders that should not execute', async () => {
|
|
184
|
+
orchestrator = new BuilderOrchestrator([
|
|
185
|
+
new MockBuilderA(),
|
|
186
|
+
new MockBuilderC(), // Should not execute
|
|
187
|
+
]);
|
|
188
|
+
|
|
189
|
+
const appDef = { featureA: true };
|
|
190
|
+
|
|
191
|
+
const result = await orchestrator.buildAll(appDef);
|
|
192
|
+
|
|
193
|
+
// Should only have results from A
|
|
194
|
+
expect(Object.keys(result.merged.resources)).toEqual(['ResourceA']);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should throw error if builder fails', async () => {
|
|
198
|
+
class FailingBuilder extends InfrastructureBuilder {
|
|
199
|
+
getName() { return 'FailingBuilder'; }
|
|
200
|
+
shouldExecute() { return true; }
|
|
201
|
+
validate() { return new ValidationResult(); }
|
|
202
|
+
async build() {
|
|
203
|
+
throw new Error('Build failed');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
orchestrator = new BuilderOrchestrator([new FailingBuilder()]);
|
|
208
|
+
|
|
209
|
+
await expect(orchestrator.buildAll({})).rejects.toThrow('Build failed');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudFormation-based Resource Discovery v2
|
|
3
|
+
*
|
|
4
|
+
* Refactored to return structured DiscoveryResult instead of flat object.
|
|
5
|
+
* Part of the clean resource ownership architecture.
|
|
6
|
+
*
|
|
7
|
+
* Domain Service - Hexagonal Architecture
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { createEmptyDiscoveryResult } = require('./types');
|
|
11
|
+
|
|
12
|
+
class CloudFormationDiscoveryV2 {
|
|
13
|
+
constructor(provider, config = {}) {
|
|
14
|
+
this.provider = provider;
|
|
15
|
+
this.serviceName = config.serviceName;
|
|
16
|
+
this.stage = config.stage;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Discover resources from an existing CloudFormation stack
|
|
21
|
+
*
|
|
22
|
+
* @param {string} stackName - Name of the CloudFormation stack
|
|
23
|
+
* @returns {Promise<Object>} DiscoveryResult or empty result if stack doesn't exist
|
|
24
|
+
*/
|
|
25
|
+
async discoverFromStack(stackName) {
|
|
26
|
+
try {
|
|
27
|
+
// Try to get the stack
|
|
28
|
+
const stack = await this.provider.describeStack(stackName);
|
|
29
|
+
|
|
30
|
+
// Get stack resources
|
|
31
|
+
const resources = await this.provider.listStackResources(stackName);
|
|
32
|
+
|
|
33
|
+
// Create structured discovery result
|
|
34
|
+
const discovery = createEmptyDiscoveryResult();
|
|
35
|
+
discovery.fromCloudFormation = true;
|
|
36
|
+
discovery.stackName = stackName;
|
|
37
|
+
discovery.region = this.provider.region;
|
|
38
|
+
|
|
39
|
+
// Extract stack-managed resources
|
|
40
|
+
await this._extractStackManagedResources(resources || [], discovery);
|
|
41
|
+
|
|
42
|
+
// Also keep flat structure for backwards compatibility (temporarily)
|
|
43
|
+
const flatDiscovered = {
|
|
44
|
+
fromCloudFormationStack: true,
|
|
45
|
+
stackName: stackName,
|
|
46
|
+
existingLogicalIds: discovery.stackManaged.map(r => r.logicalId)
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Extract from outputs (legacy)
|
|
50
|
+
if (stack.Outputs && stack.Outputs.length > 0) {
|
|
51
|
+
this._extractFromOutputs(stack.Outputs, flatDiscovered);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Extract flat properties from stackManaged resources
|
|
55
|
+
this._createFlatPropertiesFromStackManaged(discovery, flatDiscovered);
|
|
56
|
+
|
|
57
|
+
// Return both structures (flat for backwards compat, structured for new code)
|
|
58
|
+
return {
|
|
59
|
+
...flatDiscovered,
|
|
60
|
+
_structured: discovery // New structured format
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
// Stack doesn't exist - return empty discovery
|
|
64
|
+
if (error.message && error.message.includes('does not exist')) {
|
|
65
|
+
const empty = createEmptyDiscoveryResult();
|
|
66
|
+
return {
|
|
67
|
+
fromCloudFormationStack: false,
|
|
68
|
+
_structured: empty
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Other errors - log and return empty
|
|
73
|
+
console.warn(`⚠️ CloudFormation discovery failed: ${error.message}`);
|
|
74
|
+
const empty = createEmptyDiscoveryResult();
|
|
75
|
+
return {
|
|
76
|
+
fromCloudFormationStack: false,
|
|
77
|
+
_structured: empty
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Extract stack-managed resources into structured format
|
|
84
|
+
* @private
|
|
85
|
+
*/
|
|
86
|
+
async _extractStackManagedResources(resources, discovery) {
|
|
87
|
+
console.log(` DEBUG: Processing ${resources.length} CloudFormation resources...`);
|
|
88
|
+
|
|
89
|
+
for (const resource of resources) {
|
|
90
|
+
const { LogicalResourceId, PhysicalResourceId, ResourceType } = resource;
|
|
91
|
+
|
|
92
|
+
// Only track Frigg-managed resources
|
|
93
|
+
if (!LogicalResourceId.startsWith('Frigg') && !LogicalResourceId.includes('Migration')) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Add to stack-managed list
|
|
98
|
+
const stackResource = {
|
|
99
|
+
logicalId: LogicalResourceId,
|
|
100
|
+
physicalId: PhysicalResourceId,
|
|
101
|
+
resourceType: ResourceType,
|
|
102
|
+
properties: {} // Will be populated as needed
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
discovery.stackManaged.push(stackResource);
|
|
106
|
+
|
|
107
|
+
// Query AWS for detailed properties for certain resources
|
|
108
|
+
await this._enrichResourceProperties(stackResource, discovery);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(` ✓ Discovered ${discovery.stackManaged.length} stack-managed resources`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Enrich resource properties by querying AWS APIs
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
async _enrichResourceProperties(stackResource, discovery) {
|
|
119
|
+
const { logicalId, physicalId, resourceType } = stackResource;
|
|
120
|
+
|
|
121
|
+
// Security Group - query to get VPC ID
|
|
122
|
+
if (logicalId === 'FriggLambdaSecurityGroup' && resourceType === 'AWS::EC2::SecurityGroup') {
|
|
123
|
+
console.log(` ✓ Found security group in stack: ${physicalId}`);
|
|
124
|
+
|
|
125
|
+
if (this.provider && this.provider.getEC2Client) {
|
|
126
|
+
try {
|
|
127
|
+
console.log(` Querying EC2 to get VPC ID from security group...`);
|
|
128
|
+
const { DescribeSecurityGroupsCommand } = require('@aws-sdk/client-ec2');
|
|
129
|
+
const ec2Client = this.provider.getEC2Client();
|
|
130
|
+
const sgDetails = await ec2Client.send(
|
|
131
|
+
new DescribeSecurityGroupsCommand({
|
|
132
|
+
GroupIds: [physicalId]
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (sgDetails.SecurityGroups && sgDetails.SecurityGroups.length > 0) {
|
|
137
|
+
const sg = sgDetails.SecurityGroups[0];
|
|
138
|
+
stackResource.properties.VpcId = sg.VpcId;
|
|
139
|
+
console.log(` ✓ Extracted VPC ID from security group: ${sg.VpcId}`);
|
|
140
|
+
|
|
141
|
+
// Also add VPC to stack-managed if not already there
|
|
142
|
+
const vpcExists = discovery.stackManaged.some(r => r.logicalId === 'FriggVPC');
|
|
143
|
+
if (!vpcExists && sg.VpcId) {
|
|
144
|
+
// Note: VPC was created by stack, just not listed in resources yet
|
|
145
|
+
// We'll add it when we encounter it, or infer it here
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.warn(` ⚠️ Could not get VPC from security group: ${error.message}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Aurora Cluster - query to get endpoint
|
|
155
|
+
if (logicalId === 'FriggAuroraCluster' && resourceType === 'AWS::RDS::DBCluster') {
|
|
156
|
+
console.log(` ✓ Found Aurora cluster in stack: ${physicalId}`);
|
|
157
|
+
|
|
158
|
+
if (this.provider) {
|
|
159
|
+
try {
|
|
160
|
+
console.log(` Querying RDS to get Aurora endpoint...`);
|
|
161
|
+
const { DescribeDBClustersCommand, RDSClient } = require('@aws-sdk/client-rds');
|
|
162
|
+
|
|
163
|
+
const rdsClient = new RDSClient({ region: this.provider.region });
|
|
164
|
+
const clusterDetails = await rdsClient.send(
|
|
165
|
+
new DescribeDBClustersCommand({
|
|
166
|
+
DBClusterIdentifier: physicalId
|
|
167
|
+
})
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (clusterDetails.DBClusters && clusterDetails.DBClusters.length > 0) {
|
|
171
|
+
const cluster = clusterDetails.DBClusters[0];
|
|
172
|
+
stackResource.properties.Endpoint = cluster.Endpoint;
|
|
173
|
+
stackResource.properties.Port = cluster.Port;
|
|
174
|
+
stackResource.properties.DBClusterIdentifier = cluster.DBClusterIdentifier;
|
|
175
|
+
console.log(` ✓ Extracted Aurora endpoint: ${cluster.Endpoint}:${cluster.Port}`);
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.warn(` ⚠️ Could not get endpoint from Aurora cluster: ${error.message}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// KMS Key Alias - query to get ARN
|
|
184
|
+
if (logicalId === 'FriggKMSKeyAlias' && resourceType === 'AWS::KMS::Alias') {
|
|
185
|
+
console.log(` ✓ Found KMS key alias in stack: ${physicalId}`);
|
|
186
|
+
|
|
187
|
+
if (this.provider && this.provider.describeKmsKey) {
|
|
188
|
+
try {
|
|
189
|
+
console.log(` Querying KMS for alias: ${physicalId}...`);
|
|
190
|
+
const keyMetadata = await this.provider.describeKmsKey(physicalId);
|
|
191
|
+
|
|
192
|
+
if (keyMetadata) {
|
|
193
|
+
stackResource.properties.KeyArn = keyMetadata.Arn;
|
|
194
|
+
console.log(` ✓ Found KMS key via alias query: ${keyMetadata.Arn}`);
|
|
195
|
+
}
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.warn(` ⚠️ Could not get key ARN from alias: ${error.message}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// VPC - just log
|
|
203
|
+
if (logicalId === 'FriggVPC' && resourceType === 'AWS::EC2::VPC') {
|
|
204
|
+
console.log(` ✓ Found VPC in stack: ${physicalId}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Create flat properties from stack-managed resources (backwards compatibility)
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
_createFlatPropertiesFromStackManaged(discovery, flatDiscovered) {
|
|
213
|
+
for (const resource of discovery.stackManaged) {
|
|
214
|
+
const { logicalId, physicalId, properties } = resource;
|
|
215
|
+
|
|
216
|
+
// Map to flat property names
|
|
217
|
+
switch (logicalId) {
|
|
218
|
+
case 'FriggVPC':
|
|
219
|
+
flatDiscovered.defaultVpcId = physicalId;
|
|
220
|
+
break;
|
|
221
|
+
case 'FriggLambdaSecurityGroup':
|
|
222
|
+
flatDiscovered.securityGroupId = physicalId;
|
|
223
|
+
if (properties.VpcId) {
|
|
224
|
+
flatDiscovered.defaultVpcId = properties.VpcId;
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
case 'FriggPrivateSubnet1':
|
|
228
|
+
flatDiscovered.privateSubnetId1 = physicalId;
|
|
229
|
+
break;
|
|
230
|
+
case 'FriggPrivateSubnet2':
|
|
231
|
+
flatDiscovered.privateSubnetId2 = physicalId;
|
|
232
|
+
break;
|
|
233
|
+
case 'FriggPublicSubnet':
|
|
234
|
+
flatDiscovered.publicSubnetId1 = physicalId;
|
|
235
|
+
break;
|
|
236
|
+
case 'FriggPublicSubnet2':
|
|
237
|
+
flatDiscovered.publicSubnetId2 = physicalId;
|
|
238
|
+
break;
|
|
239
|
+
case 'FriggNatGateway':
|
|
240
|
+
flatDiscovered.natGatewayId = physicalId;
|
|
241
|
+
break;
|
|
242
|
+
case 'FriggLambdaRouteTable':
|
|
243
|
+
flatDiscovered.routeTableId = physicalId;
|
|
244
|
+
break;
|
|
245
|
+
case 'FriggVPCEndpointSecurityGroup':
|
|
246
|
+
flatDiscovered.vpcEndpointSecurityGroupId = physicalId;
|
|
247
|
+
break;
|
|
248
|
+
case 'FriggS3VPCEndpoint':
|
|
249
|
+
flatDiscovered.s3VpcEndpointId = physicalId;
|
|
250
|
+
break;
|
|
251
|
+
case 'FriggDynamoDBVPCEndpoint':
|
|
252
|
+
flatDiscovered.dynamoDbVpcEndpointId = physicalId;
|
|
253
|
+
break;
|
|
254
|
+
case 'FriggKMSVPCEndpoint':
|
|
255
|
+
flatDiscovered.kmsVpcEndpointId = physicalId;
|
|
256
|
+
break;
|
|
257
|
+
case 'FriggSecretsManagerVPCEndpoint':
|
|
258
|
+
flatDiscovered.secretsManagerVpcEndpointId = physicalId;
|
|
259
|
+
break;
|
|
260
|
+
case 'FriggSQSVPCEndpoint':
|
|
261
|
+
flatDiscovered.sqsVpcEndpointId = physicalId;
|
|
262
|
+
break;
|
|
263
|
+
case 'FriggAuroraCluster':
|
|
264
|
+
flatDiscovered.auroraClusterId = physicalId;
|
|
265
|
+
if (properties.Endpoint) {
|
|
266
|
+
flatDiscovered.auroraClusterEndpoint = properties.Endpoint;
|
|
267
|
+
}
|
|
268
|
+
if (properties.Port) {
|
|
269
|
+
flatDiscovered.auroraClusterPort = properties.Port;
|
|
270
|
+
flatDiscovered.auroraPort = properties.Port;
|
|
271
|
+
}
|
|
272
|
+
if (properties.DBClusterIdentifier) {
|
|
273
|
+
flatDiscovered.auroraClusterIdentifier = properties.DBClusterIdentifier;
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
case 'FriggKMSKey':
|
|
277
|
+
flatDiscovered.defaultKmsKeyId = physicalId;
|
|
278
|
+
break;
|
|
279
|
+
case 'FriggKMSKeyAlias':
|
|
280
|
+
flatDiscovered.kmsKeyAlias = physicalId;
|
|
281
|
+
if (properties.KeyArn) {
|
|
282
|
+
flatDiscovered.defaultKmsKeyId = properties.KeyArn;
|
|
283
|
+
}
|
|
284
|
+
break;
|
|
285
|
+
case 'FriggMigrationStatusBucket':
|
|
286
|
+
flatDiscovered.migrationStatusBucket = physicalId;
|
|
287
|
+
break;
|
|
288
|
+
case 'DbMigrationQueue':
|
|
289
|
+
flatDiscovered.migrationQueueUrl = physicalId;
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Extract discovered resources from CloudFormation stack outputs (legacy)
|
|
297
|
+
* @private
|
|
298
|
+
*/
|
|
299
|
+
_extractFromOutputs(outputs, discovered) {
|
|
300
|
+
const outputMap = outputs.reduce((acc, output) => {
|
|
301
|
+
acc[output.OutputKey] = output.OutputValue;
|
|
302
|
+
return acc;
|
|
303
|
+
}, {});
|
|
304
|
+
|
|
305
|
+
// VPC outputs
|
|
306
|
+
if (outputMap.VpcId) {
|
|
307
|
+
discovered.defaultVpcId = outputMap.VpcId;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (outputMap.PrivateSubnetIds) {
|
|
311
|
+
discovered.privateSubnetIds = outputMap.PrivateSubnetIds.split(',').map(id => id.trim());
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (outputMap.PublicSubnetId) {
|
|
315
|
+
discovered.publicSubnetId = outputMap.PublicSubnetId;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (outputMap.SecurityGroupId) {
|
|
319
|
+
discovered.securityGroupId = outputMap.SecurityGroupId;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// KMS outputs
|
|
323
|
+
if (outputMap.KMS_KEY_ARN) {
|
|
324
|
+
discovered.defaultKmsKeyId = outputMap.KMS_KEY_ARN;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Database outputs
|
|
328
|
+
if (outputMap.DatabaseEndpoint) {
|
|
329
|
+
discovered.databaseEndpoint = outputMap.DatabaseEndpoint;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
module.exports = CloudFormationDiscoveryV2;
|