@friggframework/devtools 2.0.0-next.11 → 2.0.0-next.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/frigg-cli/README.md +1289 -0
  2. package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
  3. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +649 -0
  4. package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
  5. package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
  6. package/frigg-cli/__tests__/unit/commands/generate-iam.test.js +97 -0
  7. package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
  8. package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
  9. package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
  10. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +397 -0
  11. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +345 -0
  12. package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
  13. package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
  14. package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
  15. package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
  16. package/frigg-cli/__tests__/utils/test-setup.js +287 -0
  17. package/frigg-cli/auth-command/CLAUDE.md +293 -0
  18. package/frigg-cli/auth-command/README.md +450 -0
  19. package/frigg-cli/auth-command/api-key-flow.js +153 -0
  20. package/frigg-cli/auth-command/auth-tester.js +344 -0
  21. package/frigg-cli/auth-command/credential-storage.js +182 -0
  22. package/frigg-cli/auth-command/index.js +256 -0
  23. package/frigg-cli/auth-command/json-schema-form.js +67 -0
  24. package/frigg-cli/auth-command/module-loader.js +172 -0
  25. package/frigg-cli/auth-command/oauth-callback-server.js +431 -0
  26. package/frigg-cli/auth-command/oauth-flow.js +195 -0
  27. package/frigg-cli/auth-command/utils/browser.js +30 -0
  28. package/frigg-cli/build-command/index.js +45 -12
  29. package/frigg-cli/db-setup-command/index.js +246 -0
  30. package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
  31. package/frigg-cli/deploy-command/index.js +339 -23
  32. package/frigg-cli/doctor-command/index.js +335 -0
  33. package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
  34. package/frigg-cli/generate-command/azure-generator.js +43 -0
  35. package/frigg-cli/generate-command/gcp-generator.js +47 -0
  36. package/frigg-cli/generate-command/index.js +333 -0
  37. package/frigg-cli/generate-command/terraform-generator.js +555 -0
  38. package/frigg-cli/generate-iam-command.js +119 -0
  39. package/frigg-cli/index.js +189 -1
  40. package/frigg-cli/index.test.js +1 -4
  41. package/frigg-cli/init-command/backend-first-handler.js +756 -0
  42. package/frigg-cli/init-command/index.js +93 -0
  43. package/frigg-cli/init-command/template-handler.js +143 -0
  44. package/frigg-cli/install-command/index.js +1 -4
  45. package/frigg-cli/jest.config.js +124 -0
  46. package/frigg-cli/package.json +63 -0
  47. package/frigg-cli/repair-command/index.js +564 -0
  48. package/frigg-cli/ssm-command/index.js +308 -0
  49. package/frigg-cli/ssm-command/index.test.js +318 -0
  50. package/frigg-cli/start-command/index.js +118 -5
  51. package/frigg-cli/start-command/start-command.test.js +297 -0
  52. package/frigg-cli/test/init-command.test.js +180 -0
  53. package/frigg-cli/test/npm-registry.test.js +319 -0
  54. package/frigg-cli/ui-command/index.js +154 -0
  55. package/frigg-cli/utils/app-resolver.js +319 -0
  56. package/frigg-cli/utils/backend-path.js +16 -17
  57. package/frigg-cli/utils/database-validator.js +167 -0
  58. package/frigg-cli/utils/error-messages.js +329 -0
  59. package/frigg-cli/utils/npm-registry.js +167 -0
  60. package/frigg-cli/utils/process-manager.js +199 -0
  61. package/frigg-cli/utils/repo-detection.js +405 -0
  62. package/infrastructure/ARCHITECTURE.md +487 -0
  63. package/infrastructure/CLAUDE.md +481 -0
  64. package/infrastructure/HEALTH.md +468 -0
  65. package/infrastructure/README.md +540 -0
  66. package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
  67. package/infrastructure/__tests__/helpers/test-utils.js +275 -0
  68. package/infrastructure/__tests__/postgres-config.test.js +914 -0
  69. package/infrastructure/__tests__/scoped-environment.test.js +126 -0
  70. package/infrastructure/__tests__/ssm-preload-node-options.test.js +79 -0
  71. package/infrastructure/__tests__/template-generation.test.js +687 -0
  72. package/infrastructure/create-frigg-infrastructure.js +129 -20
  73. package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
  74. package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
  75. package/infrastructure/docs/WEBSOCKET-CONFIGURATION.md +105 -0
  76. package/infrastructure/docs/deployment-instructions.md +268 -0
  77. package/infrastructure/docs/generate-iam-command.md +278 -0
  78. package/infrastructure/docs/iam-policy-templates.md +193 -0
  79. package/infrastructure/domains/admin-scripts/admin-script-builder.js +567 -0
  80. package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +1017 -0
  81. package/infrastructure/domains/admin-scripts/index.js +5 -0
  82. package/infrastructure/domains/database/aurora-builder.js +857 -0
  83. package/infrastructure/domains/database/aurora-builder.test.js +960 -0
  84. package/infrastructure/domains/database/aurora-discovery.js +87 -0
  85. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  86. package/infrastructure/domains/database/aurora-resolver.js +210 -0
  87. package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
  88. package/infrastructure/domains/database/migration-builder.js +757 -0
  89. package/infrastructure/domains/database/migration-builder.test.js +454 -0
  90. package/infrastructure/domains/database/migration-resolver.js +163 -0
  91. package/infrastructure/domains/database/migration-resolver.test.js +337 -0
  92. package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
  93. package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
  94. package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
  95. package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
  96. package/infrastructure/domains/health/application/ports/index.js +26 -0
  97. package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
  98. package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
  99. package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
  100. package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
  101. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
  102. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
  103. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
  104. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
  105. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
  106. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
  107. package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
  108. package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
  109. package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
  110. package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
  111. package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
  112. package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
  113. package/infrastructure/domains/health/domain/entities/issue.js +299 -0
  114. package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
  115. package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
  116. package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
  117. package/infrastructure/domains/health/domain/entities/resource.js +159 -0
  118. package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
  119. package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
  120. package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
  121. package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
  122. package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
  123. package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
  124. package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
  125. package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
  126. package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
  127. package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
  128. package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
  129. package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
  130. package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
  131. package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
  132. package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
  133. package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
  134. package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
  135. package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
  136. package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
  137. package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
  138. package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
  139. package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
  140. package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
  141. package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
  142. package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
  143. package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
  144. package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
  145. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
  146. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
  147. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
  148. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
  149. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
  150. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
  151. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
  152. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
  153. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
  154. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
  155. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
  156. package/infrastructure/domains/integration/integration-builder.js +656 -0
  157. package/infrastructure/domains/integration/integration-builder.test.js +1064 -0
  158. package/infrastructure/domains/integration/integration-resolver.js +170 -0
  159. package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
  160. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  161. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  162. package/infrastructure/domains/networking/vpc-builder.js +2101 -0
  163. package/infrastructure/domains/networking/vpc-builder.test.js +2001 -0
  164. package/infrastructure/domains/networking/vpc-discovery.js +184 -0
  165. package/infrastructure/domains/networking/vpc-discovery.test.js +368 -0
  166. package/infrastructure/domains/networking/vpc-resolver.js +514 -0
  167. package/infrastructure/domains/networking/vpc-resolver.test.js +841 -0
  168. package/infrastructure/domains/parameters/offload-utils.js +190 -0
  169. package/infrastructure/domains/parameters/offload-utils.test.js +193 -0
  170. package/infrastructure/domains/parameters/ssm-builder.js +132 -0
  171. package/infrastructure/domains/parameters/ssm-builder.test.js +346 -0
  172. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  173. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  174. package/infrastructure/domains/scheduler/scheduler-builder.js +247 -0
  175. package/infrastructure/domains/scheduler/scheduler-builder.test.js +118 -0
  176. package/infrastructure/domains/security/iam-generator.js +851 -0
  177. package/infrastructure/domains/security/iam-generator.test.js +287 -0
  178. package/infrastructure/domains/security/kms-builder.js +415 -0
  179. package/infrastructure/domains/security/kms-builder.test.js +392 -0
  180. package/infrastructure/domains/security/kms-discovery.js +80 -0
  181. package/infrastructure/domains/security/kms-discovery.test.js +177 -0
  182. package/infrastructure/domains/security/kms-resolver.js +96 -0
  183. package/infrastructure/domains/security/kms-resolver.test.js +216 -0
  184. package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +418 -0
  185. package/infrastructure/domains/security/templates/iam-policy-basic.json +218 -0
  186. package/infrastructure/domains/security/templates/iam-policy-full.json +293 -0
  187. package/infrastructure/domains/shared/base-builder.js +112 -0
  188. package/infrastructure/domains/shared/base-resolver.js +186 -0
  189. package/infrastructure/domains/shared/base-resolver.test.js +305 -0
  190. package/infrastructure/domains/shared/builder-orchestrator.js +226 -0
  191. package/infrastructure/domains/shared/builder-orchestrator.test.js +258 -0
  192. package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
  193. package/infrastructure/domains/shared/cloudformation-discovery.js +681 -0
  194. package/infrastructure/domains/shared/cloudformation-discovery.test.js +1320 -0
  195. package/infrastructure/domains/shared/environment-builder.js +192 -0
  196. package/infrastructure/domains/shared/environment-builder.test.js +529 -0
  197. package/infrastructure/domains/shared/function-environments.js +97 -0
  198. package/infrastructure/domains/shared/function-environments.test.js +146 -0
  199. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +579 -0
  200. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +416 -0
  201. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  202. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  203. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  204. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  205. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  206. package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
  207. package/infrastructure/domains/shared/resource-discovery.js +256 -0
  208. package/infrastructure/domains/shared/resource-discovery.test.js +757 -0
  209. package/infrastructure/domains/shared/types/app-definition.js +225 -0
  210. package/infrastructure/domains/shared/types/discovery-result.js +106 -0
  211. package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
  212. package/infrastructure/domains/shared/types/index.js +46 -0
  213. package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
  214. package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
  215. package/infrastructure/domains/shared/utilities/base-definition-factory.js +409 -0
  216. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  217. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +319 -0
  218. package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
  219. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
  220. package/infrastructure/domains/shared/utilities/nested-node-modules.js +53 -0
  221. package/infrastructure/domains/shared/utilities/nested-node-modules.test.js +61 -0
  222. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +159 -0
  223. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +444 -0
  224. package/infrastructure/domains/shared/validation/env-validator.js +78 -0
  225. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  226. package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
  227. package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
  228. package/infrastructure/esbuild.config.js +53 -0
  229. package/infrastructure/infrastructure-composer.js +168 -0
  230. package/infrastructure/infrastructure-composer.test.js +2042 -0
  231. package/infrastructure/integration.test.js +381 -0
  232. package/infrastructure/scripts/build-prisma-layer.js +701 -0
  233. package/infrastructure/scripts/build-prisma-layer.test.js +170 -0
  234. package/infrastructure/scripts/build-time-discovery.js +238 -0
  235. package/infrastructure/scripts/build-time-discovery.test.js +379 -0
  236. package/infrastructure/scripts/run-discovery.js +110 -0
  237. package/infrastructure/scripts/verify-prisma-layer.js +72 -0
  238. package/management-ui/README.md +203 -0
  239. package/package.json +48 -16
  240. package/test/index.js +2 -4
  241. package/test/mock-api.js +1 -3
  242. package/test/mock-integration.js +4 -14
  243. package/.eslintrc.json +0 -3
  244. package/CHANGELOG.md +0 -132
  245. package/infrastructure/serverless-template.js +0 -283
  246. package/infrastructure/webpack.config.js +0 -20
  247. package/test/auther-definition-tester.js +0 -125
@@ -0,0 +1,656 @@
1
+ /**
2
+ * Integration Builder
3
+ *
4
+ * Domain Layer - Hexagonal Architecture
5
+ *
6
+ * Responsible for:
7
+ * - Creating SQS queues for each integration (CloudFormation resources)
8
+ * - Creating InternalErrorQueue (dead letter queue)
9
+ * - Creating Lambda function definitions (serverless template code)
10
+ * - Creating queue worker Lambda functions
11
+ * - Creating webhook handler functions
12
+ * - Configuring integration-specific routes and handlers
13
+ *
14
+ * Uses ownership-based architecture to support both stack-managed
15
+ * and externally-provided SQS queues.
16
+ */
17
+
18
+ const {
19
+ InfrastructureBuilder,
20
+ ValidationResult,
21
+ } = require('../shared/base-builder');
22
+ const IntegrationResourceResolver = require('./integration-resolver');
23
+ const {
24
+ createEmptyDiscoveryResult,
25
+ ResourceOwnership,
26
+ } = require('../shared/types');
27
+ const {
28
+ isScopedEnvironmentActive,
29
+ getIntegrationFunctionNames,
30
+ getAdminFunctionNames,
31
+ } = require('../shared/function-environments');
32
+ const {
33
+ nestedNodeModulesExcludes,
34
+ } = require('../shared/utilities/nested-node-modules');
35
+
36
+ class IntegrationBuilder extends InfrastructureBuilder {
37
+ constructor() {
38
+ super();
39
+ this.name = 'IntegrationBuilder';
40
+ }
41
+
42
+ shouldExecute(appDefinition) {
43
+ return (
44
+ Array.isArray(appDefinition.integrations) &&
45
+ appDefinition.integrations.length > 0
46
+ );
47
+ }
48
+
49
+ getDependencies() {
50
+ return []; // No dependencies - integrations can run independently
51
+ }
52
+
53
+ validate(appDefinition) {
54
+ const result = new ValidationResult();
55
+
56
+ if (!appDefinition.integrations) {
57
+ return result; // Not an error, just no integrations
58
+ }
59
+
60
+ if (!Array.isArray(appDefinition.integrations)) {
61
+ result.addError('integrations must be an array');
62
+ return result;
63
+ }
64
+
65
+ // Validate each integration
66
+ appDefinition.integrations.forEach((integration, index) => {
67
+ if (!integration?.Definition?.name) {
68
+ result.addError(
69
+ `Integration at index ${index} is missing Definition or name`
70
+ );
71
+ }
72
+ });
73
+
74
+ return result;
75
+ }
76
+
77
+ /**
78
+ * Build integration infrastructure using ownership-based architecture
79
+ */
80
+ async build(appDefinition, discoveredResources) {
81
+ console.log(`\n[${this.name}] Configuring integrations...`);
82
+ console.log(
83
+ ` Processing ${appDefinition.integrations.length} integrations...`
84
+ );
85
+
86
+ const usePrismaLayer = appDefinition.usePrismaLambdaLayer !== false;
87
+
88
+ const result = {
89
+ functions: {},
90
+ resources: {},
91
+ environment: {},
92
+ custom: {},
93
+ iamStatements: [],
94
+ };
95
+
96
+ // Get structured discovery result
97
+ const discovery =
98
+ discoveredResources._structured ||
99
+ this.convertFlatDiscoveryToStructured(discoveredResources);
100
+
101
+ // Use IntegrationResourceResolver to make ownership decisions
102
+ const resolver = new IntegrationResourceResolver();
103
+ const decisions = resolver.resolveAll(appDefinition, discovery);
104
+
105
+ console.log('\n 📋 Resource Ownership Decisions:');
106
+ console.log(
107
+ ` InternalErrorQueue: ${decisions.internalErrorQueue.ownership} - ${decisions.internalErrorQueue.reason}`
108
+ );
109
+
110
+ // Log per-integration decisions
111
+ Object.keys(decisions.integrations).forEach((integrationName) => {
112
+ const queueDecision = decisions.integrations[integrationName].queue;
113
+ console.log(
114
+ ` ${integrationName}Queue: ${queueDecision.ownership} - ${queueDecision.reason}`
115
+ );
116
+ });
117
+
118
+ // Build resources based on ownership decisions
119
+ await this.buildFromDecisions(
120
+ decisions,
121
+ appDefinition,
122
+ result,
123
+ usePrismaLayer
124
+ );
125
+
126
+ console.log(`[${this.name}] ✅ Integration configuration completed`);
127
+ return result;
128
+ }
129
+
130
+ /**
131
+ * Convert flat discovery to structured discovery
132
+ * Provides backwards compatibility
133
+ */
134
+ convertFlatDiscoveryToStructured(flatDiscovery) {
135
+ const discovery = createEmptyDiscoveryResult();
136
+
137
+ if (!flatDiscovery) {
138
+ return discovery;
139
+ }
140
+
141
+ // Check if resources are from CloudFormation stack
142
+ if (flatDiscovery.fromCloudFormationStack) {
143
+ discovery.fromCloudFormation = true;
144
+ discovery.stackName = flatDiscovery.stackName || 'assumed-stack';
145
+
146
+ // Add stack-managed resources from existingLogicalIds
147
+ const existingLogicalIds = flatDiscovery.existingLogicalIds || [];
148
+ existingLogicalIds.forEach((logicalId) => {
149
+ let resourceType = '';
150
+ let physicalId = '';
151
+
152
+ // Determine resource type and physical ID
153
+ if (logicalId === 'InternalErrorQueue') {
154
+ resourceType = 'AWS::SQS::Queue';
155
+ physicalId = flatDiscovery.internalErrorQueueUrl;
156
+ } else if (logicalId.endsWith('Queue')) {
157
+ // Integration-specific queue (e.g., SlackQueue, HubspotQueue)
158
+ resourceType = 'AWS::SQS::Queue';
159
+ const integrationName = logicalId
160
+ .replace('Queue', '')
161
+ .toLowerCase();
162
+ physicalId = flatDiscovery[`${integrationName}QueueUrl`];
163
+ }
164
+
165
+ if (physicalId && typeof physicalId === 'string') {
166
+ discovery.stackManaged.push({
167
+ logicalId,
168
+ physicalId,
169
+ resourceType,
170
+ });
171
+ }
172
+ });
173
+ }
174
+
175
+ return discovery;
176
+ }
177
+
178
+ /**
179
+ * Build integration resources based on ownership decisions
180
+ */
181
+ async buildFromDecisions(
182
+ decisions,
183
+ appDefinition,
184
+ result,
185
+ usePrismaLayer = true
186
+ ) {
187
+ // Create package config first — needed by all Lambda functions including DLQ processor
188
+ const functionPackageConfig = this.createFunctionPackageConfig(
189
+ usePrismaLayer,
190
+ appDefinition
191
+ );
192
+
193
+ // Create InternalErrorQueue if ownership = STACK
194
+ const shouldCreateInternalErrorQueue =
195
+ decisions.internalErrorQueue.ownership === ResourceOwnership.STACK;
196
+
197
+ if (shouldCreateInternalErrorQueue) {
198
+ console.log(' → Creating InternalErrorQueue in stack');
199
+ this.createInternalErrorQueue(result, functionPackageConfig);
200
+ } else {
201
+ console.log(' → Using external InternalErrorQueue');
202
+ this.useExternalInternalErrorQueue(
203
+ decisions.internalErrorQueue,
204
+ result,
205
+ functionPackageConfig
206
+ );
207
+ }
208
+
209
+ for (const integration of appDefinition.integrations) {
210
+ const integrationName = integration.Definition.name;
211
+ const queueDecision = decisions.integrations[integrationName].queue;
212
+
213
+ console.log(`\n Adding integration: ${integrationName}`);
214
+
215
+ // Create Lambda function definitions (serverless template code)
216
+ await this.createFunctionDefinitions(
217
+ integration,
218
+ functionPackageConfig,
219
+ result,
220
+ usePrismaLayer
221
+ );
222
+
223
+ // Create or reference SQS queue based on ownership decision
224
+ const shouldCreateQueue =
225
+ queueDecision.ownership === ResourceOwnership.STACK;
226
+
227
+ if (shouldCreateQueue) {
228
+ console.log(
229
+ ` ✓ Creating ${integrationName}Queue in stack`
230
+ );
231
+ this.createIntegrationQueue(
232
+ integrationName,
233
+ result,
234
+ appDefinition
235
+ );
236
+ } else {
237
+ console.log(` ✓ Using external ${integrationName}Queue`);
238
+ this.useExternalIntegrationQueue(
239
+ integrationName,
240
+ queueDecision,
241
+ result,
242
+ appDefinition
243
+ );
244
+ }
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Create function package exclusion configuration
250
+ */
251
+ createFunctionPackageConfig(usePrismaLayer = true, appDefinition = {}) {
252
+ return {
253
+ exclude: [
254
+ // Exclude AWS SDK (provided by Lambda runtime)
255
+ 'node_modules/aws-sdk/**',
256
+ 'node_modules/@aws-sdk/**',
257
+
258
+ // Exclude Prisma (provided via Lambda Layer)
259
+ ...(usePrismaLayer
260
+ ? [
261
+ 'node_modules/@prisma/**',
262
+ 'node_modules/.prisma/**',
263
+ 'node_modules/prisma/**',
264
+ 'node_modules/@friggframework/core/generated/**',
265
+ ]
266
+ : []),
267
+
268
+ ...nestedNodeModulesExcludes(appDefinition, usePrismaLayer),
269
+
270
+ // Exclude build tools (not needed at runtime)
271
+ 'node_modules/esbuild/**',
272
+ 'node_modules/@esbuild/**',
273
+ 'node_modules/typescript/**',
274
+ 'node_modules/webpack/**',
275
+ 'node_modules/osls/**',
276
+ 'node_modules/serverless-esbuild/**',
277
+ 'node_modules/serverless-jetpack/**',
278
+ 'node_modules/serverless-offline/**',
279
+ 'node_modules/serverless-offline-sqs/**',
280
+ 'node_modules/serverless-dotenv-plugin/**',
281
+ 'node_modules/serverless-kms-grants/**',
282
+ // Note: DO NOT exclude serverless-http - it's a runtime dependency!
283
+
284
+ // Exclude dev/test dependencies
285
+ 'node_modules/@friggframework/test/**',
286
+ 'node_modules/@friggframework/eslint-config/**',
287
+ 'node_modules/@friggframework/prettier-config/**',
288
+ 'node_modules/jest/**',
289
+ 'node_modules/prettier/**',
290
+ 'node_modules/eslint/**',
291
+
292
+ // Exclude local dev files
293
+ 'deploy.log',
294
+ '.env.backup',
295
+ 'docker-compose.yml',
296
+ 'jest.config.js',
297
+ 'jest.unit.config.js',
298
+ '.eslintrc.json',
299
+ '.prettierrc',
300
+ '.prettierignore',
301
+ '.markdownlintignore',
302
+ 'package-lock.json',
303
+
304
+ // Exclude development/test files (keep src/ - needed for integrations and api-modules)
305
+ 'coverage/**',
306
+ 'test/**',
307
+ 'layers/**',
308
+ // Note: DO NOT exclude src/** - handlers need src/integrations and src/api-modules at runtime
309
+ '**/*.test.js',
310
+ '**/*.spec.js',
311
+ '**/.claude-flow/**',
312
+ '**/.swarm/**',
313
+ ],
314
+ };
315
+ }
316
+
317
+ /**
318
+ * Create Lambda function definitions for an integration
319
+ * These are serverless framework template function definitions
320
+ */
321
+ async createFunctionDefinitions(
322
+ integration,
323
+ functionPackageConfig,
324
+ result,
325
+ usePrismaLayer = true
326
+ ) {
327
+ const integrationName = integration.Definition.name;
328
+
329
+ // Add webhook handler if enabled (BEFORE catch-all proxy route)
330
+ // CRITICAL: Webhook routes must be defined before the catch-all {proxy+} route
331
+ // to ensure proper route matching in AWS API Gateway/HTTP API
332
+ const webhookConfig = integration.Definition.webhooks;
333
+ if (
334
+ webhookConfig &&
335
+ (webhookConfig === true || webhookConfig.enabled === true)
336
+ ) {
337
+ const webhookFunctionName = `${integrationName}Webhook`;
338
+
339
+ result.functions[webhookFunctionName] = {
340
+ handler: `node_modules/@friggframework/core/handlers/routers/integration-webhook-routers.handlers.${integrationName}Webhook.handler`,
341
+ skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
342
+ package: functionPackageConfig,
343
+ ...(usePrismaLayer && {
344
+ layers: [{ Ref: 'PrismaLambdaLayer' }],
345
+ }), // Webhook handlers need Prisma for credential lookups
346
+ events: [
347
+ {
348
+ httpApi: {
349
+ path: `/api/${integrationName}-integration/webhooks`,
350
+ method: 'POST',
351
+ },
352
+ },
353
+ {
354
+ httpApi: {
355
+ path: `/api/${integrationName}-integration/webhooks/{integrationId}`,
356
+ method: 'POST',
357
+ },
358
+ },
359
+ ],
360
+ };
361
+ console.log(` ✓ Webhook handler function defined`);
362
+ }
363
+
364
+ // Create HTTP API handler for integration (catch-all route AFTER
365
+ // webhooks). Extension routes get their own functions below.
366
+ result.functions[integrationName] = {
367
+ handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
368
+ skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
369
+ package: functionPackageConfig,
370
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }), // HTTP handlers need Prisma for integration queries
371
+ events: [
372
+ {
373
+ httpApi: {
374
+ path: `/api/${integrationName}-integration/{proxy+}`,
375
+ method: 'ANY',
376
+ },
377
+ },
378
+ ],
379
+ };
380
+ console.log(` ✓ HTTP handler function defined`);
381
+
382
+ // One serverless function per extension binding, namespaced under
383
+ // /{bindingKey}. Prisma layer attached only when useDatabase is true.
384
+ const sanitizeBindingKey = (name) =>
385
+ String(name).replace(/[^A-Za-z0-9]/g, '');
386
+ const extensionEntries = Object.entries(
387
+ integration.Definition.extensions || {}
388
+ );
389
+ for (const [bindingKey, binding] of extensionEntries) {
390
+ const extension = binding && binding.extension;
391
+ const routes = (extension && extension.routes) || [];
392
+ if (routes.length === 0) continue;
393
+ const useDatabase =
394
+ binding.useDatabase ??
395
+ (extension && extension.useDatabase) ??
396
+ false;
397
+ // Wire contract: core's integration-defined-routers derives the
398
+ // identical handler key. Keep both in sync.
399
+ const fnName = `${integrationName}__${sanitizeBindingKey(
400
+ bindingKey
401
+ )}`;
402
+ // Distinct binding keys can sanitize to the same fnName — fail loud rather than overwrite.
403
+ if (
404
+ Object.prototype.hasOwnProperty.call(result.functions, fnName)
405
+ ) {
406
+ throw new Error(
407
+ `Integration "${integrationName}" extension function conflict: ` +
408
+ `binding "${bindingKey}" sanitizes to "${fnName}", which is already taken. ` +
409
+ `Use binding keys that are distinct after stripping non-alphanumeric characters.`
410
+ );
411
+ }
412
+ result.functions[fnName] = {
413
+ handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${fnName}.handler`,
414
+ skipEsbuild: true,
415
+ package: functionPackageConfig,
416
+ ...(usePrismaLayer &&
417
+ useDatabase && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
418
+ events: routes.map((route) => ({
419
+ httpApi: {
420
+ path: `/api/${integrationName}-integration/${bindingKey}${route.path}`,
421
+ method: route.method,
422
+ },
423
+ })),
424
+ };
425
+ console.log(
426
+ ` ✓ Extension handler function defined: ${fnName} (useDatabase: ${useDatabase})`
427
+ );
428
+ }
429
+
430
+ // Create Queue Worker function
431
+ const queueWorkerName = `${integrationName}QueueWorker`;
432
+ result.functions[queueWorkerName] = {
433
+ handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
434
+ skipEsbuild: true, // Nested exports in node_modules - skip esbuild bundling
435
+ package: functionPackageConfig,
436
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }), // Queue workers need Prisma for database operations
437
+ reservedConcurrency: 20,
438
+ events: [
439
+ {
440
+ sqs: {
441
+ arn: {
442
+ 'Fn::GetAtt': [
443
+ `${this.capitalizeFirst(integrationName)}Queue`,
444
+ 'Arn',
445
+ ],
446
+ },
447
+ batchSize: 1,
448
+ functionResponseType: 'ReportBatchItemFailures',
449
+ },
450
+ },
451
+ ],
452
+ timeout: 900, // 15 minutes max for queue workers (Lambda maximum)
453
+ };
454
+ console.log(` ✓ Queue worker function defined`);
455
+ }
456
+
457
+ /**
458
+ * Create InternalErrorQueue CloudFormation resource
459
+ */
460
+ createInternalErrorQueue(result, functionPackageConfig) {
461
+ const queueName =
462
+ '${self:service}-${self:provider.stage}-InternalErrorQueue';
463
+
464
+ result.custom.InternalErrorQueue = queueName;
465
+
466
+ result.resources.InternalErrorQueue = {
467
+ Type: 'AWS::SQS::Queue',
468
+ Properties: {
469
+ QueueName: '${self:custom.InternalErrorQueue}',
470
+ MessageRetentionPeriod: 1209600, // 14 days
471
+ VisibilityTimeout: 300, // 5 minutes — must be >= 6x DLQ processor Lambda timeout (30s × 6 = 180s)
472
+ },
473
+ };
474
+
475
+ this.createDLQObservability(
476
+ result,
477
+ functionPackageConfig,
478
+ {
479
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
480
+ },
481
+ {
482
+ 'Fn::GetAtt': ['InternalErrorQueue', 'QueueName'],
483
+ }
484
+ );
485
+
486
+ console.log(' ✓ Created InternalErrorQueue resource');
487
+ }
488
+
489
+ /**
490
+ * Use external InternalErrorQueue
491
+ */
492
+ useExternalInternalErrorQueue(decision, result, functionPackageConfig) {
493
+ // Add ARN to environment for Lambda functions
494
+ result.environment.INTERNAL_ERROR_QUEUE_ARN = decision.physicalId;
495
+
496
+ // Extract queue name from ARN for CloudWatch dimensions
497
+ const arnParts = decision.physicalId.split(':');
498
+ const queueName = arnParts[arnParts.length - 1];
499
+
500
+ this.createDLQObservability(
501
+ result,
502
+ functionPackageConfig,
503
+ decision.physicalId,
504
+ queueName
505
+ );
506
+
507
+ console.log(
508
+ ` ✓ Using external InternalErrorQueue: ${decision.physicalId}`
509
+ );
510
+ }
511
+
512
+ /**
513
+ * Create DLQ observability resources (alarm + processor Lambda).
514
+ * Called for both stack-owned and external InternalErrorQueues.
515
+ */
516
+ createDLQObservability(result, functionPackageConfig, queueArn, queueName) {
517
+ // CloudWatch Alarm: fires when any message lands in the DLQ
518
+ result.resources.DLQMessageAlarm = {
519
+ Type: 'AWS::CloudWatch::Alarm',
520
+ Properties: {
521
+ AlarmDescription:
522
+ 'Messages in dead-letter queue — integration queue processing failures',
523
+ Namespace: 'AWS/SQS',
524
+ MetricName: 'ApproximateNumberOfMessagesVisible',
525
+ Statistic: 'Maximum',
526
+ Threshold: 500,
527
+ ComparisonOperator: 'GreaterThanThreshold',
528
+ EvaluationPeriods: 1,
529
+ Period: 300,
530
+ AlarmActions: [{ Ref: 'InternalErrorBridgeTopic' }],
531
+ Dimensions: [{ Name: 'QueueName', Value: queueName }],
532
+ },
533
+ };
534
+
535
+ // DLQ processor Lambda: logs failed messages with structured context
536
+ result.functions.dlqProcessor = {
537
+ handler:
538
+ 'node_modules/@friggframework/core/handlers/workers/dlq-processor.dlqProcessor',
539
+ skipEsbuild: true,
540
+ package: functionPackageConfig,
541
+ reservedConcurrency: 1,
542
+ timeout: 30,
543
+ events: [
544
+ {
545
+ sqs: {
546
+ arn: queueArn,
547
+ batchSize: 10,
548
+ functionResponseType: 'ReportBatchItemFailures',
549
+ },
550
+ },
551
+ ],
552
+ };
553
+
554
+ console.log(' ✓ Created DLQ CloudWatch alarm');
555
+ console.log(' ✓ Created DLQ processor Lambda');
556
+ }
557
+
558
+ /**
559
+ * Create integration-specific SQS queue CloudFormation resource
560
+ */
561
+ createIntegrationQueue(integrationName, result, appDefinition) {
562
+ const queueReference = `${this.capitalizeFirst(integrationName)}Queue`;
563
+ const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
564
+
565
+ result.resources[queueReference] = {
566
+ Type: 'AWS::SQS::Queue',
567
+ Properties: {
568
+ QueueName: `\${self:custom.${queueReference}}`,
569
+ MessageRetentionPeriod: 345600, // 4 days (SQS default)
570
+ VisibilityTimeout: 1800,
571
+ RedrivePolicy: {
572
+ maxReceiveCount: 3,
573
+ deadLetterTargetArn: {
574
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
575
+ },
576
+ },
577
+ },
578
+ };
579
+
580
+ // Add queue URL to environment
581
+ this.setQueueUrlEnvironment(
582
+ integrationName,
583
+ { Ref: queueReference },
584
+ result,
585
+ appDefinition
586
+ );
587
+
588
+ // Add queue name to custom section
589
+ result.custom[queueReference] = queueName;
590
+
591
+ console.log(` ✓ Created ${queueReference} resource`);
592
+ }
593
+
594
+ /**
595
+ * Use external integration queue
596
+ */
597
+ useExternalIntegrationQueue(
598
+ integrationName,
599
+ decision,
600
+ result,
601
+ appDefinition
602
+ ) {
603
+ // Add queue URL to environment for Lambda functions
604
+ this.setQueueUrlEnvironment(
605
+ integrationName,
606
+ decision.physicalId,
607
+ result,
608
+ appDefinition
609
+ );
610
+
611
+ console.log(` ✓ Using external queue: ${decision.physicalId}`);
612
+ }
613
+
614
+ /**
615
+ * Broadcast the queue URL app-wide, or — with lambda.scopedEnvironment —
616
+ * scope it to the functions that can actually enqueue: the shared auth
617
+ * router (dispatches integration actions synchronously), the admin-script
618
+ * functions (instantiate arbitrary integrations), and the owning
619
+ * integration's own functions.
620
+ */
621
+ setQueueUrlEnvironment(integrationName, value, result, appDefinition) {
622
+ const key = `${integrationName.toUpperCase()}_QUEUE_URL`;
623
+
624
+ if (!isScopedEnvironmentActive(appDefinition)) {
625
+ result.environment[key] = value;
626
+ return;
627
+ }
628
+
629
+ const integration = appDefinition.integrations.find(
630
+ (entry) => entry.Definition.name === integrationName
631
+ );
632
+ const targets = [
633
+ 'auth',
634
+ ...getAdminFunctionNames(appDefinition),
635
+ ...getIntegrationFunctionNames(integration),
636
+ ];
637
+
638
+ result.functionEnvironments = result.functionEnvironments || {};
639
+ for (const fnName of targets) {
640
+ result.functionEnvironments[fnName] = {
641
+ ...result.functionEnvironments[fnName],
642
+ [key]: value,
643
+ };
644
+ }
645
+ console.log(` ✓ Scoped ${key} to: ${targets.join(', ')}`);
646
+ }
647
+
648
+ /**
649
+ * Capitalize first letter of string (e.g., 'slack' -> 'Slack')
650
+ */
651
+ capitalizeFirst(str) {
652
+ return str.charAt(0).toUpperCase() + str.slice(1);
653
+ }
654
+ }
655
+
656
+ module.exports = { IntegrationBuilder };