@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,134 @@
1
+ /**
2
+ * Handler Path Resolver Utility
3
+ *
4
+ * Utility Layer - Hexagonal Architecture
5
+ *
6
+ * Handles Lambda handler path resolution for offline mode compatibility.
7
+ * In offline mode, handler paths need to be relative to the working directory
8
+ * rather than absolute paths to node_modules.
9
+ */
10
+
11
+ const path = require('path');
12
+ const fs = require('fs');
13
+
14
+ /**
15
+ * Find node_modules path for offline mode handler path resolution
16
+ *
17
+ * Searches upward from current directory to locate node_modules directory
18
+ * using multiple fallback strategies.
19
+ *
20
+ * @returns {string} Path to node_modules directory
21
+ */
22
+ function findNodeModulesPath() {
23
+ try {
24
+ let currentDir = process.cwd();
25
+ let nodeModulesPath = null;
26
+
27
+ // Strategy 1: Search upward through directory tree
28
+ for (let i = 0; i < 5; i++) {
29
+ const potentialPath = path.join(currentDir, 'node_modules');
30
+ if (fs.existsSync(potentialPath)) {
31
+ nodeModulesPath = potentialPath;
32
+ console.log(
33
+ `Found node_modules at: ${nodeModulesPath} (method 1)`
34
+ );
35
+ break;
36
+ }
37
+ const parentDir = path.dirname(currentDir);
38
+ if (parentDir === currentDir) break;
39
+ currentDir = parentDir;
40
+ }
41
+
42
+ // Strategy 2: Use npm root command
43
+ if (!nodeModulesPath) {
44
+ try {
45
+ const { execSync } = require('node:child_process');
46
+ const npmRoot = execSync('npm root', {
47
+ encoding: 'utf8',
48
+ }).trim();
49
+ if (fs.existsSync(npmRoot)) {
50
+ nodeModulesPath = npmRoot;
51
+ console.log(
52
+ `Found node_modules at: ${nodeModulesPath} (method 2)`
53
+ );
54
+ }
55
+ } catch (npmError) {
56
+ console.error('Error executing npm root:', npmError);
57
+ }
58
+ }
59
+
60
+ // Strategy 3: Search from package.json locations
61
+ if (!nodeModulesPath) {
62
+ currentDir = process.cwd();
63
+ for (let i = 0; i < 5; i++) {
64
+ const packageJsonPath = path.join(currentDir, 'package.json');
65
+ if (fs.existsSync(packageJsonPath)) {
66
+ const potentialNodeModules = path.join(
67
+ currentDir,
68
+ 'node_modules'
69
+ );
70
+ if (fs.existsSync(potentialNodeModules)) {
71
+ nodeModulesPath = potentialNodeModules;
72
+ console.log(
73
+ `Found node_modules at: ${nodeModulesPath} (method 3)`
74
+ );
75
+ break;
76
+ }
77
+ }
78
+ const parentDir = path.dirname(currentDir);
79
+ if (parentDir === currentDir) break;
80
+ currentDir = parentDir;
81
+ }
82
+ }
83
+
84
+ if (nodeModulesPath) {
85
+ return nodeModulesPath;
86
+ }
87
+
88
+ // Fallback: Assume parent directory
89
+ console.warn(
90
+ 'Could not find node_modules path, falling back to default'
91
+ );
92
+ return path.resolve(process.cwd(), '../node_modules');
93
+ } catch (error) {
94
+ console.error('Error finding node_modules path:', error);
95
+ return path.resolve(process.cwd(), '../node_modules');
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Modify handler paths for offline mode
101
+ *
102
+ * In serverless-offline mode, handler paths need to be relative
103
+ * to the current working directory rather than using absolute
104
+ * node_modules paths.
105
+ *
106
+ * @param {Object} functions - Serverless functions configuration
107
+ * @returns {Object} Functions with modified handler paths
108
+ */
109
+ function modifyHandlerPaths(functions) {
110
+ const isOffline = process.argv.includes('offline');
111
+ console.log('isOffline', isOffline);
112
+
113
+ if (!isOffline) {
114
+ console.log('Not in offline mode, skipping handler path modification');
115
+ // Return shallow copy to prevent mutations (DDD immutability principle)
116
+ return { ...functions };
117
+ }
118
+
119
+ // In offline mode, don't modify the handler paths at all
120
+ // serverless-offline will resolve node_modules paths from the working directory
121
+ console.log('Offline mode detected - keeping original handler paths for serverless-offline');
122
+
123
+ // Return deep copy to prevent mutations (DDD immutability principle)
124
+ return Object.entries(functions).reduce((acc, [key, value]) => {
125
+ acc[key] = { ...value };
126
+ return acc;
127
+ }, {});
128
+ }
129
+
130
+ module.exports = {
131
+ findNodeModulesPath,
132
+ modifyHandlerPaths,
133
+ };
134
+
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Tests for Handler Path Resolver Utility
3
+ *
4
+ * Tests offline mode handler path resolution
5
+ */
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const { findNodeModulesPath, modifyHandlerPaths } = require('./handler-path-resolver');
10
+
11
+ // Mock fs and child_process
12
+ jest.mock('fs');
13
+ jest.mock('node:child_process', () => ({
14
+ execSync: jest.fn(),
15
+ }));
16
+
17
+ describe('Handler Path Resolver', () => {
18
+ let originalArgv;
19
+ let originalCwd;
20
+
21
+ beforeEach(() => {
22
+ originalArgv = process.argv;
23
+ originalCwd = process.cwd;
24
+ jest.clearAllMocks();
25
+ });
26
+
27
+ afterEach(() => {
28
+ process.argv = originalArgv;
29
+ process.cwd = originalCwd;
30
+ });
31
+
32
+ describe('findNodeModulesPath()', () => {
33
+ it('should find node_modules in current directory (method 1)', () => {
34
+ const mockCwd = '/project';
35
+ process.cwd = jest.fn().mockReturnValue(mockCwd);
36
+ fs.existsSync = jest.fn().mockImplementation((p) =>
37
+ p === path.join(mockCwd, 'node_modules')
38
+ );
39
+
40
+ const result = findNodeModulesPath();
41
+
42
+ expect(result).toBe(path.join(mockCwd, 'node_modules'));
43
+ });
44
+
45
+ it('should search parent directories if not found in current (method 1)', () => {
46
+ const mockCwd = '/project/nested/deep';
47
+ process.cwd = jest.fn().mockReturnValue(mockCwd);
48
+ fs.existsSync = jest.fn().mockImplementation((p) =>
49
+ p === '/project/node_modules'
50
+ );
51
+
52
+ const result = findNodeModulesPath();
53
+
54
+ expect(result).toBe('/project/node_modules');
55
+ expect(fs.existsSync).toHaveBeenCalledWith('/project/nested/deep/node_modules');
56
+ expect(fs.existsSync).toHaveBeenCalledWith('/project/nested/node_modules');
57
+ expect(fs.existsSync).toHaveBeenCalledWith('/project/node_modules');
58
+ });
59
+
60
+ it('should use npm root if directory search fails (method 2)', () => {
61
+ process.cwd = jest.fn().mockReturnValue('/some/unusual/directory');
62
+
63
+ const { execSync } = require('node:child_process');
64
+ // npm root returns a different path
65
+ execSync.mockReturnValue('/usr/local/lib/node_modules\n');
66
+
67
+ // Mock to fail directory searches but succeed for npm root result
68
+ fs.existsSync = jest.fn().mockImplementation((p) => {
69
+ // Only succeed for the npm root path (not under /some/unusual/directory)
70
+ return p === '/usr/local/lib/node_modules';
71
+ });
72
+
73
+ const result = findNodeModulesPath();
74
+
75
+ expect(result).toBe('/usr/local/lib/node_modules');
76
+ expect(execSync).toHaveBeenCalledWith('npm root', { encoding: 'utf8' });
77
+ });
78
+
79
+ it('should handle npm root errors gracefully', () => {
80
+ process.cwd = jest.fn().mockReturnValue('/project');
81
+ fs.existsSync = jest.fn().mockReturnValue(false);
82
+
83
+ const { execSync } = require('node:child_process');
84
+ execSync.mockImplementation(() => {
85
+ throw new Error('npm not found');
86
+ });
87
+
88
+ const result = findNodeModulesPath();
89
+
90
+ // Should fall back to default
91
+ expect(result).toBe(path.resolve('/project', '../node_modules'));
92
+ });
93
+
94
+ it('should search from package.json locations (method 3)', () => {
95
+ process.cwd = jest.fn().mockReturnValue('/project/workspace');
96
+
97
+ let callCount = 0;
98
+ fs.existsSync = jest.fn().mockImplementation((p) => {
99
+ callCount++;
100
+ // First 5 calls fail (directory search)
101
+ if (callCount <= 5) return false;
102
+ // Package.json search
103
+ if (p === '/project/package.json') return true;
104
+ if (p === '/project/node_modules') return true;
105
+ return false;
106
+ });
107
+
108
+ const { execSync } = require('node:child_process');
109
+ execSync.mockImplementation(() => {
110
+ throw new Error('npm root failed');
111
+ });
112
+
113
+ const result = findNodeModulesPath();
114
+
115
+ expect(result).toBe('/project/node_modules');
116
+ });
117
+
118
+ it('should fall back to default path if all methods fail', () => {
119
+ process.cwd = jest.fn().mockReturnValue('/project');
120
+ fs.existsSync = jest.fn().mockReturnValue(false);
121
+
122
+ const { execSync } = require('node:child_process');
123
+ execSync.mockImplementation(() => {
124
+ throw new Error('npm root failed');
125
+ });
126
+
127
+ const result = findNodeModulesPath();
128
+
129
+ expect(result).toBe(path.resolve('/project', '../node_modules'));
130
+ });
131
+
132
+ it('should handle errors during search', () => {
133
+ process.cwd = jest.fn().mockReturnValue('/project');
134
+ // Mock fs.existsSync to throw an error
135
+ fs.existsSync = jest.fn().mockImplementation(() => {
136
+ throw new Error('fs error');
137
+ });
138
+
139
+ const { execSync } = require('node:child_process');
140
+ execSync.mockImplementation(() => {
141
+ throw new Error('npm error');
142
+ });
143
+
144
+ const result = findNodeModulesPath();
145
+
146
+ // Should fallback to default path even when search methods fail
147
+ expect(result).toBe(path.resolve('/project', '../node_modules'));
148
+ });
149
+ });
150
+
151
+ describe('modifyHandlerPaths()', () => {
152
+ it('should not modify paths when not in offline mode', () => {
153
+ process.argv = ['node', 'test'];
154
+
155
+ const functions = {
156
+ auth: {
157
+ handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
158
+ },
159
+ };
160
+
161
+ const result = modifyHandlerPaths(functions);
162
+
163
+ expect(result.auth.handler).toBe('node_modules/@friggframework/core/handlers/routers/auth.handler');
164
+ });
165
+
166
+ it('should modify handler paths in offline mode', () => {
167
+ process.argv = ['node', 'test', 'offline'];
168
+ process.cwd = jest.fn().mockReturnValue('/project');
169
+ fs.existsSync = jest.fn().mockImplementation((p) =>
170
+ p === '/project/node_modules'
171
+ );
172
+
173
+ const functions = {
174
+ auth: {
175
+ handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
176
+ },
177
+ };
178
+
179
+ const result = modifyHandlerPaths(functions);
180
+
181
+ expect(result.auth.handler).toBe('node_modules/@friggframework/core/handlers/routers/auth.handler');
182
+ });
183
+
184
+ it('should handle functions without handlers', () => {
185
+ process.argv = ['node', 'test', 'offline'];
186
+
187
+ const functions = {
188
+ noHandler: {
189
+ events: [{ http: { path: '/test' } }],
190
+ },
191
+ };
192
+
193
+ const result = modifyHandlerPaths(functions);
194
+
195
+ expect(result.noHandler.handler).toBeUndefined();
196
+ });
197
+
198
+ it('should only modify handlers with node_modules path', () => {
199
+ process.argv = ['node', 'test', 'offline'];
200
+ process.cwd = jest.fn().mockReturnValue('/project');
201
+ fs.existsSync = jest.fn().mockImplementation((p) =>
202
+ p === '/project/node_modules'
203
+ );
204
+
205
+ const functions = {
206
+ coreHandler: {
207
+ handler: 'node_modules/@friggframework/core/handlers/auth.handler',
208
+ },
209
+ customHandler: {
210
+ handler: 'src/handlers/custom.handler',
211
+ },
212
+ };
213
+
214
+ const result = modifyHandlerPaths(functions);
215
+
216
+ expect(result.coreHandler.handler).toContain('node_modules');
217
+ expect(result.customHandler.handler).toBe('src/handlers/custom.handler');
218
+ });
219
+
220
+ it('should not mutate original functions object', () => {
221
+ process.argv = ['node', 'test', 'offline'];
222
+ process.cwd = jest.fn().mockReturnValue('/project');
223
+ fs.existsSync = jest.fn().mockImplementation((p) =>
224
+ p === '/project/node_modules'
225
+ );
226
+
227
+ const original = {
228
+ auth: {
229
+ handler: 'node_modules/@friggframework/core/handlers/auth.handler',
230
+ },
231
+ };
232
+
233
+ const result = modifyHandlerPaths(original);
234
+
235
+ // Result should be a copy
236
+ expect(result).not.toBe(original);
237
+ expect(result.auth).not.toBe(original.auth);
238
+ });
239
+
240
+ it('should handle multiple functions', () => {
241
+ process.argv = ['node', 'test', 'offline'];
242
+ process.cwd = jest.fn().mockReturnValue('/project');
243
+ fs.existsSync = jest.fn().mockImplementation((p) =>
244
+ p === '/project/node_modules'
245
+ );
246
+
247
+ const functions = {
248
+ auth: {
249
+ handler: 'node_modules/@friggframework/core/handlers/auth.handler',
250
+ },
251
+ user: {
252
+ handler: 'node_modules/@friggframework/core/handlers/user.handler',
253
+ },
254
+ health: {
255
+ handler: 'node_modules/@friggframework/core/handlers/health.handler',
256
+ },
257
+ };
258
+
259
+ const result = modifyHandlerPaths(functions);
260
+
261
+ expect(Object.keys(result)).toHaveLength(3);
262
+ expect(result.auth.handler).toContain('node_modules');
263
+ expect(result.user.handler).toContain('node_modules');
264
+ expect(result.health.handler).toContain('node_modules');
265
+ });
266
+ });
267
+ });
268
+
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Prisma Lambda Layer Manager
3
+ *
4
+ * Utility Layer - Hexagonal Architecture
5
+ *
6
+ * Manages Prisma Lambda Layer for serverless deployments.
7
+ * Ensures the layer exists and is built before deployment.
8
+ */
9
+
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+ const { buildPrismaLayer } = require('../../../scripts/build-prisma-layer');
13
+
14
+ /**
15
+ * Ensure Prisma Lambda Layer exists
16
+ *
17
+ * Automatically builds the layer if it doesn't exist.
18
+ * The layer contains ONLY the Prisma runtime client (minimal, ~10-15MB).
19
+ * Prisma CLI is bundled separately in the dbMigrate function.
20
+ *
21
+ * @param {Object} databaseConfig - Database configuration from app definition
22
+ * @returns {Promise<void>}
23
+ * @throws {Error} If layer build fails
24
+ */
25
+ async function ensurePrismaLayerExists(databaseConfig = {}) {
26
+ const projectRoot = process.cwd();
27
+ const layerPath = path.join(projectRoot, 'layers/prisma');
28
+
29
+ // Check if layer already exists
30
+ if (fs.existsSync(layerPath)) {
31
+ console.log('✓ Prisma Lambda Layer already exists at', layerPath);
32
+ return;
33
+ }
34
+
35
+ // Layer doesn't exist - build it automatically
36
+ console.log('📦 Prisma Lambda Layer not found - building automatically...');
37
+ console.log(' Building MINIMAL layer (runtime only, NO CLI)');
38
+ console.log(' CLI is packaged separately in dbMigrate function');
39
+ console.log(' This may take a minute on first deployment.\n');
40
+
41
+ try {
42
+ // Build layer WITHOUT CLI (runtime only for minimal size)
43
+ await buildPrismaLayer(databaseConfig);
44
+ console.log('✓ Prisma Lambda Layer built successfully (~10-15MB)\n');
45
+ } catch (error) {
46
+ console.error('✗ Failed to build Prisma Lambda Layer:', error.message);
47
+ console.error(' You may need to run: npm install @friggframework/core\n');
48
+ throw error;
49
+ }
50
+ }
51
+
52
+ module.exports = {
53
+ ensurePrismaLayerExists,
54
+ };
55
+
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Tests for Prisma Layer Manager
3
+ *
4
+ * Tests Prisma Lambda Layer existence checking and building
5
+ */
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const { ensurePrismaLayerExists } = require('./prisma-layer-manager');
10
+
11
+ // Mock fs and buildPrismaLayer
12
+ jest.mock('fs');
13
+ jest.mock('../../../scripts/build-prisma-layer', () => ({
14
+ buildPrismaLayer: jest.fn(),
15
+ }));
16
+
17
+ const { buildPrismaLayer } = require('../../../scripts/build-prisma-layer');
18
+
19
+ describe('Prisma Layer Manager', () => {
20
+ let originalCwd;
21
+
22
+ beforeEach(() => {
23
+ originalCwd = process.cwd;
24
+ process.cwd = jest.fn().mockReturnValue('/project');
25
+ jest.clearAllMocks();
26
+ });
27
+
28
+ afterEach(() => {
29
+ process.cwd = originalCwd;
30
+ });
31
+
32
+ describe('ensurePrismaLayerExists()', () => {
33
+ it('should skip build if layer already exists', async () => {
34
+ fs.existsSync = jest.fn().mockReturnValue(true);
35
+
36
+ await ensurePrismaLayerExists();
37
+
38
+ expect(fs.existsSync).toHaveBeenCalledWith('/project/layers/prisma');
39
+ expect(buildPrismaLayer).not.toHaveBeenCalled();
40
+ });
41
+
42
+ it('should build layer if it does not exist', async () => {
43
+ fs.existsSync = jest.fn().mockReturnValue(false);
44
+ buildPrismaLayer.mockResolvedValue();
45
+
46
+ await ensurePrismaLayerExists();
47
+
48
+ expect(buildPrismaLayer).toHaveBeenCalledTimes(1);
49
+ });
50
+
51
+ it('should pass database config to buildPrismaLayer', async () => {
52
+ fs.existsSync = jest.fn().mockReturnValue(false);
53
+ buildPrismaLayer.mockResolvedValue();
54
+
55
+ const databaseConfig = {
56
+ postgres: { enable: true },
57
+ };
58
+
59
+ await ensurePrismaLayerExists(databaseConfig);
60
+
61
+ expect(buildPrismaLayer).toHaveBeenCalledWith(databaseConfig);
62
+ });
63
+
64
+ it('should handle build errors and rethrow', async () => {
65
+ fs.existsSync = jest.fn().mockReturnValue(false);
66
+ buildPrismaLayer.mockRejectedValue(new Error('Build failed'));
67
+
68
+ await expect(ensurePrismaLayerExists()).rejects.toThrow('Build failed');
69
+ });
70
+
71
+ it('should default to empty database config', async () => {
72
+ fs.existsSync = jest.fn().mockReturnValue(false);
73
+ buildPrismaLayer.mockResolvedValue();
74
+
75
+ await ensurePrismaLayerExists();
76
+
77
+ expect(buildPrismaLayer).toHaveBeenCalledWith({});
78
+ });
79
+
80
+ it('should use correct layer path relative to project root', async () => {
81
+ process.cwd = jest.fn().mockReturnValue('/custom/project/path');
82
+ fs.existsSync = jest.fn().mockReturnValue(true);
83
+
84
+ await ensurePrismaLayerExists();
85
+
86
+ expect(fs.existsSync).toHaveBeenCalledWith('/custom/project/path/layers/prisma');
87
+ });
88
+
89
+ it('should log success when layer already exists', async () => {
90
+ fs.existsSync = jest.fn().mockReturnValue(true);
91
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
92
+
93
+ await ensurePrismaLayerExists();
94
+
95
+ // console.log is called with 2 args: message + path
96
+ expect(consoleSpy).toHaveBeenCalledWith(
97
+ expect.stringContaining('already exists'),
98
+ expect.any(String)
99
+ );
100
+
101
+ consoleSpy.mockRestore();
102
+ });
103
+
104
+ it('should log build progress when building layer', async () => {
105
+ fs.existsSync = jest.fn().mockReturnValue(false);
106
+ buildPrismaLayer.mockResolvedValue();
107
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
108
+
109
+ await ensurePrismaLayerExists();
110
+
111
+ expect(consoleSpy).toHaveBeenCalledWith(
112
+ expect.stringContaining('building automatically')
113
+ );
114
+ expect(consoleSpy).toHaveBeenCalledWith(
115
+ expect.stringContaining('built successfully')
116
+ );
117
+
118
+ consoleSpy.mockRestore();
119
+ });
120
+
121
+ it('should log error message on build failure', async () => {
122
+ fs.existsSync = jest.fn().mockReturnValue(false);
123
+ buildPrismaLayer.mockRejectedValue(new Error('Build error'));
124
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
125
+
126
+ await expect(ensurePrismaLayerExists()).rejects.toThrow();
127
+
128
+ // console.error is called with 2 args: message + error
129
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
130
+ expect.stringContaining('Failed to build'),
131
+ expect.any(String)
132
+ );
133
+
134
+ consoleErrorSpy.mockRestore();
135
+ });
136
+ });
137
+ });
138
+
@@ -23,7 +23,8 @@ const validateEnvironmentVariables = (AppDefinition) => {
23
23
 
24
24
  for (const [key, value] of Object.entries(AppDefinition.environment)) {
25
25
  if (value === true) {
26
- if (process.env[key]) {
26
+ // Use 'in' operator to check if key exists (undefined = missing, empty string = present)
27
+ if (key in process.env) {
27
28
  results.valid.push(key);
28
29
  } else {
29
30
  results.missing.push(key);