@friggframework/devtools 2.0.0-next.45 → 2.0.0-next.47
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 +695 -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 -2094
- /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,687 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudFormation Template Generation Tests
|
|
3
|
+
*
|
|
4
|
+
* These tests validate that the infrastructure builders generate
|
|
5
|
+
* valid, consistent CloudFormation templates.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { composeServerlessDefinition } = require('../infrastructure-composer');
|
|
9
|
+
|
|
10
|
+
describe('CloudFormation Template Generation', () => {
|
|
11
|
+
// Mock AWS region for tests (prevents actual AWS calls)
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
process.env.AWS_REGION = 'us-east-1';
|
|
14
|
+
// Note: Not setting FRIGG_SKIP_AWS_DISCOVERY - we want builders to execute
|
|
15
|
+
// Resource discovery will return empty results which is fine for testing
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterAll(() => {
|
|
19
|
+
delete process.env.AWS_REGION;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('Basic Structure', () => {
|
|
23
|
+
it('should generate template with required top-level properties', async () => {
|
|
24
|
+
const appDefinition = {
|
|
25
|
+
name: 'test-app',
|
|
26
|
+
provider: 'aws',
|
|
27
|
+
region: 'us-east-1',
|
|
28
|
+
integrations: []
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
32
|
+
|
|
33
|
+
expect(result).toBeDefined();
|
|
34
|
+
expect(result.provider).toBeDefined();
|
|
35
|
+
expect(result.service).toBe('test-app');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should include functions section', async () => {
|
|
39
|
+
const appDefinition = {
|
|
40
|
+
name: 'test-app',
|
|
41
|
+
integrations: [
|
|
42
|
+
{ Definition: { name: 'slack' } }
|
|
43
|
+
]
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
47
|
+
|
|
48
|
+
expect(result.functions).toBeDefined();
|
|
49
|
+
expect(typeof result.functions).toBe('object');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should include resources section', async () => {
|
|
53
|
+
const appDefinition = {
|
|
54
|
+
name: 'test-app',
|
|
55
|
+
integrations: [
|
|
56
|
+
{ Definition: { name: 'slack' } }
|
|
57
|
+
]
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
61
|
+
|
|
62
|
+
expect(result.resources).toBeDefined();
|
|
63
|
+
expect(result.resources.Resources).toBeDefined();
|
|
64
|
+
expect(result.provider.iamRoleStatements).toBeDefined();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('Resource Reference Validation', () => {
|
|
69
|
+
it('should have valid Ref references', async () => {
|
|
70
|
+
const appDefinition = {
|
|
71
|
+
name: 'test-app',
|
|
72
|
+
integrations: [
|
|
73
|
+
{ Definition: { name: 'slack' } }
|
|
74
|
+
]
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
78
|
+
|
|
79
|
+
// Extract all resources and layers
|
|
80
|
+
const resources = result.resources.Resources || {};
|
|
81
|
+
const layers = result.layers || {};
|
|
82
|
+
const allIds = [...Object.keys(resources), ...Object.keys(layers).map(k => `${k.charAt(0).toUpperCase()}${k.slice(1)}LambdaLayer`)];
|
|
83
|
+
|
|
84
|
+
// Find all Ref references
|
|
85
|
+
const refs = findAllRefs(result);
|
|
86
|
+
|
|
87
|
+
// Framework-generated resources (created by Serverless Framework, not our builders)
|
|
88
|
+
const frameworkResources = ['HttpApi', 'HttpApiLogGroup'];
|
|
89
|
+
|
|
90
|
+
// Verify all Refs point to existing resources, layers, or framework resources
|
|
91
|
+
refs.forEach(ref => {
|
|
92
|
+
if (!frameworkResources.includes(ref)) {
|
|
93
|
+
expect(allIds).toContain(ref);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should have valid Fn::GetAtt references', async () => {
|
|
99
|
+
const appDefinition = {
|
|
100
|
+
name: 'test-app',
|
|
101
|
+
integrations: [
|
|
102
|
+
{ Definition: { name: 'slack' } }
|
|
103
|
+
]
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
107
|
+
|
|
108
|
+
const resources = result.resources.Resources || {};
|
|
109
|
+
const resourceIds = Object.keys(resources);
|
|
110
|
+
|
|
111
|
+
// Find all GetAtt references
|
|
112
|
+
const getAtts = findAllGetAtts(result);
|
|
113
|
+
|
|
114
|
+
// Verify all GetAtt references point to existing resources
|
|
115
|
+
getAtts.forEach(([resourceId]) => {
|
|
116
|
+
expect(resourceIds).toContain(resourceId);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should not have circular references', async () => {
|
|
121
|
+
const appDefinition = {
|
|
122
|
+
name: 'test-app',
|
|
123
|
+
integrations: [
|
|
124
|
+
{ Definition: { name: 'slack' } }
|
|
125
|
+
],
|
|
126
|
+
vpc: { enable: true },
|
|
127
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' }
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
131
|
+
|
|
132
|
+
const resources = result.resources.Resources || {};
|
|
133
|
+
|
|
134
|
+
// Build dependency graph
|
|
135
|
+
const graph = buildDependencyGraph(resources);
|
|
136
|
+
|
|
137
|
+
// Check for circular dependencies
|
|
138
|
+
const cycles = detectCycles(graph);
|
|
139
|
+
|
|
140
|
+
expect(cycles).toEqual([]);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('Integration Resources', () => {
|
|
145
|
+
it('should create queue for each integration', async () => {
|
|
146
|
+
const appDefinition = {
|
|
147
|
+
name: 'test-app',
|
|
148
|
+
integrations: [
|
|
149
|
+
{ Definition: { name: 'slack' } },
|
|
150
|
+
{ Definition: { name: 'hubspot' } }
|
|
151
|
+
]
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
155
|
+
|
|
156
|
+
expect(result.resources.Resources.SlackQueue).toBeDefined();
|
|
157
|
+
expect(result.resources.Resources.SlackQueue.Type).toBe('AWS::SQS::Queue');
|
|
158
|
+
expect(result.resources.Resources.HubspotQueue).toBeDefined();
|
|
159
|
+
expect(result.resources.Resources.HubspotQueue.Type).toBe('AWS::SQS::Queue');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should create InternalErrorQueue as DLQ', async () => {
|
|
163
|
+
const appDefinition = {
|
|
164
|
+
name: 'test-app',
|
|
165
|
+
integrations: [
|
|
166
|
+
{ Definition: { name: 'slack' } }
|
|
167
|
+
]
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
171
|
+
|
|
172
|
+
expect(result.resources.Resources.InternalErrorQueue).toBeDefined();
|
|
173
|
+
expect(result.resources.Resources.InternalErrorQueue.Type).toBe('AWS::SQS::Queue');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should configure redrive policy to InternalErrorQueue', async () => {
|
|
177
|
+
const appDefinition = {
|
|
178
|
+
name: 'test-app',
|
|
179
|
+
integrations: [
|
|
180
|
+
{ Definition: { name: 'slack' } }
|
|
181
|
+
]
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
185
|
+
|
|
186
|
+
const slackQueue = result.resources.Resources.SlackQueue;
|
|
187
|
+
expect(slackQueue.Properties.RedrivePolicy).toBeDefined();
|
|
188
|
+
expect(slackQueue.Properties.RedrivePolicy.deadLetterTargetArn).toEqual({
|
|
189
|
+
'Fn::GetAtt': ['InternalErrorQueue', 'Arn']
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should create Lambda functions for each integration', async () => {
|
|
194
|
+
const appDefinition = {
|
|
195
|
+
name: 'test-app',
|
|
196
|
+
integrations: [
|
|
197
|
+
{ Definition: { name: 'slack' } }
|
|
198
|
+
]
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
202
|
+
|
|
203
|
+
// HTTP handler
|
|
204
|
+
expect(result.functions.slack).toBeDefined();
|
|
205
|
+
expect(result.functions.slack.handler).toContain('integration-defined-routers');
|
|
206
|
+
|
|
207
|
+
// Queue worker
|
|
208
|
+
expect(result.functions.slackQueueWorker).toBeDefined();
|
|
209
|
+
expect(result.functions.slackQueueWorker.handler).toContain('integration-defined-workers');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should create webhook handler when enabled', async () => {
|
|
213
|
+
const appDefinition = {
|
|
214
|
+
name: 'test-app',
|
|
215
|
+
integrations: [
|
|
216
|
+
{
|
|
217
|
+
Definition: {
|
|
218
|
+
name: 'hubspot',
|
|
219
|
+
webhooks: true
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
226
|
+
|
|
227
|
+
expect(result.functions.hubspotWebhook).toBeDefined();
|
|
228
|
+
expect(result.functions.hubspotWebhook.handler).toContain('integration-webhook-routers');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should add queue worker SQS event trigger', async () => {
|
|
232
|
+
const appDefinition = {
|
|
233
|
+
name: 'test-app',
|
|
234
|
+
integrations: [
|
|
235
|
+
{ Definition: { name: 'slack' } }
|
|
236
|
+
]
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
240
|
+
|
|
241
|
+
const queueWorker = result.functions.slackQueueWorker;
|
|
242
|
+
expect(queueWorker.events).toBeDefined();
|
|
243
|
+
expect(queueWorker.events[0].sqs).toBeDefined();
|
|
244
|
+
expect(queueWorker.events[0].sqs.arn).toEqual({
|
|
245
|
+
'Fn::GetAtt': ['SlackQueue', 'Arn']
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('VPC Resources', () => {
|
|
251
|
+
it('should create VPC resources when enabled', async () => {
|
|
252
|
+
const appDefinition = {
|
|
253
|
+
name: 'test-app',
|
|
254
|
+
vpc: { enable: true },
|
|
255
|
+
integrations: []
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
259
|
+
|
|
260
|
+
expect(result.resources.Resources.FriggVPC).toBeDefined();
|
|
261
|
+
expect(result.resources.Resources.FriggVPC.Type).toBe('AWS::EC2::VPC');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should create subnets in VPC', async () => {
|
|
265
|
+
const appDefinition = {
|
|
266
|
+
name: 'test-app',
|
|
267
|
+
vpc: { enable: true },
|
|
268
|
+
integrations: []
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
272
|
+
|
|
273
|
+
expect(result.resources.Resources.FriggPrivateSubnet1).toBeDefined();
|
|
274
|
+
expect(result.resources.Resources.FriggPrivateSubnet2).toBeDefined();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should create security group in VPC', async () => {
|
|
278
|
+
const appDefinition = {
|
|
279
|
+
name: 'test-app',
|
|
280
|
+
vpc: { enable: true },
|
|
281
|
+
integrations: []
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
285
|
+
|
|
286
|
+
expect(result.resources.Resources.FriggLambdaSecurityGroup).toBeDefined();
|
|
287
|
+
expect(result.resources.Resources.FriggLambdaSecurityGroup.Type).toBe('AWS::EC2::SecurityGroup');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should configure Lambda functions with VPC', async () => {
|
|
291
|
+
const appDefinition = {
|
|
292
|
+
name: 'test-app',
|
|
293
|
+
vpc: { enable: true },
|
|
294
|
+
integrations: [
|
|
295
|
+
{ Definition: { name: 'slack' } }
|
|
296
|
+
]
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
300
|
+
|
|
301
|
+
expect(result.provider.vpc).toBeDefined();
|
|
302
|
+
expect(result.provider.vpc.subnetIds).toBeDefined();
|
|
303
|
+
expect(result.provider.vpc.securityGroupIds).toBeDefined();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should not create VPC resources when disabled', async () => {
|
|
307
|
+
const appDefinition = {
|
|
308
|
+
name: 'test-app',
|
|
309
|
+
integrations: []
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
313
|
+
|
|
314
|
+
expect(result.resources.Resources.FriggVPC).toBeUndefined();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('KMS Configuration', () => {
|
|
319
|
+
it('should configure KMS ARN when encryption enabled', async () => {
|
|
320
|
+
const appDefinition = {
|
|
321
|
+
name: 'test-app',
|
|
322
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
323
|
+
integrations: []
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
327
|
+
|
|
328
|
+
// KMS uses discovered keys, not CloudFormation resources
|
|
329
|
+
// Verify environment variable is set
|
|
330
|
+
expect(result.provider.environment.KMS_KEY_ARN).toBeDefined();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should grant KMS permissions when encryption enabled', async () => {
|
|
334
|
+
const appDefinition = {
|
|
335
|
+
name: 'test-app',
|
|
336
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
337
|
+
integrations: []
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
341
|
+
|
|
342
|
+
const kmsStatements = result.provider.iamRoleStatements.filter(stmt =>
|
|
343
|
+
stmt.Action && stmt.Action.some(action => action.startsWith('kms:'))
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
expect(kmsStatements.length).toBeGreaterThan(0);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should use external KMS key when configured', async () => {
|
|
350
|
+
const appDefinition = {
|
|
351
|
+
name: 'test-app',
|
|
352
|
+
encryption: {
|
|
353
|
+
fieldLevelEncryptionMethod: 'kms',
|
|
354
|
+
ownership: { kmsKey: 'external' },
|
|
355
|
+
external: { kmsKeyArn: 'arn:aws:kms:us-east-1:123456789012:key/external' }
|
|
356
|
+
},
|
|
357
|
+
integrations: []
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
361
|
+
|
|
362
|
+
// Should use provided external ARN (may be wrapped in CloudFormation function)
|
|
363
|
+
const kmsArn = result.provider.environment.KMS_KEY_ARN;
|
|
364
|
+
if (typeof kmsArn === 'string') {
|
|
365
|
+
expect(kmsArn).toBe('arn:aws:kms:us-east-1:123456789012:key/external');
|
|
366
|
+
} else {
|
|
367
|
+
// Intrinsic function reference
|
|
368
|
+
expect(kmsArn).toBeDefined();
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
describe('Migration Resources', () => {
|
|
374
|
+
it('should create migration queue for PostgreSQL', async () => {
|
|
375
|
+
const appDefinition = {
|
|
376
|
+
name: 'test-app',
|
|
377
|
+
database: {
|
|
378
|
+
type: 'postgresql',
|
|
379
|
+
aurora: { enable: true }
|
|
380
|
+
},
|
|
381
|
+
integrations: []
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
385
|
+
|
|
386
|
+
expect(result.resources.Resources.DbMigrationQueue).toBeDefined();
|
|
387
|
+
expect(result.resources.Resources.DbMigrationQueue.Type).toBe('AWS::SQS::Queue');
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('should create S3 bucket for migration status', async () => {
|
|
391
|
+
const appDefinition = {
|
|
392
|
+
name: 'test-app',
|
|
393
|
+
database: {
|
|
394
|
+
type: 'postgresql',
|
|
395
|
+
aurora: { enable: true }
|
|
396
|
+
},
|
|
397
|
+
integrations: []
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
401
|
+
|
|
402
|
+
expect(result.resources.Resources.FriggMigrationStatusBucket).toBeDefined();
|
|
403
|
+
expect(result.resources.Resources.FriggMigrationStatusBucket.Type).toBe('AWS::S3::Bucket');
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('should set DeletionPolicy=Retain on migration bucket', async () => {
|
|
407
|
+
const appDefinition = {
|
|
408
|
+
name: 'test-app',
|
|
409
|
+
database: {
|
|
410
|
+
type: 'postgresql',
|
|
411
|
+
aurora: { enable: true }
|
|
412
|
+
},
|
|
413
|
+
integrations: []
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
417
|
+
|
|
418
|
+
expect(result.resources.Resources.FriggMigrationStatusBucket.DeletionPolicy).toBe('Retain');
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
describe('Resource Consistency', () => {
|
|
423
|
+
it('should have no duplicate resource logical IDs', async () => {
|
|
424
|
+
const appDefinition = {
|
|
425
|
+
name: 'test-app',
|
|
426
|
+
integrations: [
|
|
427
|
+
{ Definition: { name: 'slack' } },
|
|
428
|
+
{ Definition: { name: 'hubspot' } }
|
|
429
|
+
],
|
|
430
|
+
vpc: { enable: true },
|
|
431
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
432
|
+
database: {
|
|
433
|
+
type: 'postgresql',
|
|
434
|
+
aurora: { enable: true }
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
439
|
+
|
|
440
|
+
const resources = result.resources.Resources || {};
|
|
441
|
+
const resourceIds = Object.keys(resources);
|
|
442
|
+
const uniqueIds = new Set(resourceIds);
|
|
443
|
+
|
|
444
|
+
expect(resourceIds.length).toBe(uniqueIds.size);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should have consistent resource naming', async () => {
|
|
448
|
+
const appDefinition = {
|
|
449
|
+
name: 'test-app',
|
|
450
|
+
integrations: [
|
|
451
|
+
{ Definition: { name: 'my-integration' } }
|
|
452
|
+
]
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
456
|
+
|
|
457
|
+
// Queue name should be capitalized (first letter only)
|
|
458
|
+
expect(result.resources.Resources['My-integrationQueue']).toBeDefined();
|
|
459
|
+
|
|
460
|
+
// Function name should match integration name
|
|
461
|
+
expect(result.functions['my-integration']).toBeDefined();
|
|
462
|
+
expect(result.functions['my-integrationQueueWorker']).toBeDefined();
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('should set appropriate timeouts for queue workers', async () => {
|
|
466
|
+
const appDefinition = {
|
|
467
|
+
name: 'test-app',
|
|
468
|
+
integrations: [
|
|
469
|
+
{ Definition: { name: 'slack' } }
|
|
470
|
+
]
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
474
|
+
|
|
475
|
+
const queueWorker = result.functions.slackQueueWorker;
|
|
476
|
+
expect(queueWorker.timeout).toBe(900); // 15 minutes (Lambda max)
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('should configure appropriate queue visibility timeout', async () => {
|
|
480
|
+
const appDefinition = {
|
|
481
|
+
name: 'test-app',
|
|
482
|
+
integrations: [
|
|
483
|
+
{ Definition: { name: 'slack' } }
|
|
484
|
+
]
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
488
|
+
|
|
489
|
+
const queue = result.resources.Resources.SlackQueue;
|
|
490
|
+
expect(queue.Properties.VisibilityTimeout).toBe(1800); // 30 minutes (2x Lambda timeout)
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
describe('Environment Variables', () => {
|
|
495
|
+
it('should add queue URLs to environment', async () => {
|
|
496
|
+
const appDefinition = {
|
|
497
|
+
name: 'test-app',
|
|
498
|
+
integrations: [
|
|
499
|
+
{ Definition: { name: 'slack' } }
|
|
500
|
+
]
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
504
|
+
|
|
505
|
+
expect(result.provider.environment.SLACK_QUEUE_URL).toEqual({
|
|
506
|
+
Ref: 'SlackQueue'
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('should add KMS key ARN when encryption enabled', async () => {
|
|
511
|
+
const appDefinition = {
|
|
512
|
+
name: 'test-app',
|
|
513
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
514
|
+
integrations: []
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
518
|
+
|
|
519
|
+
expect(result.provider.environment.KMS_KEY_ARN).toBeDefined();
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('should add migration environment variables for PostgreSQL', async () => {
|
|
523
|
+
const appDefinition = {
|
|
524
|
+
name: 'test-app',
|
|
525
|
+
database: {
|
|
526
|
+
type: 'postgresql',
|
|
527
|
+
aurora: { enable: true }
|
|
528
|
+
},
|
|
529
|
+
integrations: []
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
533
|
+
|
|
534
|
+
expect(result.provider.environment.DB_MIGRATION_QUEUE_URL).toBeDefined();
|
|
535
|
+
expect(result.provider.environment.MIGRATION_STATUS_BUCKET).toBeDefined();
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
describe('IAM Permissions', () => {
|
|
540
|
+
it('should grant SQS permissions for integration queues', async () => {
|
|
541
|
+
const appDefinition = {
|
|
542
|
+
name: 'test-app',
|
|
543
|
+
integrations: [
|
|
544
|
+
{ Definition: { name: 'slack' } }
|
|
545
|
+
]
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
549
|
+
|
|
550
|
+
const sqsStatements = result.provider.iamRoleStatements.filter(stmt =>
|
|
551
|
+
stmt.Action && stmt.Action.some(action => action.startsWith('sqs:'))
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
expect(sqsStatements.length).toBeGreaterThan(0);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('should grant S3 permissions for migration bucket', async () => {
|
|
558
|
+
const appDefinition = {
|
|
559
|
+
name: 'test-app',
|
|
560
|
+
database: {
|
|
561
|
+
type: 'postgresql',
|
|
562
|
+
aurora: { enable: true }
|
|
563
|
+
},
|
|
564
|
+
integrations: []
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
568
|
+
|
|
569
|
+
const s3Statements = result.provider.iamRoleStatements.filter(stmt =>
|
|
570
|
+
stmt.Action && stmt.Action.some(action => action.startsWith('s3:'))
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
expect(s3Statements.length).toBeGreaterThan(0);
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
describe('Ownership-Based Decisions', () => {
|
|
578
|
+
it('should use existing stack resources when discovered', async () => {
|
|
579
|
+
// This test would need actual discovery data
|
|
580
|
+
// Skipping for now - integration tests would cover this
|
|
581
|
+
expect(true).toBe(true);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should use external resources when configured', async () => {
|
|
585
|
+
const appDefinition = {
|
|
586
|
+
name: 'test-app',
|
|
587
|
+
integrations: [
|
|
588
|
+
{
|
|
589
|
+
Definition: { name: 'slack' },
|
|
590
|
+
ownership: { queue: 'external' },
|
|
591
|
+
queue: { url: 'https://sqs.us-east-1.amazonaws.com/123/my-queue' }
|
|
592
|
+
}
|
|
593
|
+
]
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
597
|
+
|
|
598
|
+
// Should not create queue resource
|
|
599
|
+
expect(result.resources.Resources.SlackQueue).toBeUndefined();
|
|
600
|
+
|
|
601
|
+
// Should use external queue URL in environment
|
|
602
|
+
expect(result.provider.environment.SLACK_QUEUE_URL).toBe('https://sqs.us-east-1.amazonaws.com/123/my-queue');
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
// Helper functions
|
|
608
|
+
function findAllRefs(obj, refs = []) {
|
|
609
|
+
if (typeof obj !== 'object' || obj === null) return refs;
|
|
610
|
+
|
|
611
|
+
if (obj.Ref && typeof obj.Ref === 'string') {
|
|
612
|
+
refs.push(obj.Ref);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
Object.values(obj).forEach(value => findAllRefs(value, refs));
|
|
616
|
+
return refs;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function findAllGetAtts(obj, getAtts = []) {
|
|
620
|
+
if (typeof obj !== 'object' || obj === null) return getAtts;
|
|
621
|
+
|
|
622
|
+
if (obj['Fn::GetAtt'] && Array.isArray(obj['Fn::GetAtt'])) {
|
|
623
|
+
getAtts.push(obj['Fn::GetAtt']);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
Object.values(obj).forEach(value => findAllGetAtts(value, getAtts));
|
|
627
|
+
return getAtts;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function buildDependencyGraph(resources) {
|
|
631
|
+
const graph = {};
|
|
632
|
+
|
|
633
|
+
Object.keys(resources).forEach(resourceId => {
|
|
634
|
+
const deps = new Set();
|
|
635
|
+
const resource = resources[resourceId];
|
|
636
|
+
|
|
637
|
+
// Find Ref dependencies
|
|
638
|
+
findAllRefs(resource).forEach(ref => {
|
|
639
|
+
if (ref !== resourceId && resources[ref]) {
|
|
640
|
+
deps.add(ref);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
// Find GetAtt dependencies
|
|
645
|
+
findAllGetAtts(resource).forEach(([ref]) => {
|
|
646
|
+
if (ref !== resourceId && resources[ref]) {
|
|
647
|
+
deps.add(ref);
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
graph[resourceId] = Array.from(deps);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
return graph;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function detectCycles(graph) {
|
|
658
|
+
const visited = new Set();
|
|
659
|
+
const recursionStack = new Set();
|
|
660
|
+
const cycles = [];
|
|
661
|
+
|
|
662
|
+
function dfs(node, path = []) {
|
|
663
|
+
if (recursionStack.has(node)) {
|
|
664
|
+
cycles.push([...path, node]);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (visited.has(node)) return;
|
|
669
|
+
|
|
670
|
+
visited.add(node);
|
|
671
|
+
recursionStack.add(node);
|
|
672
|
+
path.push(node);
|
|
673
|
+
|
|
674
|
+
const neighbors = graph[node] || [];
|
|
675
|
+
neighbors.forEach(neighbor => dfs(neighbor, [...path]));
|
|
676
|
+
|
|
677
|
+
recursionStack.delete(node);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
Object.keys(graph).forEach(node => {
|
|
681
|
+
if (!visited.has(node)) {
|
|
682
|
+
dfs(node);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
return cycles;
|
|
687
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
|
-
const { composeServerlessDefinition } = require('./
|
|
3
|
+
const { composeServerlessDefinition } = require('./infrastructure-composer');
|
|
4
4
|
const { findNearestBackendPackageJson } = require('@friggframework/core');
|
|
5
5
|
|
|
6
6
|
async function createFriggInfrastructure() {
|