@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.
Files changed (212) hide show
  1. package/infrastructure/ARCHITECTURE.md +487 -0
  2. package/infrastructure/HEALTH.md +468 -0
  3. package/infrastructure/README.md +51 -0
  4. package/infrastructure/__tests__/postgres-config.test.js +914 -0
  5. package/infrastructure/__tests__/template-generation.test.js +687 -0
  6. package/infrastructure/create-frigg-infrastructure.js +1 -1
  7. package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
  8. package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
  9. package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
  10. package/infrastructure/domains/database/aurora-builder.js +809 -0
  11. package/infrastructure/domains/database/aurora-builder.test.js +950 -0
  12. package/infrastructure/domains/database/aurora-discovery.js +87 -0
  13. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  14. package/infrastructure/domains/database/aurora-resolver.js +210 -0
  15. package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
  16. package/infrastructure/domains/database/migration-builder.js +695 -0
  17. package/infrastructure/domains/database/migration-builder.test.js +294 -0
  18. package/infrastructure/domains/database/migration-resolver.js +163 -0
  19. package/infrastructure/domains/database/migration-resolver.test.js +337 -0
  20. package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
  21. package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
  22. package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
  23. package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
  24. package/infrastructure/domains/health/application/ports/index.js +26 -0
  25. package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
  26. package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
  27. package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
  28. package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
  29. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
  30. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
  31. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
  32. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
  33. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
  34. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
  35. package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
  36. package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
  37. package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
  38. package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
  39. package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
  40. package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
  41. package/infrastructure/domains/health/domain/entities/issue.js +299 -0
  42. package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
  43. package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
  44. package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
  45. package/infrastructure/domains/health/domain/entities/resource.js +159 -0
  46. package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
  47. package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
  48. package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
  49. package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
  50. package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
  51. package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
  52. package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
  53. package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
  54. package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
  55. package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
  56. package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
  57. package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
  58. package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
  59. package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
  60. package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
  61. package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
  62. package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
  63. package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
  64. package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
  65. package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
  66. package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
  67. package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
  68. package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
  69. package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
  70. package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
  71. package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
  72. package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
  73. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
  74. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
  75. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
  76. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
  77. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
  78. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
  79. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
  80. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
  81. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
  82. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
  83. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
  84. package/infrastructure/domains/integration/integration-builder.js +397 -0
  85. package/infrastructure/domains/integration/integration-builder.test.js +593 -0
  86. package/infrastructure/domains/integration/integration-resolver.js +170 -0
  87. package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
  88. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  89. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  90. package/infrastructure/domains/networking/vpc-builder.js +1829 -0
  91. package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
  92. package/infrastructure/domains/networking/vpc-discovery.js +177 -0
  93. package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
  94. package/infrastructure/domains/networking/vpc-resolver.js +324 -0
  95. package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
  96. package/infrastructure/domains/parameters/ssm-builder.js +79 -0
  97. package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
  98. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  99. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  100. package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
  101. package/infrastructure/domains/security/kms-builder.js +366 -0
  102. package/infrastructure/domains/security/kms-builder.test.js +374 -0
  103. package/infrastructure/domains/security/kms-discovery.js +80 -0
  104. package/infrastructure/domains/security/kms-discovery.test.js +177 -0
  105. package/infrastructure/domains/security/kms-resolver.js +96 -0
  106. package/infrastructure/domains/security/kms-resolver.test.js +216 -0
  107. package/infrastructure/domains/shared/base-builder.js +112 -0
  108. package/infrastructure/domains/shared/base-resolver.js +186 -0
  109. package/infrastructure/domains/shared/base-resolver.test.js +305 -0
  110. package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
  111. package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
  112. package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
  113. package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
  114. package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
  115. package/infrastructure/domains/shared/environment-builder.js +119 -0
  116. package/infrastructure/domains/shared/environment-builder.test.js +247 -0
  117. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
  118. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -0
  119. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  120. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  121. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  122. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  123. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  124. package/infrastructure/domains/shared/resource-discovery.js +192 -0
  125. package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
  126. package/infrastructure/domains/shared/types/app-definition.js +205 -0
  127. package/infrastructure/domains/shared/types/discovery-result.js +106 -0
  128. package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
  129. package/infrastructure/domains/shared/types/index.js +46 -0
  130. package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
  131. package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
  132. package/infrastructure/domains/shared/utilities/base-definition-factory.js +380 -0
  133. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  134. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
  135. package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
  136. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
  137. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
  138. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
  139. package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
  140. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  141. package/infrastructure/esbuild.config.js +53 -0
  142. package/infrastructure/infrastructure-composer.js +87 -0
  143. package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
  144. package/infrastructure/scripts/build-prisma-layer.js +553 -0
  145. package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
  146. package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
  147. package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
  148. package/layers/prisma/nodejs/package.json +8 -0
  149. package/management-ui/server/utils/cliIntegration.js +1 -1
  150. package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
  151. package/package.json +11 -11
  152. package/frigg-cli/.eslintrc.js +0 -141
  153. package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
  154. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
  155. package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
  156. package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
  157. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
  158. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
  159. package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
  160. package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
  161. package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
  162. package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
  163. package/frigg-cli/__tests__/utils/test-setup.js +0 -287
  164. package/frigg-cli/build-command/index.js +0 -65
  165. package/frigg-cli/db-setup-command/index.js +0 -193
  166. package/frigg-cli/deploy-command/index.js +0 -175
  167. package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
  168. package/frigg-cli/generate-command/azure-generator.js +0 -43
  169. package/frigg-cli/generate-command/gcp-generator.js +0 -47
  170. package/frigg-cli/generate-command/index.js +0 -332
  171. package/frigg-cli/generate-command/terraform-generator.js +0 -555
  172. package/frigg-cli/generate-iam-command.js +0 -118
  173. package/frigg-cli/index.js +0 -75
  174. package/frigg-cli/index.test.js +0 -158
  175. package/frigg-cli/init-command/backend-first-handler.js +0 -756
  176. package/frigg-cli/init-command/index.js +0 -93
  177. package/frigg-cli/init-command/template-handler.js +0 -143
  178. package/frigg-cli/install-command/backend-js.js +0 -33
  179. package/frigg-cli/install-command/commit-changes.js +0 -16
  180. package/frigg-cli/install-command/environment-variables.js +0 -127
  181. package/frigg-cli/install-command/environment-variables.test.js +0 -136
  182. package/frigg-cli/install-command/index.js +0 -54
  183. package/frigg-cli/install-command/install-package.js +0 -13
  184. package/frigg-cli/install-command/integration-file.js +0 -30
  185. package/frigg-cli/install-command/logger.js +0 -12
  186. package/frigg-cli/install-command/template.js +0 -90
  187. package/frigg-cli/install-command/validate-package.js +0 -75
  188. package/frigg-cli/jest.config.js +0 -124
  189. package/frigg-cli/package.json +0 -54
  190. package/frigg-cli/start-command/index.js +0 -149
  191. package/frigg-cli/start-command/start-command.test.js +0 -297
  192. package/frigg-cli/test/init-command.test.js +0 -180
  193. package/frigg-cli/test/npm-registry.test.js +0 -319
  194. package/frigg-cli/ui-command/index.js +0 -154
  195. package/frigg-cli/utils/app-resolver.js +0 -319
  196. package/frigg-cli/utils/backend-path.js +0 -25
  197. package/frigg-cli/utils/database-validator.js +0 -161
  198. package/frigg-cli/utils/error-messages.js +0 -257
  199. package/frigg-cli/utils/npm-registry.js +0 -167
  200. package/frigg-cli/utils/prisma-runner.js +0 -280
  201. package/frigg-cli/utils/process-manager.js +0 -199
  202. package/frigg-cli/utils/repo-detection.js +0 -405
  203. package/infrastructure/aws-discovery.js +0 -1176
  204. package/infrastructure/aws-discovery.test.js +0 -1220
  205. package/infrastructure/serverless-template.js +0 -2094
  206. /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
  207. /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
  208. /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
  209. /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
  210. /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
  211. /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
  212. /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
@@ -0,0 +1,553 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Build Prisma Lambda Layer
5
+ *
6
+ * Creates a MINIMAL Lambda Layer containing only Prisma runtime client and query engines.
7
+ * This reduces individual Lambda function sizes by ~60% (120MB → 45MB).
8
+ *
9
+ * IMPORTANT: This layer does NOT include the Prisma CLI (saves ~82MB).
10
+ * The CLI is only needed for migrations and is packaged separately with the dbMigrate function.
11
+ *
12
+ * The layer is configured based on AppDefinition database settings:
13
+ * - PostgreSQL: Includes PostgreSQL client + query engine only
14
+ * - MongoDB: Includes MongoDB client + query engine only (if needed)
15
+ * - Defaults to PostgreSQL only if not specified
16
+ *
17
+ * Usage:
18
+ * node scripts/build-prisma-layer.js
19
+ * npm run build:prisma-layer
20
+ *
21
+ * Output:
22
+ * layers/prisma/nodejs/node_modules/
23
+ * ├── @prisma/client (runtime only, ~10-15MB)
24
+ * ├── generated/prisma-postgresql (if PostgreSQL enabled)
25
+ * └── generated/prisma-mongodb (if MongoDB enabled)
26
+ *
27
+ * See: LAMBDA-LAYER-PRISMA.md for complete documentation
28
+ */
29
+
30
+ const fs = require('fs-extra');
31
+ const path = require('path');
32
+ const { execSync } = require('child_process');
33
+
34
+ /**
35
+ * Find @friggframework/core package, handling workspace hoisting
36
+ * Searches up the directory tree to find node_modules/@friggframework/core
37
+ */
38
+ function findCorePackage(startDir) {
39
+ let currentDir = startDir;
40
+ const root = path.parse(currentDir).root;
41
+
42
+ while (currentDir !== root) {
43
+ const candidatePath = path.join(currentDir, 'node_modules/@friggframework/core');
44
+ if (fs.existsSync(candidatePath)) {
45
+ return candidatePath;
46
+ }
47
+ currentDir = path.dirname(currentDir);
48
+ }
49
+
50
+ throw new Error(
51
+ '@friggframework/core not found in node_modules.\n' +
52
+ 'Run "npm install" to install dependencies.'
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Determine which database clients to include based on configuration
58
+ * @param {Object} databaseConfig - Database configuration from AppDefinition
59
+ * @returns {Array} List of generated client packages to include
60
+ */
61
+ function getGeneratedClientPackages(databaseConfig = {}) {
62
+ const packages = [];
63
+
64
+ // Check if MongoDB is enabled (via mongoDB or documentDB config)
65
+ const mongoEnabled = databaseConfig?.mongoDB?.enable === true ||
66
+ databaseConfig?.documentDB?.enable === true;
67
+ if (mongoEnabled) {
68
+ packages.push('generated/prisma-mongodb');
69
+ log('Including MongoDB client (based on AppDefinition)', 'blue');
70
+ }
71
+
72
+ // Check if PostgreSQL is enabled (default to true if not specified)
73
+ const postgresEnabled = databaseConfig?.postgres?.enable !== false;
74
+ if (postgresEnabled) {
75
+ packages.push('generated/prisma-postgresql');
76
+ log('Including PostgreSQL client (based on AppDefinition)', 'blue');
77
+ }
78
+
79
+ // If neither specified, default to PostgreSQL only
80
+ if (packages.length === 0) {
81
+ packages.push('generated/prisma-postgresql');
82
+ log('No database specified - defaulting to PostgreSQL', 'yellow');
83
+ }
84
+
85
+ return packages;
86
+ }
87
+
88
+ // Configuration
89
+ // Script runs from integration project root (e.g., backend/)
90
+ // and reads Prisma packages from @friggframework/core
91
+ const PROJECT_ROOT = process.cwd();
92
+ const CORE_PACKAGE_PATH = findCorePackage(PROJECT_ROOT);
93
+ const LAYER_OUTPUT_PATH = path.join(PROJECT_ROOT, 'layers/prisma');
94
+ const LAYER_NODE_MODULES = path.join(LAYER_OUTPUT_PATH, 'nodejs/node_modules');
95
+
96
+ // Binary patterns to remove (non-rhel)
97
+ const BINARY_PATTERNS_TO_REMOVE = [
98
+ '*darwin*',
99
+ '*debian*',
100
+ '*linux-arm*',
101
+ '*linux-musl*',
102
+ '*windows*',
103
+ ];
104
+
105
+ // Files to remove for size optimization
106
+ const FILES_TO_REMOVE = [
107
+ '*.map', // Source maps (37MB savings)
108
+ '*.md', // Markdown files
109
+ 'LICENSE*', // License files
110
+ 'CHANGELOG*', // Changelog files
111
+ '*.test.js', // Test files
112
+ '*.spec.js', // Spec files
113
+ '*mysql.wasm*', // MySQL WASM files (not needed for PostgreSQL)
114
+ '*cockroachdb.wasm*', // CockroachDB WASM files
115
+ '*sqlite.wasm*', // SQLite WASM files
116
+ '*sqlserver.wasm*', // SQL Server WASM files
117
+ ];
118
+
119
+ // ANSI color codes for output
120
+ const colors = {
121
+ reset: '\x1b[0m',
122
+ bright: '\x1b[1m',
123
+ green: '\x1b[32m',
124
+ yellow: '\x1b[33m',
125
+ blue: '\x1b[34m',
126
+ red: '\x1b[31m',
127
+ };
128
+
129
+ function log(message, color = 'reset') {
130
+ console.log(`${colors[color]}${message}${colors.reset}`);
131
+ }
132
+
133
+ function logStep(step, message) {
134
+ log(`\n[${step}] ${message}`, 'blue');
135
+ }
136
+
137
+ function logSuccess(message) {
138
+ log(`✓ ${message}`, 'green');
139
+ }
140
+
141
+ function logWarning(message) {
142
+ log(`⚠ ${message}`, 'yellow');
143
+ }
144
+
145
+ function logError(message) {
146
+ log(`✗ ${message}`, 'red');
147
+ }
148
+
149
+ /**
150
+ * Get directory size in MB
151
+ */
152
+ function getDirectorySize(dirPath) {
153
+ try {
154
+ const output = execSync(`du -sm "${dirPath}"`, { encoding: 'utf8' });
155
+ const size = parseInt(output.split('\t')[0], 10);
156
+ return size;
157
+ } catch (error) {
158
+ logWarning(`Could not calculate directory size: ${error.message}`);
159
+ return 0;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Clean existing layer directory
165
+ */
166
+ async function cleanLayerDirectory() {
167
+ logStep(1, 'Cleaning existing layer directory');
168
+
169
+ if (await fs.pathExists(LAYER_OUTPUT_PATH)) {
170
+ await fs.remove(LAYER_OUTPUT_PATH);
171
+ logSuccess(`Removed existing layer at ${LAYER_OUTPUT_PATH}`);
172
+ } else {
173
+ log('No existing layer to clean');
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Create layer directory structure
179
+ */
180
+ async function createLayerStructure() {
181
+ logStep(2, 'Creating layer directory structure');
182
+
183
+ await fs.ensureDir(LAYER_NODE_MODULES);
184
+ logSuccess(`Created ${LAYER_NODE_MODULES}`);
185
+ }
186
+
187
+ /**
188
+ * Install Prisma client directly into layer (RUNTIME ONLY - NO CLI)
189
+ *
190
+ * The Prisma CLI is NOT included in this layer to keep it small.
191
+ * For migrations, the dbMigrate function has its own separate packaging with CLI.
192
+ */
193
+ async function installPrismaPackages() {
194
+ logStep(3, 'Installing Prisma runtime client (CLI excluded)');
195
+
196
+ // Create a minimal package.json with ONLY the runtime client
197
+ const dependencies = {
198
+ '@prisma/client': '^6.16.3',
199
+ };
200
+
201
+ log(' Runtime client only - CLI excluded (saves ~82MB)', 'green');
202
+
203
+ const layerPackageJson = {
204
+ name: 'prisma-lambda-layer',
205
+ version: '1.0.0',
206
+ private: true,
207
+ dependencies,
208
+ };
209
+
210
+ const packageJsonPath = path.join(LAYER_OUTPUT_PATH, 'nodejs/package.json');
211
+ await fs.writeJson(packageJsonPath, layerPackageJson, { spaces: 2 });
212
+ logSuccess('Created layer package.json');
213
+
214
+ // Install Prisma packages with Lambda binary target
215
+ // Setting PRISMA_CLI_BINARY_TARGETS ensures only rhel-openssl-3.0.x binary is downloaded
216
+ log('Installing @prisma/client for AWS Lambda (rhel-openssl-3.0.x)...');
217
+ try {
218
+ const env = {
219
+ ...process.env,
220
+ PRISMA_CLI_BINARY_TARGETS: 'rhel-openssl-3.0.x',
221
+ };
222
+
223
+ execSync('npm install --omit=dev --no-package-lock', {
224
+ cwd: path.join(LAYER_OUTPUT_PATH, 'nodejs'),
225
+ stdio: 'inherit',
226
+ env,
227
+ });
228
+ logSuccess('Prisma packages installed with Lambda binary target');
229
+ } catch (error) {
230
+ throw new Error(`Failed to install Prisma packages: ${error.message}`);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Copy generated Prisma clients from @friggframework/core to layer
236
+ * @param {Array} clientPackages - List of generated client packages to copy
237
+ */
238
+ async function copyPrismaPackages(clientPackages) {
239
+ logStep(4, 'Copying generated Prisma clients from @friggframework/core');
240
+
241
+ // Copy the generated clients from core based on database config
242
+ // Packages can be in:
243
+ // 1. Core's own node_modules (if not hoisted)
244
+ // 2. Project root node_modules (if hoisted from project)
245
+ // 3. Workspace root node_modules (where core is located - handles hoisting)
246
+ // 4. Core package itself (for generated/ directories)
247
+ const workspaceNodeModules = path.join(path.dirname(CORE_PACKAGE_PATH), '..');
248
+ const searchPaths = [
249
+ path.join(CORE_PACKAGE_PATH, 'node_modules'), // Core's own node_modules
250
+ path.join(PROJECT_ROOT, 'node_modules'), // Project node_modules
251
+ workspaceNodeModules, // Workspace root node_modules
252
+ CORE_PACKAGE_PATH, // Core package itself (for generated/)
253
+ ];
254
+
255
+ let copiedCount = 0;
256
+ let missingPackages = [];
257
+
258
+ for (const pkg of clientPackages) {
259
+ let sourcePath = null;
260
+
261
+ // Try to find package in search paths
262
+ for (const searchPath of searchPaths) {
263
+ const candidatePath = path.join(searchPath, pkg);
264
+ if (await fs.pathExists(candidatePath)) {
265
+ sourcePath = candidatePath;
266
+ break;
267
+ }
268
+ }
269
+
270
+ if (sourcePath) {
271
+ const destPath = path.join(LAYER_NODE_MODULES, pkg);
272
+ await fs.copy(sourcePath, destPath, {
273
+ dereference: true, // Follow symlinks
274
+ filter: (src) => {
275
+ // Skip node_modules within Prisma packages
276
+ return !src.includes('/node_modules/node_modules/');
277
+ }
278
+ });
279
+ const fromLocation = sourcePath.includes('@friggframework/core/generated')
280
+ ? 'core package (generated)'
281
+ : sourcePath.includes('@friggframework/core/node_modules')
282
+ ? 'core node_modules'
283
+ : 'workspace';
284
+ logSuccess(`Copied ${pkg} (from ${fromLocation})`);
285
+ copiedCount++;
286
+ } else {
287
+ missingPackages.push(pkg);
288
+ logWarning(`Package not found: ${pkg}`);
289
+ }
290
+ }
291
+
292
+ if (missingPackages.length > 0) {
293
+ throw new Error(
294
+ `Missing generated Prisma clients: ${missingPackages.join(', ')}.\n` +
295
+ 'Ensure @friggframework/core has generated Prisma clients (run "npm run prisma:generate" in core package).'
296
+ );
297
+ }
298
+
299
+ logSuccess(`Copied ${copiedCount} generated client packages from @friggframework/core`);
300
+ }
301
+
302
+ /**
303
+ * Remove unnecessary files to reduce layer size
304
+ */
305
+ async function removeUnnecessaryFiles() {
306
+ logStep(5, 'Removing unnecessary files (source maps, docs, tests)');
307
+
308
+ let removedCount = 0;
309
+ let totalSize = 0;
310
+
311
+ for (const pattern of FILES_TO_REMOVE) {
312
+ try {
313
+ // Find files matching the pattern
314
+ const findCmd = `find "${LAYER_NODE_MODULES}" -name "${pattern}" -type f 2>/dev/null || true`;
315
+ const files = execSync(findCmd, { encoding: 'utf8' })
316
+ .split('\n')
317
+ .filter(f => f.trim());
318
+
319
+ for (const file of files) {
320
+ if (await fs.pathExists(file)) {
321
+ const stats = await fs.stat(file);
322
+ totalSize += stats.size;
323
+ await fs.remove(file);
324
+ removedCount++;
325
+ }
326
+ }
327
+ } catch (error) {
328
+ logWarning(`Error removing ${pattern}: ${error.message}`);
329
+ }
330
+ }
331
+
332
+ if (removedCount > 0) {
333
+ const sizeMB = (totalSize / (1024 * 1024)).toFixed(2);
334
+ logSuccess(`Removed ${removedCount} unnecessary files (saved ${sizeMB} MB)`);
335
+ } else {
336
+ log('No unnecessary files found to remove');
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Remove non-rhel engine binaries to reduce layer size
342
+ */
343
+ async function removeNonRhelBinaries() {
344
+ logStep(6, 'Removing non-rhel engine binaries');
345
+
346
+ let removedCount = 0;
347
+ let totalSize = 0;
348
+
349
+ for (const pattern of BINARY_PATTERNS_TO_REMOVE) {
350
+ try {
351
+ // Find files matching the pattern
352
+ const findCmd = `find "${LAYER_NODE_MODULES}" -name "${pattern}" 2>/dev/null || true`;
353
+ const files = execSync(findCmd, { encoding: 'utf8' })
354
+ .split('\n')
355
+ .filter(f => f.trim());
356
+
357
+ for (const file of files) {
358
+ if (await fs.pathExists(file)) {
359
+ const stats = await fs.stat(file);
360
+ totalSize += stats.size;
361
+ await fs.remove(file);
362
+ removedCount++;
363
+ }
364
+ }
365
+ } catch (error) {
366
+ logWarning(`Error removing ${pattern}: ${error.message}`);
367
+ }
368
+ }
369
+
370
+ if (removedCount > 0) {
371
+ const sizeMB = (totalSize / (1024 * 1024)).toFixed(2);
372
+ logSuccess(`Removed ${removedCount} non-rhel binaries (saved ${sizeMB} MB)`);
373
+ } else {
374
+ log('No non-rhel binaries found to remove');
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Verify rhel binaries are present
380
+ * @param {Array} expectedClients - List of client packages that should have binaries
381
+ */
382
+ async function verifyRhelBinaries(expectedClients) {
383
+ logStep(7, 'Verifying rhel-openssl-3.0.x binaries are present');
384
+
385
+ try {
386
+ const findCmd = `find "${LAYER_NODE_MODULES}" -name "*rhel-openssl-3.0.x*" 2>/dev/null || true`;
387
+ const rhelFiles = execSync(findCmd, { encoding: 'utf8' })
388
+ .split('\n')
389
+ .filter(f => f.trim());
390
+
391
+ if (rhelFiles.length === 0) {
392
+ throw new Error(
393
+ 'No rhel-openssl-3.0.x binaries found!\n' +
394
+ 'Check that Prisma schemas have binaryTargets = ["native", "rhel-openssl-3.0.x"]'
395
+ );
396
+ }
397
+
398
+ const expectedCount = expectedClients.length;
399
+ logSuccess(`Found ${rhelFiles.length} rhel-openssl-3.0.x ${rhelFiles.length === 1 ? 'binary' : 'binaries'} (expected ${expectedCount})`);
400
+
401
+ // Show the binaries found
402
+ rhelFiles.forEach(file => {
403
+ const relativePath = path.relative(LAYER_NODE_MODULES, file);
404
+ log(` - ${relativePath}`, 'reset');
405
+ });
406
+ } catch (error) {
407
+ throw new Error(`Binary verification failed: ${error.message}`);
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Verify required files exist
413
+ * Runtime layer should NOT have CLI files
414
+ * @param {Array} clientPackages - Generated client packages that were included
415
+ */
416
+ async function verifyLayerStructure(clientPackages) {
417
+ logStep(8, 'Verifying layer structure (runtime only)');
418
+
419
+ const requiredPaths = [
420
+ '@prisma/client/runtime',
421
+ '@prisma/client/index.d.ts',
422
+ ];
423
+
424
+ // Add schema.prisma for each included client
425
+ for (const pkg of clientPackages) {
426
+ requiredPaths.push(`${pkg}/schema.prisma`);
427
+ }
428
+
429
+ // Verify CLI is NOT present (keeps layer small)
430
+ const forbiddenPaths = [
431
+ 'prisma/build',
432
+ '.bin/prisma',
433
+ ];
434
+
435
+ let allPresent = true;
436
+
437
+ for (const requiredPath of requiredPaths) {
438
+ const fullPath = path.join(LAYER_NODE_MODULES, requiredPath);
439
+ if (await fs.pathExists(fullPath)) {
440
+ log(` ✓ ${requiredPath}`, 'green');
441
+ } else {
442
+ log(` ✗ ${requiredPath} (missing)`, 'red');
443
+ allPresent = false;
444
+ }
445
+ }
446
+
447
+ if (!allPresent) {
448
+ throw new Error('Layer structure verification failed - missing required files');
449
+ }
450
+
451
+ logSuccess('All required runtime files present');
452
+
453
+ // Verify CLI is NOT present
454
+ for (const forbiddenPath of forbiddenPaths) {
455
+ const fullPath = path.join(LAYER_NODE_MODULES, forbiddenPath);
456
+ if (await fs.pathExists(fullPath)) {
457
+ logWarning(` ⚠ ${forbiddenPath} found (should be excluded for minimal layer)`);
458
+ }
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Calculate and display final layer size
464
+ */
465
+ async function displayLayerSummary() {
466
+ logStep(9, 'Layer build summary');
467
+
468
+ const layerSizeMB = getDirectorySize(LAYER_OUTPUT_PATH);
469
+
470
+ log('\n' + '='.repeat(60), 'bright');
471
+ log(' Prisma Lambda Layer Built Successfully!', 'bright');
472
+ log('='.repeat(60), 'bright');
473
+
474
+ log(`\nLayer location: ${LAYER_OUTPUT_PATH}`, 'blue');
475
+ log(`Layer size: ~${layerSizeMB} MB`, 'green');
476
+
477
+ log('\nPackages included:', 'bright');
478
+ log(' - @prisma/client (runtime only - ~10-15MB)', 'reset');
479
+ log(' - generated/prisma-postgresql (PostgreSQL client)', 'reset');
480
+ log('\nPackages EXCLUDED for minimal size:', 'bright');
481
+ log(' - prisma CLI (excluded - only in dbMigrate function)', 'yellow');
482
+ log(' - @prisma/engines (minimal - only rhel binary)', 'yellow');
483
+
484
+ log('\nNext steps:', 'bright');
485
+ log(' 1. Verify layer structure: ls -lah layers/prisma/nodejs/node_modules/', 'reset');
486
+ log(' 2. Deploy to AWS: frigg deploy --stage dev', 'reset');
487
+ log(' 3. Check function sizes in Lambda console (should be ~45-55MB)', 'reset');
488
+
489
+ log('\nSee LAMBDA-LAYER-PRISMA.md for complete documentation.', 'yellow');
490
+ log('='.repeat(60) + '\n', 'bright');
491
+ }
492
+
493
+ /**
494
+ * Main build function
495
+ * @param {Object} databaseConfig - Database configuration from AppDefinition.database
496
+ */
497
+ async function buildPrismaLayer(databaseConfig = {}) {
498
+ const startTime = Date.now();
499
+
500
+ log('\n' + '='.repeat(60), 'bright');
501
+ log(' Building Minimal Prisma Lambda Layer (Runtime Only)', 'bright');
502
+ log('='.repeat(60) + '\n', 'bright');
503
+
504
+ // Log paths
505
+ log(`Project root: ${PROJECT_ROOT}`, 'reset');
506
+ log(`Core package: ${CORE_PACKAGE_PATH}`, 'reset');
507
+ log(`Layer output: ${LAYER_OUTPUT_PATH}\n`, 'reset');
508
+
509
+ // Determine which database clients to include
510
+ const clientPackages = getGeneratedClientPackages(databaseConfig);
511
+
512
+ try {
513
+ await cleanLayerDirectory();
514
+ await createLayerStructure();
515
+ await installPrismaPackages(); // Install runtime client only (NO CLI)
516
+ await copyPrismaPackages(clientPackages); // Copy generated clients from core
517
+ await removeUnnecessaryFiles(); // Remove source maps, docs, tests (37MB+)
518
+ await removeNonRhelBinaries(); // Remove non-Linux binaries
519
+ await verifyRhelBinaries(clientPackages); // Verify query engines present
520
+ await verifyLayerStructure(clientPackages); // Verify minimal runtime structure
521
+ await displayLayerSummary();
522
+
523
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
524
+ log(`Build completed in ${duration}s\n`, 'green');
525
+
526
+ return { success: true, duration };
527
+ } catch (error) {
528
+ logError(`\nBuild failed: ${error.message}`);
529
+
530
+ if (error.stack) {
531
+ log('\nStack trace:', 'red');
532
+ console.error(error.stack);
533
+ }
534
+
535
+ log('\nTroubleshooting:', 'yellow');
536
+ log(' 1. Ensure @friggframework/core has dependencies installed', 'reset');
537
+ log(' 2. Run "npm run prisma:generate" in core package', 'reset');
538
+ log(' 3. Check that Prisma schemas have correct binaryTargets', 'reset');
539
+ log(' 4. See LAMBDA-LAYER-PRISMA.md for details\n', 'reset');
540
+
541
+ // Throw error instead of exit when used as module
542
+ throw error;
543
+ }
544
+ }
545
+
546
+ // Run the build when executed directly
547
+ if (require.main === module) {
548
+ buildPrismaLayer()
549
+ .then(() => process.exit(0))
550
+ .catch(() => process.exit(1));
551
+ }
552
+
553
+ module.exports = { buildPrismaLayer, getGeneratedClientPackages };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Tests for Prisma Lambda Layer Builder
3
+ * Validates database client selection logic
4
+ */
5
+
6
+ const { getGeneratedClientPackages } = require('./build-prisma-layer');
7
+
8
+ // Mock the log function
9
+ jest.mock('./build-prisma-layer', () => {
10
+ const actual = jest.requireActual('./build-prisma-layer');
11
+ return {
12
+ ...actual,
13
+ getGeneratedClientPackages: actual.getGeneratedClientPackages,
14
+ };
15
+ });
16
+
17
+ describe('getGeneratedClientPackages()', () => {
18
+ it('should include MongoDB client when mongoDB.enable is true', () => {
19
+ const databaseConfig = {
20
+ mongoDB: { enable: true },
21
+ };
22
+
23
+ const packages = getGeneratedClientPackages(databaseConfig);
24
+
25
+ expect(packages).toContain('generated/prisma-mongodb');
26
+ });
27
+
28
+ it('should include MongoDB client when documentDB.enable is true', () => {
29
+ const databaseConfig = {
30
+ documentDB: { enable: true },
31
+ };
32
+
33
+ const packages = getGeneratedClientPackages(databaseConfig);
34
+
35
+ expect(packages).toContain('generated/prisma-mongodb');
36
+ });
37
+
38
+ it('should include PostgreSQL client when postgres.enable is true', () => {
39
+ const databaseConfig = {
40
+ postgres: { enable: true },
41
+ };
42
+
43
+ const packages = getGeneratedClientPackages(databaseConfig);
44
+
45
+ expect(packages).toContain('generated/prisma-postgresql');
46
+ });
47
+
48
+ it('should include both clients when both databases are enabled', () => {
49
+ const databaseConfig = {
50
+ mongoDB: { enable: true },
51
+ postgres: { enable: true },
52
+ };
53
+
54
+ const packages = getGeneratedClientPackages(databaseConfig);
55
+
56
+ expect(packages).toContain('generated/prisma-mongodb');
57
+ expect(packages).toContain('generated/prisma-postgresql');
58
+ expect(packages).toHaveLength(2);
59
+ });
60
+
61
+ it('should include both clients when documentDB and postgres are enabled', () => {
62
+ const databaseConfig = {
63
+ documentDB: { enable: true },
64
+ postgres: { enable: true },
65
+ };
66
+
67
+ const packages = getGeneratedClientPackages(databaseConfig);
68
+
69
+ expect(packages).toContain('generated/prisma-mongodb');
70
+ expect(packages).toContain('generated/prisma-postgresql');
71
+ expect(packages).toHaveLength(2);
72
+ });
73
+
74
+ it('should default to PostgreSQL when no database config provided', () => {
75
+ const packages = getGeneratedClientPackages({});
76
+
77
+ expect(packages).toContain('generated/prisma-postgresql');
78
+ expect(packages).toHaveLength(1);
79
+ });
80
+
81
+ it('should default to PostgreSQL when database config is null', () => {
82
+ const packages = getGeneratedClientPackages(null);
83
+
84
+ expect(packages).toContain('generated/prisma-postgresql');
85
+ expect(packages).toHaveLength(1);
86
+ });
87
+
88
+ it('should only include MongoDB when postgres.enable is false and mongoDB is true', () => {
89
+ const databaseConfig = {
90
+ mongoDB: { enable: true },
91
+ postgres: { enable: false },
92
+ };
93
+
94
+ const packages = getGeneratedClientPackages(databaseConfig);
95
+
96
+ expect(packages).toContain('generated/prisma-mongodb');
97
+ expect(packages).not.toContain('generated/prisma-postgresql');
98
+ expect(packages).toHaveLength(1);
99
+ });
100
+ });
101
+
102
+