@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,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Resource Resolver
|
|
3
|
+
*
|
|
4
|
+
* Domain Layer - Hexagonal Architecture
|
|
5
|
+
*
|
|
6
|
+
* Responsible for resolving ownership decisions for integration infrastructure:
|
|
7
|
+
* - SQS queues per integration
|
|
8
|
+
*
|
|
9
|
+
* Follows the ownership-based architecture pattern:
|
|
10
|
+
* - STACK: Create/manage resources in CloudFormation stack
|
|
11
|
+
* - EXTERNAL: Use existing resources by physical ID
|
|
12
|
+
* - AUTO: Intelligent decision based on discovery
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const BaseResourceResolver = require('../shared/base-resolver');
|
|
16
|
+
const { ResourceOwnership } = require('../shared/types/resource-ownership');
|
|
17
|
+
|
|
18
|
+
class IntegrationResourceResolver extends BaseResourceResolver {
|
|
19
|
+
/**
|
|
20
|
+
* Resolve ownership for all integration resources
|
|
21
|
+
* @param {Object} appDefinition - Application definition
|
|
22
|
+
* @param {Object} discovery - Structured discovery result
|
|
23
|
+
* @returns {Object} Ownership decisions for all integration resources
|
|
24
|
+
*/
|
|
25
|
+
resolveAll(appDefinition, discovery) {
|
|
26
|
+
const integrations = appDefinition.integrations || [];
|
|
27
|
+
const decisions = {
|
|
28
|
+
// Shared resources used by all integrations
|
|
29
|
+
internalErrorQueue: this.resolveInternalErrorQueue(appDefinition, discovery),
|
|
30
|
+
// Per-integration queues
|
|
31
|
+
integrations: {},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Resolve ownership for each integration's SQS queue
|
|
35
|
+
integrations.forEach(integration => {
|
|
36
|
+
const integrationName = integration.Definition?.name;
|
|
37
|
+
if (!integrationName) {
|
|
38
|
+
return; // Skip invalid integrations
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
decisions.integrations[integrationName] = {
|
|
42
|
+
queue: this.resolveQueue(integrationName, appDefinition, discovery),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return decisions;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve ownership for the shared InternalErrorQueue (dead letter queue)
|
|
51
|
+
* @param {Object} appDefinition - Application definition
|
|
52
|
+
* @param {Object} discovery - Structured discovery result
|
|
53
|
+
* @returns {Object} Ownership decision for InternalErrorQueue
|
|
54
|
+
*/
|
|
55
|
+
resolveInternalErrorQueue(appDefinition, discovery) {
|
|
56
|
+
const userIntent = appDefinition.integrations?.ownership?.internalErrorQueue || ResourceOwnership.AUTO;
|
|
57
|
+
const queueLogicalId = 'InternalErrorQueue';
|
|
58
|
+
const inStack = this.isInStack(queueLogicalId, discovery);
|
|
59
|
+
|
|
60
|
+
// STACK: User wants this in the CloudFormation stack
|
|
61
|
+
if (userIntent === ResourceOwnership.STACK) {
|
|
62
|
+
const stackResource = inStack ? this.findInStack(queueLogicalId, discovery) : null;
|
|
63
|
+
return this.createStackDecision(
|
|
64
|
+
stackResource?.physicalId || null,
|
|
65
|
+
inStack
|
|
66
|
+
? `Found ${queueLogicalId} in CloudFormation stack`
|
|
67
|
+
: `Will create ${queueLogicalId} in stack`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// EXTERNAL: User wants to use an existing queue
|
|
72
|
+
if (userIntent === ResourceOwnership.EXTERNAL) {
|
|
73
|
+
const queueArn = appDefinition.integrations?.internalErrorQueue?.arn;
|
|
74
|
+
if (!queueArn) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
'InternalErrorQueue configured with ownership=external but integrations.internalErrorQueue.arn not provided'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return this.createExternalDecision(
|
|
80
|
+
queueArn,
|
|
81
|
+
`Using external InternalErrorQueue ARN: ${queueArn}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// AUTO: Intelligent decision based on discovery
|
|
86
|
+
if (inStack) {
|
|
87
|
+
const stackResource = this.findInStack(queueLogicalId, discovery);
|
|
88
|
+
return this.createStackDecision(
|
|
89
|
+
stackResource.physicalId,
|
|
90
|
+
`Found ${queueLogicalId} in CloudFormation stack - will include definition (idempotent)`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Default: Create in stack
|
|
95
|
+
return this.createStackDecision(
|
|
96
|
+
null,
|
|
97
|
+
`No existing ${queueLogicalId} found - will create in stack`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve ownership for an integration's SQS queue
|
|
103
|
+
* @param {string} integrationName - Name of the integration (e.g., 'slack')
|
|
104
|
+
* @param {Object} appDefinition - Application definition
|
|
105
|
+
* @param {Object} discovery - Structured discovery result
|
|
106
|
+
* @returns {Object} Ownership decision for the queue
|
|
107
|
+
*/
|
|
108
|
+
resolveQueue(integrationName, appDefinition, discovery) {
|
|
109
|
+
// Check for explicit ownership configuration
|
|
110
|
+
const integration = appDefinition.integrations?.find(
|
|
111
|
+
i => i.Definition?.name === integrationName
|
|
112
|
+
);
|
|
113
|
+
const userIntent = integration?.ownership?.queue || ResourceOwnership.AUTO;
|
|
114
|
+
|
|
115
|
+
// Queue logical ID follows pattern: ${IntegrationName}Queue (e.g., SlackQueue)
|
|
116
|
+
const queueLogicalId = `${this.capitalizeFirst(integrationName)}Queue`;
|
|
117
|
+
const inStack = this.isInStack(queueLogicalId, discovery);
|
|
118
|
+
|
|
119
|
+
// STACK: User wants this in the CloudFormation stack
|
|
120
|
+
if (userIntent === ResourceOwnership.STACK) {
|
|
121
|
+
const stackResource = inStack ? this.findInStack(queueLogicalId, discovery) : null;
|
|
122
|
+
return this.createStackDecision(
|
|
123
|
+
stackResource?.physicalId || null,
|
|
124
|
+
inStack
|
|
125
|
+
? `Found ${queueLogicalId} in CloudFormation stack`
|
|
126
|
+
: `Will create ${queueLogicalId} in stack`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// EXTERNAL: User wants to use an existing queue
|
|
131
|
+
if (userIntent === ResourceOwnership.EXTERNAL) {
|
|
132
|
+
const queueUrl = integration?.queue?.url;
|
|
133
|
+
if (!queueUrl) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Integration '${integrationName}' configured with ownership=external but queue.url not provided`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return this.createExternalDecision(
|
|
139
|
+
queueUrl,
|
|
140
|
+
`Using external queue URL: ${queueUrl}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// AUTO: Intelligent decision based on discovery
|
|
145
|
+
if (inStack) {
|
|
146
|
+
const stackResource = this.findInStack(queueLogicalId, discovery);
|
|
147
|
+
return this.createStackDecision(
|
|
148
|
+
stackResource.physicalId,
|
|
149
|
+
`Found ${queueLogicalId} in CloudFormation stack - will include definition (idempotent)`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Default: Create in stack
|
|
154
|
+
return this.createStackDecision(
|
|
155
|
+
null,
|
|
156
|
+
`No existing ${queueLogicalId} found - will create in stack`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Capitalize first letter of string (e.g., 'slack' -> 'Slack')
|
|
162
|
+
* @param {string} str - String to capitalize
|
|
163
|
+
* @returns {string} Capitalized string
|
|
164
|
+
*/
|
|
165
|
+
capitalizeFirst(str) {
|
|
166
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = IntegrationResourceResolver;
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for IntegrationResourceResolver
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const IntegrationResourceResolver = require('./integration-resolver');
|
|
6
|
+
const { ResourceOwnership } = require('../shared/types/resource-ownership');
|
|
7
|
+
|
|
8
|
+
describe('IntegrationResourceResolver', () => {
|
|
9
|
+
let resolver;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
resolver = new IntegrationResourceResolver();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('resolveAll', () => {
|
|
16
|
+
it('should resolve InternalErrorQueue and per-integration queues', () => {
|
|
17
|
+
const appDef = {
|
|
18
|
+
integrations: [
|
|
19
|
+
{ Definition: { name: 'slack' } },
|
|
20
|
+
{ Definition: { name: 'hubspot' } },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const discovery = {
|
|
25
|
+
fromCloudFormationStack: false,
|
|
26
|
+
stackName: null,
|
|
27
|
+
existingLogicalIds: [],
|
|
28
|
+
stackManaged: [],
|
|
29
|
+
stackResources: {},
|
|
30
|
+
external: [],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const decisions = resolver.resolveAll(appDef, discovery);
|
|
34
|
+
|
|
35
|
+
expect(decisions.internalErrorQueue).toBeDefined();
|
|
36
|
+
expect(decisions.internalErrorQueue.ownership).toBe(ResourceOwnership.STACK);
|
|
37
|
+
expect(decisions.integrations.slack).toBeDefined();
|
|
38
|
+
expect(decisions.integrations.slack.queue.ownership).toBe(ResourceOwnership.STACK);
|
|
39
|
+
expect(decisions.integrations.hubspot).toBeDefined();
|
|
40
|
+
expect(decisions.integrations.hubspot.queue.ownership).toBe(ResourceOwnership.STACK);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should skip integrations without Definition.name', () => {
|
|
44
|
+
const appDef = {
|
|
45
|
+
integrations: [
|
|
46
|
+
{ Definition: { name: 'slack' } },
|
|
47
|
+
{ Definition: {} }, // Missing name
|
|
48
|
+
{ something: 'else' }, // Missing Definition
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const discovery = {
|
|
53
|
+
fromCloudFormationStack: false,
|
|
54
|
+
stackName: null,
|
|
55
|
+
existingLogicalIds: [],
|
|
56
|
+
stackManaged: [],
|
|
57
|
+
stackResources: {},
|
|
58
|
+
external: [],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const decisions = resolver.resolveAll(appDef, discovery);
|
|
62
|
+
|
|
63
|
+
expect(decisions.integrations.slack).toBeDefined();
|
|
64
|
+
expect(Object.keys(decisions.integrations)).toHaveLength(1);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('resolveInternalErrorQueue', () => {
|
|
69
|
+
it('should create in stack when nothing exists (AUTO)', () => {
|
|
70
|
+
const appDef = { integrations: [] };
|
|
71
|
+
const discovery = {
|
|
72
|
+
fromCloudFormationStack: false,
|
|
73
|
+
stackName: null,
|
|
74
|
+
existingLogicalIds: [],
|
|
75
|
+
stackManaged: [],
|
|
76
|
+
stackResources: {},
|
|
77
|
+
external: [],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const decision = resolver.resolveInternalErrorQueue(appDef, discovery);
|
|
81
|
+
|
|
82
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
83
|
+
expect(decision.physicalId).toBeNull();
|
|
84
|
+
expect(decision.reason).toContain('will create in stack');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should use stack resource when found (AUTO)', () => {
|
|
88
|
+
const appDef = { integrations: [] };
|
|
89
|
+
const discovery = {
|
|
90
|
+
fromCloudFormationStack: true,
|
|
91
|
+
stackName: 'test-stack',
|
|
92
|
+
existingLogicalIds: ['InternalErrorQueue'],
|
|
93
|
+
stackManaged: [
|
|
94
|
+
{
|
|
95
|
+
logicalId: 'InternalErrorQueue',
|
|
96
|
+
physicalId: 'https://sqs.us-east-1.amazonaws.com/123456789/internal-error-queue',
|
|
97
|
+
type: 'AWS::SQS::Queue',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
stackResources: {
|
|
101
|
+
InternalErrorQueue: {
|
|
102
|
+
logicalId: 'InternalErrorQueue',
|
|
103
|
+
physicalId: 'https://sqs.us-east-1.amazonaws.com/123456789/internal-error-queue',
|
|
104
|
+
type: 'AWS::SQS::Queue',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
external: [],
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const decision = resolver.resolveInternalErrorQueue(appDef, discovery);
|
|
111
|
+
|
|
112
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
113
|
+
expect(decision.physicalId).toBe('https://sqs.us-east-1.amazonaws.com/123456789/internal-error-queue');
|
|
114
|
+
expect(decision.reason).toContain('Found InternalErrorQueue in CloudFormation stack');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should respect explicit STACK ownership', () => {
|
|
118
|
+
const appDef = {
|
|
119
|
+
integrations: {
|
|
120
|
+
ownership: {
|
|
121
|
+
internalErrorQueue: ResourceOwnership.STACK,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
const discovery = {
|
|
126
|
+
fromCloudFormationStack: false,
|
|
127
|
+
stackName: null,
|
|
128
|
+
existingLogicalIds: [],
|
|
129
|
+
stackManaged: [],
|
|
130
|
+
stackResources: {},
|
|
131
|
+
external: [],
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const decision = resolver.resolveInternalErrorQueue(appDef, discovery);
|
|
135
|
+
|
|
136
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
137
|
+
expect(decision.physicalId).toBeNull();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should use external ARN when ownership=EXTERNAL', () => {
|
|
141
|
+
const appDef = {
|
|
142
|
+
integrations: {
|
|
143
|
+
ownership: {
|
|
144
|
+
internalErrorQueue: ResourceOwnership.EXTERNAL,
|
|
145
|
+
},
|
|
146
|
+
internalErrorQueue: {
|
|
147
|
+
arn: 'arn:aws:sqs:us-east-1:123456789:my-error-queue',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
const discovery = {
|
|
152
|
+
fromCloudFormationStack: false,
|
|
153
|
+
stackName: null,
|
|
154
|
+
existingLogicalIds: [],
|
|
155
|
+
stackManaged: [],
|
|
156
|
+
stackResources: {},
|
|
157
|
+
external: [],
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const decision = resolver.resolveInternalErrorQueue(appDef, discovery);
|
|
161
|
+
|
|
162
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
163
|
+
expect(decision.physicalId).toBe('arn:aws:sqs:us-east-1:123456789:my-error-queue');
|
|
164
|
+
expect(decision.reason).toContain('Using external InternalErrorQueue ARN');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should throw when EXTERNAL ownership but no ARN provided', () => {
|
|
168
|
+
const appDef = {
|
|
169
|
+
integrations: {
|
|
170
|
+
ownership: {
|
|
171
|
+
internalErrorQueue: ResourceOwnership.EXTERNAL,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
const discovery = {
|
|
176
|
+
fromCloudFormationStack: false,
|
|
177
|
+
stackName: null,
|
|
178
|
+
existingLogicalIds: [],
|
|
179
|
+
stackManaged: [],
|
|
180
|
+
stackResources: {},
|
|
181
|
+
external: [],
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
expect(() => {
|
|
185
|
+
resolver.resolveInternalErrorQueue(appDef, discovery);
|
|
186
|
+
}).toThrow('InternalErrorQueue configured with ownership=external');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('resolveQueue', () => {
|
|
191
|
+
it('should create in stack when nothing exists (AUTO)', () => {
|
|
192
|
+
const appDef = {
|
|
193
|
+
integrations: [{ Definition: { name: 'slack' } }],
|
|
194
|
+
};
|
|
195
|
+
const discovery = {
|
|
196
|
+
fromCloudFormationStack: false,
|
|
197
|
+
stackName: null,
|
|
198
|
+
existingLogicalIds: [],
|
|
199
|
+
stackManaged: [],
|
|
200
|
+
stackResources: {},
|
|
201
|
+
external: [],
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const decision = resolver.resolveQueue('slack', appDef, discovery);
|
|
205
|
+
|
|
206
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
207
|
+
expect(decision.physicalId).toBeNull();
|
|
208
|
+
expect(decision.reason).toContain('will create in stack');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should use stack resource when found (AUTO)', () => {
|
|
212
|
+
const appDef = {
|
|
213
|
+
integrations: [{ Definition: { name: 'slack' } }],
|
|
214
|
+
};
|
|
215
|
+
const discovery = {
|
|
216
|
+
fromCloudFormationStack: true,
|
|
217
|
+
stackName: 'test-stack',
|
|
218
|
+
existingLogicalIds: ['SlackQueue'],
|
|
219
|
+
stackManaged: [
|
|
220
|
+
{
|
|
221
|
+
logicalId: 'SlackQueue',
|
|
222
|
+
physicalId: 'https://sqs.us-east-1.amazonaws.com/123456789/slack-queue',
|
|
223
|
+
type: 'AWS::SQS::Queue',
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
stackResources: {
|
|
227
|
+
SlackQueue: {
|
|
228
|
+
logicalId: 'SlackQueue',
|
|
229
|
+
physicalId: 'https://sqs.us-east-1.amazonaws.com/123456789/slack-queue',
|
|
230
|
+
type: 'AWS::SQS::Queue',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
external: [],
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const decision = resolver.resolveQueue('slack', appDef, discovery);
|
|
237
|
+
|
|
238
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
239
|
+
expect(decision.physicalId).toBe('https://sqs.us-east-1.amazonaws.com/123456789/slack-queue');
|
|
240
|
+
expect(decision.reason).toContain('Found SlackQueue in CloudFormation stack');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should respect per-integration STACK ownership', () => {
|
|
244
|
+
const appDef = {
|
|
245
|
+
integrations: [
|
|
246
|
+
{
|
|
247
|
+
Definition: { name: 'slack' },
|
|
248
|
+
ownership: { queue: ResourceOwnership.STACK },
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
};
|
|
252
|
+
const discovery = {
|
|
253
|
+
fromCloudFormationStack: false,
|
|
254
|
+
stackName: null,
|
|
255
|
+
existingLogicalIds: [],
|
|
256
|
+
stackManaged: [],
|
|
257
|
+
stackResources: {},
|
|
258
|
+
external: [],
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const decision = resolver.resolveQueue('slack', appDef, discovery);
|
|
262
|
+
|
|
263
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
264
|
+
expect(decision.physicalId).toBeNull();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('should use external URL when ownership=EXTERNAL', () => {
|
|
268
|
+
const appDef = {
|
|
269
|
+
integrations: [
|
|
270
|
+
{
|
|
271
|
+
Definition: { name: 'slack' },
|
|
272
|
+
ownership: { queue: ResourceOwnership.EXTERNAL },
|
|
273
|
+
queue: { url: 'https://sqs.us-east-1.amazonaws.com/123456789/my-slack-queue' },
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
const discovery = {
|
|
278
|
+
fromCloudFormationStack: false,
|
|
279
|
+
stackName: null,
|
|
280
|
+
existingLogicalIds: [],
|
|
281
|
+
stackManaged: [],
|
|
282
|
+
stackResources: {},
|
|
283
|
+
external: [],
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const decision = resolver.resolveQueue('slack', appDef, discovery);
|
|
287
|
+
|
|
288
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
289
|
+
expect(decision.physicalId).toBe('https://sqs.us-east-1.amazonaws.com/123456789/my-slack-queue');
|
|
290
|
+
expect(decision.reason).toContain('Using external queue URL');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should throw when EXTERNAL ownership but no URL provided', () => {
|
|
294
|
+
const appDef = {
|
|
295
|
+
integrations: [
|
|
296
|
+
{
|
|
297
|
+
Definition: { name: 'slack' },
|
|
298
|
+
ownership: { queue: ResourceOwnership.EXTERNAL },
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
};
|
|
302
|
+
const discovery = {
|
|
303
|
+
fromCloudFormationStack: false,
|
|
304
|
+
stackName: null,
|
|
305
|
+
existingLogicalIds: [],
|
|
306
|
+
stackManaged: [],
|
|
307
|
+
stackResources: {},
|
|
308
|
+
external: [],
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
expect(() => {
|
|
312
|
+
resolver.resolveQueue('slack', appDef, discovery);
|
|
313
|
+
}).toThrow("Integration 'slack' configured with ownership=external but queue.url not provided");
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should handle multiple integrations with different queues', () => {
|
|
317
|
+
const appDef = {
|
|
318
|
+
integrations: [
|
|
319
|
+
{ Definition: { name: 'slack' } },
|
|
320
|
+
{ Definition: { name: 'hubspot' } },
|
|
321
|
+
],
|
|
322
|
+
};
|
|
323
|
+
const discovery = {
|
|
324
|
+
fromCloudFormationStack: true,
|
|
325
|
+
stackName: 'test-stack',
|
|
326
|
+
existingLogicalIds: ['SlackQueue'],
|
|
327
|
+
stackManaged: [
|
|
328
|
+
{
|
|
329
|
+
logicalId: 'SlackQueue',
|
|
330
|
+
physicalId: 'https://sqs.us-east-1.amazonaws.com/123456789/slack-queue',
|
|
331
|
+
type: 'AWS::SQS::Queue',
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
stackResources: {
|
|
335
|
+
SlackQueue: {
|
|
336
|
+
logicalId: 'SlackQueue',
|
|
337
|
+
physicalId: 'https://sqs.us-east-1.amazonaws.com/123456789/slack-queue',
|
|
338
|
+
type: 'AWS::SQS::Queue',
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
external: [],
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const slackDecision = resolver.resolveQueue('slack', appDef, discovery);
|
|
345
|
+
const hubspotDecision = resolver.resolveQueue('hubspot', appDef, discovery);
|
|
346
|
+
|
|
347
|
+
expect(slackDecision.ownership).toBe(ResourceOwnership.STACK);
|
|
348
|
+
expect(slackDecision.physicalId).toBe('https://sqs.us-east-1.amazonaws.com/123456789/slack-queue');
|
|
349
|
+
expect(hubspotDecision.ownership).toBe(ResourceOwnership.STACK);
|
|
350
|
+
expect(hubspotDecision.physicalId).toBeNull(); // HubspotQueue not in stack
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe('capitalizeFirst', () => {
|
|
355
|
+
it('should capitalize first letter', () => {
|
|
356
|
+
expect(resolver.capitalizeFirst('slack')).toBe('Slack');
|
|
357
|
+
expect(resolver.capitalizeFirst('hubspot')).toBe('Hubspot');
|
|
358
|
+
expect(resolver.capitalizeFirst('s')).toBe('S');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should handle already capitalized strings', () => {
|
|
362
|
+
expect(resolver.capitalizeFirst('Slack')).toBe('Slack');
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should handle empty string', () => {
|
|
366
|
+
expect(resolver.capitalizeFirst('')).toBe('');
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Builder
|
|
3
|
+
*
|
|
4
|
+
* Domain Layer - Hexagonal Architecture
|
|
5
|
+
*
|
|
6
|
+
* Responsible for:
|
|
7
|
+
* - WebSocket API Gateway configuration
|
|
8
|
+
* - WebSocket route handlers ($connect, $default, $disconnect)
|
|
9
|
+
* - WebSocket function definitions
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { InfrastructureBuilder, ValidationResult } = require('../shared/base-builder');
|
|
13
|
+
|
|
14
|
+
class WebsocketBuilder extends InfrastructureBuilder {
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.name = 'WebsocketBuilder';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
shouldExecute(appDefinition) {
|
|
21
|
+
// Skip WebSocket in local mode (when FRIGG_SKIP_AWS_DISCOVERY is set)
|
|
22
|
+
// API Gateway WebSocket is an AWS-specific service that should only be created in production
|
|
23
|
+
if (process.env.FRIGG_SKIP_AWS_DISCOVERY === 'true') {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return appDefinition.websockets?.enable === true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
validate(appDefinition) {
|
|
31
|
+
const result = new ValidationResult();
|
|
32
|
+
|
|
33
|
+
if (!appDefinition.websockets) {
|
|
34
|
+
result.addError('WebSocket configuration is missing');
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build WebSocket infrastructure
|
|
43
|
+
*/
|
|
44
|
+
async build(appDefinition, discoveredResources) {
|
|
45
|
+
console.log(`\n[${this.name}] Configuring WebSocket API Gateway...`);
|
|
46
|
+
|
|
47
|
+
const result = {
|
|
48
|
+
functions: {},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Create default WebSocket handler
|
|
52
|
+
result.functions.defaultWebsocket = {
|
|
53
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
54
|
+
events: [
|
|
55
|
+
{ websocket: { route: '$connect' } },
|
|
56
|
+
{ websocket: { route: '$default' } },
|
|
57
|
+
{ websocket: { route: '$disconnect' } },
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
console.log(' ✅ WebSocket functions configured ($connect, $default, $disconnect)');
|
|
62
|
+
console.log(`[${this.name}] ✅ WebSocket configuration completed`);
|
|
63
|
+
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { WebsocketBuilder };
|
|
69
|
+
|