@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,567 @@
1
+ /**
2
+ * Admin Script Builder
3
+ *
4
+ * Domain Layer - Hexagonal Architecture
5
+ *
6
+ * Responsible for:
7
+ * - Creating SQS queue for admin script execution
8
+ * - Creating Lambda function for script execution (worker)
9
+ * - Creating Lambda function for admin API routes (router)
10
+ * - Creating EventBridge Scheduler resources (Phase 2)
11
+ * - Creating IAM roles for scheduler to invoke Lambda
12
+ * - Granting the router/executor Lambdas IAM permission to send to the queue
13
+ * and (when scheduling is enabled) manage EventBridge schedules
14
+ */
15
+
16
+ const { InfrastructureBuilder, ValidationResult } = require('../shared/base-builder');
17
+ const { isScopedEnvironmentActive } = require('../shared/function-environments');
18
+ const { nestedNodeModulesExcludes } = require('../shared/utilities/nested-node-modules');
19
+
20
+ class AdminScriptBuilder extends InfrastructureBuilder {
21
+ constructor() {
22
+ super();
23
+ this.name = 'AdminScriptBuilder';
24
+ }
25
+
26
+ shouldExecute(appDefinition) {
27
+ const hasScripts =
28
+ Array.isArray(appDefinition.adminScripts) &&
29
+ appDefinition.adminScripts.length > 0;
30
+ const hasReports =
31
+ Array.isArray(appDefinition.reports) &&
32
+ appDefinition.reports.length > 0;
33
+ const hasBuiltinReports =
34
+ appDefinition.admin?.includeBuiltinReports === true;
35
+ return hasScripts || hasReports || hasBuiltinReports;
36
+ }
37
+
38
+ getDependencies() {
39
+ return []; // Can run independently
40
+ }
41
+
42
+ validate(appDefinition) {
43
+ const result = new ValidationResult();
44
+
45
+ if (appDefinition.adminScripts !== undefined) {
46
+ if (!Array.isArray(appDefinition.adminScripts)) {
47
+ result.addError('adminScripts must be an array');
48
+ } else {
49
+ appDefinition.adminScripts.forEach((script, index) => {
50
+ if (!script?.Definition?.name) {
51
+ result.addError(`Admin script at index ${index} is missing Definition or name`);
52
+ }
53
+ });
54
+ }
55
+ }
56
+
57
+ if (appDefinition.reports !== undefined) {
58
+ if (!Array.isArray(appDefinition.reports)) {
59
+ result.addError('reports must be an array');
60
+ } else {
61
+ appDefinition.reports.forEach((report, index) => {
62
+ if (!report?.Definition?.name) {
63
+ result.addError(`Report at index ${index} is missing Definition or name`);
64
+ }
65
+ });
66
+ }
67
+ }
68
+
69
+ return result;
70
+ }
71
+
72
+ async build(appDefinition, discoveredResources) {
73
+ console.log(`\n[${this.name}] Configuring admin operations...`);
74
+
75
+ const usePrismaLayer = appDefinition.usePrismaLambdaLayer !== false;
76
+ const adminConfig = appDefinition.admin || {};
77
+ const adminScripts = Array.isArray(appDefinition.adminScripts)
78
+ ? appDefinition.adminScripts
79
+ : [];
80
+ const reports = Array.isArray(appDefinition.reports)
81
+ ? appDefinition.reports
82
+ : [];
83
+ const hasReports =
84
+ reports.length > 0 || adminConfig.includeBuiltinReports === true;
85
+
86
+ // Only non-JSON report output is stored in S3, so provision the bucket
87
+ // only for that. Built-in reports emit JSON, so they don't trigger it.
88
+ const reportsNeedArtifacts = reports.some((report) => {
89
+ const format = report?.Definition?.output?.format;
90
+ return Boolean(format) && format !== 'json';
91
+ });
92
+
93
+ const result = {
94
+ functions: {},
95
+ resources: {},
96
+ environment: {},
97
+ custom: {},
98
+ iamStatements: [],
99
+ };
100
+
101
+ if (adminScripts.length > 0) {
102
+ console.log(` Processing ${adminScripts.length} scripts...`);
103
+ this.createAdminScriptQueue(result, appDefinition);
104
+ this.createScriptExecutorFunction(appDefinition, result, usePrismaLayer);
105
+ this.createAdminScriptRoutes(appDefinition, result, usePrismaLayer);
106
+
107
+ adminScripts.forEach(script => {
108
+ const name = script.Definition?.name || 'unknown';
109
+ console.log(` ✓ Registered script: ${name}`);
110
+ });
111
+ }
112
+
113
+ if (hasReports) {
114
+ this.createReportQueue(result, appDefinition);
115
+ this.createReportExecutorFunction(appDefinition, result, usePrismaLayer);
116
+ this.createReportRoutes(appDefinition, result, usePrismaLayer);
117
+ if (reportsNeedArtifacts) {
118
+ this.createReportArtifactBucket(result, appDefinition);
119
+ }
120
+ reports.forEach(report => {
121
+ const name = report.Definition?.name || 'unknown';
122
+ console.log(` ✓ Registered report: ${name}`);
123
+ });
124
+ if (adminConfig.includeBuiltinReports) {
125
+ console.log(' ✓ Built-in reports enabled');
126
+ }
127
+ }
128
+
129
+ if (
130
+ adminConfig.enableScheduling &&
131
+ (adminScripts.length > 0 || hasReports)
132
+ ) {
133
+ this.createSchedulerResources(appDefinition, result, {
134
+ scriptsPresent: adminScripts.length > 0,
135
+ hasReports,
136
+ });
137
+ }
138
+
139
+ console.log(`[${this.name}] ✅ Admin operations configuration completed`);
140
+ return result;
141
+ }
142
+
143
+ createAdminScriptQueue(result, appDefinition) {
144
+ result.resources.AdminScriptQueue = {
145
+ Type: 'AWS::SQS::Queue',
146
+ Properties: {
147
+ QueueName: '${self:service}-${self:provider.stage}-AdminScriptQueue',
148
+ MessageRetentionPeriod: 86400, // 1 day
149
+ VisibilityTimeout: 900, // 15 minutes (Lambda max)
150
+ RedrivePolicy: {
151
+ maxReceiveCount: 3,
152
+ deadLetterTargetArn: {
153
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
154
+ },
155
+ },
156
+ },
157
+ };
158
+
159
+ if (isScopedEnvironmentActive(appDefinition)) {
160
+ // Only the admin functions read this queue URL
161
+ result.functionEnvironments = result.functionEnvironments || {};
162
+ for (const fnName of ['adminScriptRouter', 'adminScriptExecutor']) {
163
+ result.functionEnvironments[fnName] = {
164
+ ...result.functionEnvironments[fnName],
165
+ ADMIN_SCRIPT_QUEUE_URL: { Ref: 'AdminScriptQueue' },
166
+ };
167
+ }
168
+ } else {
169
+ result.environment.ADMIN_SCRIPT_QUEUE_URL = {
170
+ Ref: 'AdminScriptQueue',
171
+ };
172
+ }
173
+
174
+ // The router enqueues async executions and scripts enqueue continuations
175
+ // via queueScript()/queueScriptBatch(). The base role's wildcard does not
176
+ // cover this queue's name, so grant SendMessage explicitly.
177
+ result.iamStatements.push({
178
+ Effect: 'Allow',
179
+ Action: [
180
+ 'sqs:SendMessage',
181
+ 'sqs:SendMessageBatch',
182
+ 'sqs:GetQueueUrl',
183
+ 'sqs:GetQueueAttributes',
184
+ ],
185
+ Resource: { 'Fn::GetAtt': ['AdminScriptQueue', 'Arn'] },
186
+ });
187
+
188
+ console.log(' ✓ Created AdminScriptQueue');
189
+ }
190
+
191
+ createScriptExecutorFunction(appDefinition, result, usePrismaLayer) {
192
+ result.functions.adminScriptExecutor = {
193
+ handler: 'node_modules/@friggframework/admin-scripts/src/infrastructure/script-executor-handler.handler',
194
+ skipEsbuild: true,
195
+ package: this.skipEsbuildPackageConfig(appDefinition, usePrismaLayer),
196
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
197
+ timeout: 900, // 15 minutes max
198
+ memorySize: 1024,
199
+ events: [
200
+ {
201
+ sqs: {
202
+ arn: { 'Fn::GetAtt': ['AdminScriptQueue', 'Arn'] },
203
+ batchSize: 1,
204
+ },
205
+ },
206
+ ],
207
+ };
208
+ console.log(' ✓ Created adminScriptExecutor function');
209
+ }
210
+
211
+ createAdminScriptRoutes(appDefinition, result, usePrismaLayer) {
212
+ result.functions.adminScriptRouter = {
213
+ handler: 'node_modules/@friggframework/admin-scripts/src/infrastructure/admin-script-router.handler',
214
+ skipEsbuild: true,
215
+ package: this.skipEsbuildPackageConfig(appDefinition, usePrismaLayer),
216
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
217
+ timeout: 30,
218
+ events: [
219
+ // List scripts
220
+ { httpApi: { path: '/admin/scripts', method: 'GET' } },
221
+ // Get script details
222
+ { httpApi: { path: '/admin/scripts/{scriptName}', method: 'GET' } },
223
+ // Execute script (sync or async)
224
+ { httpApi: { path: '/admin/scripts/{scriptName}', method: 'POST' } },
225
+ // Validate script input (dry-run preview)
226
+ { httpApi: { path: '/admin/scripts/{scriptName}/validate', method: 'POST' } },
227
+ // List executions for a script
228
+ { httpApi: { path: '/admin/scripts/{scriptName}/executions', method: 'GET' } },
229
+ // Get a single execution
230
+ {
231
+ httpApi: {
232
+ path: '/admin/scripts/{scriptName}/executions/{executionId}',
233
+ method: 'GET',
234
+ },
235
+ },
236
+ // Schedule management (Phase 2)
237
+ { httpApi: { path: '/admin/scripts/{scriptName}/schedule', method: 'GET' } },
238
+ { httpApi: { path: '/admin/scripts/{scriptName}/schedule', method: 'PUT' } },
239
+ { httpApi: { path: '/admin/scripts/{scriptName}/schedule', method: 'DELETE' } },
240
+ ],
241
+ };
242
+ console.log(' ✓ Created adminScriptRouter function');
243
+ }
244
+
245
+ createReportRoutes(appDefinition, result, usePrismaLayer) {
246
+ result.functions.reportRouter = {
247
+ handler: 'node_modules/@friggframework/admin-scripts/src/infrastructure/report-router.handler',
248
+ skipEsbuild: true,
249
+ package: this.skipEsbuildPackageConfig(appDefinition, usePrismaLayer),
250
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
251
+ timeout: 30,
252
+ events: [
253
+ { httpApi: { path: '/api/v2/reports', method: 'GET' } },
254
+ // Definition detail, snapshots, executions, schedule, back-compat alias
255
+ { httpApi: { path: '/api/v2/reports/{proxy+}', method: 'GET' } },
256
+ // Run a report ({name}/run)
257
+ { httpApi: { path: '/api/v2/reports/{proxy+}', method: 'POST' } },
258
+ // Schedule management (PUT/DELETE {name}/schedule)
259
+ { httpApi: { path: '/api/v2/reports/{proxy+}', method: 'PUT' } },
260
+ { httpApi: { path: '/api/v2/reports/{proxy+}', method: 'DELETE' } },
261
+ ],
262
+ };
263
+ console.log(' ✓ Created reportRouter function');
264
+ }
265
+
266
+ createReportQueue(result, appDefinition) {
267
+ result.resources.ReportQueue = {
268
+ Type: 'AWS::SQS::Queue',
269
+ Properties: {
270
+ QueueName: '${self:service}-${self:provider.stage}-ReportQueue',
271
+ MessageRetentionPeriod: 86400, // 1 day
272
+ VisibilityTimeout: 900, // 15 minutes (Lambda max)
273
+ RedrivePolicy: {
274
+ maxReceiveCount: 3,
275
+ deadLetterTargetArn: {
276
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
277
+ },
278
+ },
279
+ },
280
+ };
281
+
282
+ if (isScopedEnvironmentActive(appDefinition)) {
283
+ // Only the report functions read this queue URL
284
+ result.functionEnvironments = result.functionEnvironments || {};
285
+ for (const fnName of ['reportRouter', 'reportExecutor']) {
286
+ result.functionEnvironments[fnName] = {
287
+ ...result.functionEnvironments[fnName],
288
+ REPORT_QUEUE_URL: { Ref: 'ReportQueue' },
289
+ };
290
+ }
291
+ } else {
292
+ result.environment.REPORT_QUEUE_URL = { Ref: 'ReportQueue' };
293
+ }
294
+
295
+ // The report router enqueues async recorded/snapshot runs. The base
296
+ // role's wildcard does not cover this queue's name, so grant
297
+ // SendMessage explicitly.
298
+ result.iamStatements.push({
299
+ Effect: 'Allow',
300
+ Action: [
301
+ 'sqs:SendMessage',
302
+ 'sqs:SendMessageBatch',
303
+ 'sqs:GetQueueUrl',
304
+ 'sqs:GetQueueAttributes',
305
+ ],
306
+ Resource: { 'Fn::GetAtt': ['ReportQueue', 'Arn'] },
307
+ });
308
+
309
+ console.log(' ✓ Created ReportQueue');
310
+ }
311
+
312
+ createReportExecutorFunction(appDefinition, result, usePrismaLayer) {
313
+ result.functions.reportExecutor = {
314
+ handler: 'node_modules/@friggframework/admin-scripts/src/infrastructure/report-executor-handler.handler',
315
+ skipEsbuild: true,
316
+ package: this.skipEsbuildPackageConfig(appDefinition, usePrismaLayer),
317
+ ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
318
+ timeout: 900, // 15 minutes max
319
+ memorySize: 1024,
320
+ events: [
321
+ {
322
+ sqs: {
323
+ arn: { 'Fn::GetAtt': ['ReportQueue', 'Arn'] },
324
+ batchSize: 1,
325
+ },
326
+ },
327
+ ],
328
+ };
329
+ console.log(' ✓ Created reportExecutor function');
330
+ }
331
+
332
+ // Non-JSON report output; the router mints short-lived presigned URLs for reads.
333
+ createReportArtifactBucket(result, appDefinition) {
334
+ result.resources.ReportArtifactBucket = {
335
+ Type: 'AWS::S3::Bucket',
336
+ Properties: {
337
+ BucketName:
338
+ '${self:service}-${self:provider.stage}-report-artifacts',
339
+ BucketEncryption: {
340
+ ServerSideEncryptionConfiguration: [
341
+ {
342
+ ServerSideEncryptionByDefault: {
343
+ SSEAlgorithm: 'AES256',
344
+ },
345
+ },
346
+ ],
347
+ },
348
+ PublicAccessBlockConfiguration: {
349
+ BlockPublicAcls: true,
350
+ BlockPublicPolicy: true,
351
+ IgnorePublicAcls: true,
352
+ RestrictPublicBuckets: true,
353
+ },
354
+ },
355
+ };
356
+
357
+ if (isScopedEnvironmentActive(appDefinition)) {
358
+ result.functionEnvironments = result.functionEnvironments || {};
359
+ for (const fnName of ['reportRouter', 'reportExecutor']) {
360
+ result.functionEnvironments[fnName] = {
361
+ ...result.functionEnvironments[fnName],
362
+ REPORT_ARTIFACT_BUCKET: { Ref: 'ReportArtifactBucket' },
363
+ };
364
+ }
365
+ } else {
366
+ result.environment.REPORT_ARTIFACT_BUCKET = {
367
+ Ref: 'ReportArtifactBucket',
368
+ };
369
+ }
370
+
371
+ // Executor writes and router reads (via presign); scope to this bucket's objects.
372
+ result.iamStatements.push({
373
+ Effect: 'Allow',
374
+ Action: ['s3:PutObject', 's3:GetObject'],
375
+ Resource: {
376
+ 'Fn::Sub': [
377
+ '${BucketArn}/*',
378
+ {
379
+ BucketArn: {
380
+ 'Fn::GetAtt': ['ReportArtifactBucket', 'Arn'],
381
+ },
382
+ },
383
+ ],
384
+ },
385
+ });
386
+
387
+ console.log(' ✓ Created ReportArtifactBucket');
388
+ }
389
+
390
+ // Without this, the skipEsbuild functions package the whole node_modules
391
+ // closure (aws-sdk, Prisma, dev deps) and blow past Lambda's 250 MB limit.
392
+ // Mirrors the exclusions the framework's other node_modules handlers use.
393
+ skipEsbuildPackageConfig(appDefinition, usePrismaLayer) {
394
+ const tlsCAFile = appDefinition?.database?.documentDB?.tlsCAFile;
395
+ return {
396
+ include: [
397
+ // Handlers connect to the DB, so ship the DocumentDB CA cert.
398
+ ...(tlsCAFile ? [tlsCAFile.replace(/^\.\//, '')] : []),
399
+ ],
400
+ exclude: [
401
+ 'node_modules/aws-sdk/**',
402
+ 'node_modules/@aws-sdk/**',
403
+ ...(usePrismaLayer
404
+ ? [
405
+ 'node_modules/@prisma/**',
406
+ 'node_modules/.prisma/**',
407
+ 'node_modules/prisma/**',
408
+ 'node_modules/@friggframework/core/generated/**',
409
+ ]
410
+ : []),
411
+ ...nestedNodeModulesExcludes(appDefinition, usePrismaLayer),
412
+ 'node_modules/@friggframework/test/**',
413
+ 'node_modules/@friggframework/eslint-config/**',
414
+ 'node_modules/@friggframework/prettier-config/**',
415
+ 'node_modules/jest/**',
416
+ 'node_modules/prettier/**',
417
+ 'node_modules/eslint/**',
418
+ 'node_modules/esbuild/**',
419
+ 'node_modules/@esbuild/**',
420
+ 'node_modules/typescript/**',
421
+ 'node_modules/webpack/**',
422
+ 'node_modules/osls/**',
423
+ 'node_modules/serverless-esbuild/**',
424
+ 'node_modules/serverless-jetpack/**',
425
+ 'node_modules/serverless-offline/**',
426
+ 'node_modules/serverless-offline-sqs/**',
427
+ 'node_modules/serverless-dotenv-plugin/**',
428
+ 'node_modules/serverless-kms-grants/**',
429
+ // Never deploy secrets or the lockfile.
430
+ '.env',
431
+ '.env.*',
432
+ '**/.env',
433
+ '**/.env.*',
434
+ '.frigg-credentials.json',
435
+ 'package-lock.json',
436
+ 'test/**',
437
+ 'layers/**',
438
+ 'coverage/**',
439
+ '**/*.test.js',
440
+ '**/*.spec.js',
441
+ ],
442
+ };
443
+ }
444
+
445
+ createSchedulerResources(
446
+ appDefinition,
447
+ result,
448
+ { scriptsPresent = true, hasReports = false } = {}
449
+ ) {
450
+ // Constructed ARNs, not Fn::GetAtt: a GetAtt edge to an executor closes a
451
+ // CloudFormation circular dependency via the shared Lambda execution role.
452
+ const fnArn = (logicalName) => ({
453
+ 'Fn::Sub': `arn:aws:lambda:\${AWS::Region}:\${AWS::AccountId}:function:\${self:service}-\${self:provider.stage}-${logicalName}`,
454
+ });
455
+ const scriptExecutorArn = fnArn('adminScriptExecutor');
456
+ const reportExecutorArn = fnArn('reportExecutor');
457
+
458
+ const invokeResources = [
459
+ ...(scriptsPresent ? [scriptExecutorArn] : []),
460
+ ...(hasReports ? [reportExecutorArn] : []),
461
+ ];
462
+ const invokeResource =
463
+ invokeResources.length === 1 ? invokeResources[0] : invokeResources;
464
+
465
+ // Create IAM role for EventBridge Scheduler
466
+ result.resources.AdminScriptSchedulerRole = {
467
+ Type: 'AWS::IAM::Role',
468
+ Properties: {
469
+ RoleName: '${self:service}-${self:provider.stage}-admin-script-scheduler',
470
+ AssumeRolePolicyDocument: {
471
+ Version: '2012-10-17',
472
+ Statement: [{
473
+ Effect: 'Allow',
474
+ Principal: { Service: 'scheduler.amazonaws.com' },
475
+ Action: 'sts:AssumeRole',
476
+ }],
477
+ },
478
+ Policies: [{
479
+ PolicyName: 'InvokeLambda',
480
+ PolicyDocument: {
481
+ Version: '2012-10-17',
482
+ Statement: [{
483
+ Effect: 'Allow',
484
+ Action: 'lambda:InvokeFunction',
485
+ Resource: invokeResource,
486
+ }],
487
+ },
488
+ }],
489
+ },
490
+ };
491
+
492
+ // Create schedule group
493
+ result.resources.AdminScriptScheduleGroup = {
494
+ Type: 'AWS::Scheduler::ScheduleGroup',
495
+ Properties: {
496
+ Name: '${self:service}-${self:provider.stage}-admin-scripts',
497
+ },
498
+ };
499
+
500
+ // Router-scoped, not shared provider env. Two reasons: broadcasting the
501
+ // resource references to every function creates CloudFormation circular
502
+ // deps; and SCHEDULER_PROVIDER='aws' is only valid for the admin-scripts
503
+ // adapter (the routers are its sole consumers) — core's scheduler
504
+ // factory, used by integration Lambdas, rejects 'aws', so it must not
505
+ // leak app-wide.
506
+ if (scriptsPresent) {
507
+ result.functions.adminScriptRouter.environment = {
508
+ ...(result.functions.adminScriptRouter.environment || {}),
509
+ SCHEDULER_PROVIDER: 'aws',
510
+ SCHEDULER_ROLE_ARN: {
511
+ 'Fn::GetAtt': ['AdminScriptSchedulerRole', 'Arn'],
512
+ },
513
+ ADMIN_SCRIPT_SCHEDULE_GROUP: { Ref: 'AdminScriptScheduleGroup' },
514
+ ADMIN_SCRIPT_EXECUTOR_LAMBDA_ARN: scriptExecutorArn,
515
+ };
516
+ }
517
+
518
+ // Report schedules reuse the shared role/group but must target the
519
+ // report executor, not the script one (REPORT_EXECUTOR_LAMBDA_ARN).
520
+ if (hasReports) {
521
+ result.functions.reportRouter.environment = {
522
+ ...(result.functions.reportRouter.environment || {}),
523
+ SCHEDULER_PROVIDER: 'aws',
524
+ SCHEDULER_ROLE_ARN: {
525
+ 'Fn::GetAtt': ['AdminScriptSchedulerRole', 'Arn'],
526
+ },
527
+ REPORT_SCHEDULE_GROUP: { Ref: 'AdminScriptScheduleGroup' },
528
+ REPORT_EXECUTOR_LAMBDA_ARN: reportExecutorArn,
529
+ };
530
+ }
531
+
532
+ // The router manages schedules through the AWS scheduler adapter, so it
533
+ // needs scheduler:* on this group plus iam:PassRole for the role it hands
534
+ // to EventBridge. (UpdateSchedule covers the upsert conflict path.)
535
+ result.iamStatements.push(
536
+ {
537
+ Effect: 'Allow',
538
+ Action: [
539
+ 'scheduler:CreateSchedule',
540
+ 'scheduler:UpdateSchedule',
541
+ 'scheduler:DeleteSchedule',
542
+ 'scheduler:GetSchedule',
543
+ ],
544
+ Resource: {
545
+ 'Fn::Sub': [
546
+ 'arn:aws:scheduler:${AWS::Region}:${AWS::AccountId}:schedule/${GroupName}/*',
547
+ { GroupName: { Ref: 'AdminScriptScheduleGroup' } },
548
+ ],
549
+ },
550
+ },
551
+ {
552
+ Effect: 'Allow',
553
+ Action: ['iam:PassRole'],
554
+ Resource: { 'Fn::GetAtt': ['AdminScriptSchedulerRole', 'Arn'] },
555
+ Condition: {
556
+ StringEquals: {
557
+ 'iam:PassedToService': 'scheduler.amazonaws.com',
558
+ },
559
+ },
560
+ }
561
+ );
562
+
563
+ console.log(' ✓ Created EventBridge Scheduler resources');
564
+ }
565
+ }
566
+
567
+ module.exports = { AdminScriptBuilder };