@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,851 @@
1
+ const path = require('path');
2
+
3
+ /**
4
+ * Generate IAM CloudFormation template
5
+ * @param {Object} options - Generation options
6
+ * @param {string} [options.appName='Frigg'] - Application name
7
+ * @param {Object} [options.features={}] - Enabled features { vpc, kms, ssm, websockets }
8
+ * @param {string} [options.userPrefix='frigg-deployment-user'] - IAM user name prefix
9
+ * @param {string} [options.stackName='frigg-deployment-iam'] - CloudFormation stack name
10
+ * @param {string} [options.ssmKmsKeyArn] - Customer-managed KMS key ARN for SecureString offload (appDefinition.ssm.kmsKeyArn)
11
+ * @returns {string} CloudFormation YAML template
12
+ */
13
+ function generateIAMCloudFormation(options = {}) {
14
+ const {
15
+ appName = 'Frigg',
16
+ features = {},
17
+ userPrefix = 'frigg-deployment-user',
18
+ stackName = 'frigg-deployment-iam',
19
+ ssmKmsKeyArn
20
+ } = options;
21
+
22
+ const deploymentUserName = userPrefix;
23
+
24
+ // Features are already analyzed by caller (use getFeatureSummary to extract features from appDefinition)
25
+ // Expected features: { vpc, kms, ssm, websockets }
26
+
27
+ // Build the CloudFormation template
28
+ const template = {
29
+ AWSTemplateFormatVersion: '2010-09-09',
30
+ Description: `IAM roles and policies for ${appName} application deployment pipeline`,
31
+
32
+ Parameters: {
33
+ DeploymentUserName: {
34
+ Type: 'String',
35
+ Default: deploymentUserName,
36
+ Description:
37
+ 'Name for the IAM user that will deploy Frigg applications',
38
+ },
39
+ EnableVPCSupport: {
40
+ Type: 'String',
41
+ Default: features.vpc ? 'true' : 'false',
42
+ AllowedValues: ['true', 'false'],
43
+ Description:
44
+ 'Enable VPC-related permissions for Frigg applications',
45
+ },
46
+ EnableKMSSupport: {
47
+ Type: 'String',
48
+ Default: features.kms ? 'true' : 'false',
49
+ AllowedValues: ['true', 'false'],
50
+ Description:
51
+ 'Enable KMS encryption permissions for Frigg applications',
52
+ },
53
+ EnableSSMSupport: {
54
+ Type: 'String',
55
+ Default: features.ssm ? 'true' : 'false',
56
+ AllowedValues: ['true', 'false'],
57
+ Description:
58
+ 'Enable SSM Parameter Store permissions for Frigg applications',
59
+ },
60
+ DeploymentKmsAliasName: {
61
+ Type: 'String',
62
+ Default: 'alias/frigg-deployment',
63
+ Description:
64
+ 'Alias name to create or manage for the deployment KMS key',
65
+ },
66
+ DeploymentKmsTargetKeyArn: {
67
+ Type: 'String',
68
+ Default: '',
69
+ Description:
70
+ 'Optional existing KMS key ARN that the deployment alias should reference',
71
+ },
72
+ },
73
+
74
+ Conditions: {
75
+ CreateVPCPermissions: {
76
+ 'Fn::Equals': [{ Ref: 'EnableVPCSupport' }, 'true'],
77
+ },
78
+ CreateKMSPermissions: {
79
+ 'Fn::Equals': [{ Ref: 'EnableKMSSupport' }, 'true'],
80
+ },
81
+ CreateSSMPermissions: {
82
+ 'Fn::Equals': [{ Ref: 'EnableSSMSupport' }, 'true'],
83
+ },
84
+ CreateKMSAlias: {
85
+ 'Fn::And': [
86
+ {
87
+ 'Fn::Equals': [{ Ref: 'EnableKMSSupport' }, 'true'],
88
+ },
89
+ {
90
+ 'Fn::Not': [
91
+ {
92
+ 'Fn::Equals': [
93
+ { Ref: 'DeploymentKmsTargetKeyArn' },
94
+ '',
95
+ ],
96
+ },
97
+ ],
98
+ },
99
+ ],
100
+ },
101
+ },
102
+
103
+ Resources: {},
104
+ };
105
+
106
+ // Add IAM User
107
+ template.Resources.FriggDeploymentUser = {
108
+ Type: 'AWS::IAM::User',
109
+ Properties: {
110
+ UserName: { Ref: 'DeploymentUserName' },
111
+ ManagedPolicyArns: [
112
+ { Ref: 'FriggDiscoveryPolicy' },
113
+ { Ref: 'FriggCoreDeploymentPolicy' },
114
+ ],
115
+ },
116
+ };
117
+
118
+ // Conditionally add feature-specific policies
119
+ if (features.vpc) {
120
+ template.Resources.FriggDeploymentUser.Properties.ManagedPolicyArns.push(
121
+ {
122
+ 'Fn::If': [
123
+ 'CreateVPCPermissions',
124
+ { Ref: 'FriggVPCPolicy' },
125
+ { Ref: 'AWS::NoValue' },
126
+ ],
127
+ }
128
+ );
129
+ }
130
+ if (features.kms) {
131
+ template.Resources.FriggDeploymentUser.Properties.ManagedPolicyArns.push(
132
+ {
133
+ 'Fn::If': [
134
+ 'CreateKMSPermissions',
135
+ { Ref: 'FriggKMSPolicy' },
136
+ { Ref: 'AWS::NoValue' },
137
+ ],
138
+ }
139
+ );
140
+ }
141
+ if (features.ssm) {
142
+ template.Resources.FriggDeploymentUser.Properties.ManagedPolicyArns.push(
143
+ {
144
+ 'Fn::If': [
145
+ 'CreateSSMPermissions',
146
+ { Ref: 'FriggSSMPolicy' },
147
+ { Ref: 'AWS::NoValue' },
148
+ ],
149
+ }
150
+ );
151
+ }
152
+
153
+ // Add Access Key
154
+ template.Resources.FriggDeploymentAccessKey = {
155
+ Type: 'AWS::IAM::AccessKey',
156
+ Properties: {
157
+ UserName: { Ref: 'FriggDeploymentUser' },
158
+ },
159
+ };
160
+
161
+ // Add Discovery Policy (always needed)
162
+ template.Resources.FriggDiscoveryPolicy = {
163
+ Type: 'AWS::IAM::ManagedPolicy',
164
+ Properties: {
165
+ ManagedPolicyName: 'FriggDiscoveryPolicy',
166
+ Description:
167
+ 'Permissions for AWS resource discovery during Frigg build process',
168
+ PolicyDocument: {
169
+ Version: '2012-10-17',
170
+ Statement: [
171
+ {
172
+ Sid: 'AWSDiscoveryPermissions',
173
+ Effect: 'Allow',
174
+ Action: [
175
+ 'sts:GetCallerIdentity',
176
+ 'ec2:DescribeVpcs',
177
+ 'ec2:DescribeSubnets',
178
+ 'ec2:DescribeSecurityGroups',
179
+ 'ec2:DescribeRouteTables',
180
+ 'ec2:DescribeNatGateways',
181
+ 'ec2:DescribeAddresses',
182
+ 'kms:ListKeys',
183
+ 'kms:DescribeKey',
184
+ ],
185
+ Resource: '*',
186
+ },
187
+ ],
188
+ },
189
+ },
190
+ };
191
+
192
+ // Add Core Deployment Policy (always needed)
193
+ const coreActions = [
194
+ // CloudFormation permissions
195
+ 'cloudformation:CreateStack',
196
+ 'cloudformation:UpdateStack',
197
+ 'cloudformation:DeleteStack',
198
+ 'cloudformation:DescribeStacks',
199
+ 'cloudformation:DescribeStackEvents',
200
+ 'cloudformation:DescribeStackResources',
201
+ 'cloudformation:DescribeStackResource',
202
+ 'cloudformation:ListStackResources',
203
+ 'cloudformation:GetTemplate',
204
+ 'cloudformation:DescribeChangeSet',
205
+ 'cloudformation:CreateChangeSet',
206
+ 'cloudformation:DeleteChangeSet',
207
+ 'cloudformation:ExecuteChangeSet',
208
+ 'cloudformation:ValidateTemplate',
209
+
210
+ // Lambda permissions
211
+ 'lambda:CreateFunction',
212
+ 'lambda:UpdateFunctionCode',
213
+ 'lambda:UpdateFunctionConfiguration',
214
+ 'lambda:DeleteFunction',
215
+ 'lambda:GetFunction',
216
+ 'lambda:ListFunctions',
217
+ 'lambda:PublishVersion',
218
+ 'lambda:CreateAlias',
219
+ 'lambda:UpdateAlias',
220
+ 'lambda:DeleteAlias',
221
+ 'lambda:GetAlias',
222
+ 'lambda:AddPermission',
223
+ 'lambda:RemovePermission',
224
+ 'lambda:GetPolicy',
225
+ 'lambda:PutProvisionedConcurrencyConfig',
226
+ 'lambda:DeleteProvisionedConcurrencyConfig',
227
+ 'lambda:PutConcurrency',
228
+ 'lambda:DeleteConcurrency',
229
+ 'lambda:TagResource',
230
+ 'lambda:UntagResource',
231
+ 'lambda:ListVersionsByFunction',
232
+
233
+ // IAM permissions
234
+ 'iam:CreateRole',
235
+ 'iam:DeleteRole',
236
+ 'iam:GetRole',
237
+ 'iam:PassRole',
238
+ 'iam:PutRolePolicy',
239
+ 'iam:DeleteRolePolicy',
240
+ 'iam:GetRolePolicy',
241
+ 'iam:AttachRolePolicy',
242
+ 'iam:DetachRolePolicy',
243
+ 'iam:TagRole',
244
+ 'iam:UntagRole',
245
+ 'iam:ListPolicyVersions',
246
+
247
+ // S3 permissions
248
+ 's3:CreateBucket',
249
+ 's3:PutObject',
250
+ 's3:GetObject',
251
+ 's3:DeleteObject',
252
+ 's3:PutBucketPolicy',
253
+ 's3:PutBucketVersioning',
254
+ 's3:PutBucketPublicAccessBlock',
255
+ 's3:GetBucketLocation',
256
+ 's3:ListBucket',
257
+
258
+ // SQS permissions
259
+ 'sqs:CreateQueue',
260
+ 'sqs:DeleteQueue',
261
+ 'sqs:GetQueueAttributes',
262
+ 'sqs:SetQueueAttributes',
263
+ 'sqs:GetQueueUrl',
264
+ 'sqs:TagQueue',
265
+ 'sqs:UntagQueue',
266
+
267
+ // SNS permissions
268
+ 'sns:CreateTopic',
269
+ 'sns:DeleteTopic',
270
+ 'sns:GetTopicAttributes',
271
+ 'sns:SetTopicAttributes',
272
+ 'sns:Subscribe',
273
+ 'sns:Unsubscribe',
274
+ 'sns:ListSubscriptionsByTopic',
275
+ 'sns:TagResource',
276
+ 'sns:UntagResource',
277
+
278
+ // CloudWatch and Logs permissions
279
+ 'cloudwatch:PutMetricAlarm',
280
+ 'cloudwatch:DeleteAlarms',
281
+ 'cloudwatch:DescribeAlarms',
282
+ 'logs:CreateLogGroup',
283
+ 'logs:CreateLogStream',
284
+ 'logs:DeleteLogGroup',
285
+ 'logs:DescribeLogGroups',
286
+ 'logs:DescribeLogStreams',
287
+ 'logs:FilterLogEvents',
288
+ 'logs:PutLogEvents',
289
+ 'logs:PutRetentionPolicy',
290
+
291
+ // API Gateway permissions
292
+ 'apigateway:POST',
293
+ 'apigateway:PUT',
294
+ 'apigateway:DELETE',
295
+ 'apigateway:GET',
296
+ 'apigateway:PATCH',
297
+ 'apigateway:TagResource',
298
+ 'apigateway:UntagResource',
299
+ ];
300
+
301
+ const coreStatements = [
302
+ {
303
+ Sid: 'CloudFormationFriggStacks',
304
+ Effect: 'Allow',
305
+ Action: [
306
+ 'cloudformation:CreateStack',
307
+ 'cloudformation:UpdateStack',
308
+ 'cloudformation:DeleteStack',
309
+ 'cloudformation:DescribeStacks',
310
+ 'cloudformation:DescribeStackEvents',
311
+ 'cloudformation:DescribeStackResources',
312
+ 'cloudformation:DescribeStackResource',
313
+ 'cloudformation:ListStackResources',
314
+ 'cloudformation:GetTemplate',
315
+ 'cloudformation:DescribeChangeSet',
316
+ 'cloudformation:CreateChangeSet',
317
+ 'cloudformation:DeleteChangeSet',
318
+ 'cloudformation:ExecuteChangeSet',
319
+ ],
320
+ Resource: [
321
+ {
322
+ 'Fn::Sub':
323
+ 'arn:aws:cloudformation:*:${AWS::AccountId}:stack/*frigg*/*',
324
+ },
325
+ ],
326
+ },
327
+ {
328
+ Sid: 'CloudFormationValidateTemplate',
329
+ Effect: 'Allow',
330
+ Action: ['cloudformation:ValidateTemplate'],
331
+ Resource: '*',
332
+ },
333
+ {
334
+ Sid: 'S3DeploymentBucket',
335
+ Effect: 'Allow',
336
+ Action: [
337
+ 's3:CreateBucket',
338
+ 's3:PutObject',
339
+ 's3:GetObject',
340
+ 's3:DeleteObject',
341
+ 's3:PutBucketPolicy',
342
+ 's3:PutBucketVersioning',
343
+ 's3:PutBucketPublicAccessBlock',
344
+ 's3:GetBucketLocation',
345
+ 's3:ListBucket',
346
+ ],
347
+ Resource: [
348
+ 'arn:aws:s3:::*serverless*',
349
+ 'arn:aws:s3:::*serverless*/*',
350
+ ],
351
+ },
352
+ {
353
+ Sid: 'LambdaFriggFunctions',
354
+ Effect: 'Allow',
355
+ Action: [
356
+ 'lambda:CreateFunction',
357
+ 'lambda:UpdateFunctionCode',
358
+ 'lambda:UpdateFunctionConfiguration',
359
+ 'lambda:DeleteFunction',
360
+ 'lambda:GetFunction',
361
+ 'lambda:ListFunctions',
362
+ 'lambda:PublishVersion',
363
+ 'lambda:CreateAlias',
364
+ 'lambda:UpdateAlias',
365
+ 'lambda:DeleteAlias',
366
+ 'lambda:GetAlias',
367
+ 'lambda:AddPermission',
368
+ 'lambda:RemovePermission',
369
+ 'lambda:GetPolicy',
370
+ 'lambda:PutProvisionedConcurrencyConfig',
371
+ 'lambda:DeleteProvisionedConcurrencyConfig',
372
+ 'lambda:PutConcurrency',
373
+ 'lambda:DeleteConcurrency',
374
+ 'lambda:TagResource',
375
+ 'lambda:UntagResource',
376
+ 'lambda:ListVersionsByFunction',
377
+ ],
378
+ Resource: [
379
+ {
380
+ 'Fn::Sub':
381
+ 'arn:aws:lambda:*:${AWS::AccountId}:function:*frigg*',
382
+ },
383
+ ],
384
+ },
385
+ {
386
+ Sid: 'IAMRolesForFriggLambda',
387
+ Effect: 'Allow',
388
+ Action: [
389
+ 'iam:CreateRole',
390
+ 'iam:DeleteRole',
391
+ 'iam:GetRole',
392
+ 'iam:PassRole',
393
+ 'iam:PutRolePolicy',
394
+ 'iam:DeleteRolePolicy',
395
+ 'iam:GetRolePolicy',
396
+ 'iam:AttachRolePolicy',
397
+ 'iam:DetachRolePolicy',
398
+ 'iam:TagRole',
399
+ 'iam:UntagRole',
400
+ ],
401
+ Resource: [
402
+ { 'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:role/*frigg*' },
403
+ {
404
+ 'Fn::Sub':
405
+ 'arn:aws:iam::${AWS::AccountId}:role/*frigg*LambdaRole*',
406
+ },
407
+ ],
408
+ },
409
+ {
410
+ Sid: 'IAMPolicyVersionPermissions',
411
+ Effect: 'Allow',
412
+ Action: ['iam:ListPolicyVersions'],
413
+ Resource: [
414
+ { 'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:policy/*' },
415
+ ],
416
+ },
417
+ {
418
+ Sid: 'FriggMessagingServices',
419
+ Effect: 'Allow',
420
+ Action: [
421
+ 'sqs:CreateQueue',
422
+ 'sqs:DeleteQueue',
423
+ 'sqs:GetQueueAttributes',
424
+ 'sqs:SetQueueAttributes',
425
+ 'sqs:GetQueueUrl',
426
+ 'sqs:TagQueue',
427
+ 'sqs:UntagQueue',
428
+ ],
429
+ Resource: [
430
+ { 'Fn::Sub': 'arn:aws:sqs:*:${AWS::AccountId}:*frigg*' },
431
+ {
432
+ 'Fn::Sub':
433
+ 'arn:aws:sqs:*:${AWS::AccountId}:internal-error-queue-*',
434
+ },
435
+ ],
436
+ },
437
+ {
438
+ Sid: 'FriggSNSTopics',
439
+ Effect: 'Allow',
440
+ Action: [
441
+ 'sns:CreateTopic',
442
+ 'sns:DeleteTopic',
443
+ 'sns:GetTopicAttributes',
444
+ 'sns:SetTopicAttributes',
445
+ 'sns:Subscribe',
446
+ 'sns:Unsubscribe',
447
+ 'sns:ListSubscriptionsByTopic',
448
+ 'sns:TagResource',
449
+ 'sns:UntagResource',
450
+ ],
451
+ Resource: [
452
+ { 'Fn::Sub': 'arn:aws:sns:*:${AWS::AccountId}:*frigg*' },
453
+ ],
454
+ },
455
+ {
456
+ Sid: 'FriggMonitoringAndLogs',
457
+ Effect: 'Allow',
458
+ Action: [
459
+ 'cloudwatch:PutMetricAlarm',
460
+ 'cloudwatch:DeleteAlarms',
461
+ 'cloudwatch:DescribeAlarms',
462
+ 'logs:CreateLogGroup',
463
+ 'logs:CreateLogStream',
464
+ 'logs:DeleteLogGroup',
465
+ 'logs:DescribeLogGroups',
466
+ 'logs:DescribeLogStreams',
467
+ 'logs:FilterLogEvents',
468
+ 'logs:PutLogEvents',
469
+ 'logs:PutRetentionPolicy',
470
+ ],
471
+ Resource: [
472
+ {
473
+ 'Fn::Sub':
474
+ 'arn:aws:logs:*:${AWS::AccountId}:log-group:/aws/lambda/*frigg*',
475
+ },
476
+ {
477
+ 'Fn::Sub':
478
+ 'arn:aws:logs:*:${AWS::AccountId}:log-group:/aws/lambda/*frigg*:*',
479
+ },
480
+ {
481
+ 'Fn::Sub':
482
+ 'arn:aws:cloudwatch:*:${AWS::AccountId}:alarm:*frigg*',
483
+ },
484
+ ],
485
+ },
486
+ {
487
+ Sid: 'FriggAPIGateway',
488
+ Effect: 'Allow',
489
+ Action: [
490
+ 'apigateway:POST',
491
+ 'apigateway:PUT',
492
+ 'apigateway:DELETE',
493
+ 'apigateway:GET',
494
+ 'apigateway:PATCH',
495
+ 'apigateway:TagResource',
496
+ 'apigateway:UntagResource',
497
+ ],
498
+ Resource: [
499
+ 'arn:aws:apigateway:*::/restapis',
500
+ 'arn:aws:apigateway:*::/restapis/*',
501
+ 'arn:aws:apigateway:*::/domainnames',
502
+ 'arn:aws:apigateway:*::/domainnames/*',
503
+ ],
504
+ },
505
+ {
506
+ Sid: 'FriggAPIGatewayV2',
507
+ Effect: 'Allow',
508
+ Action: [
509
+ 'apigateway:GET',
510
+ 'apigateway:DELETE',
511
+ 'apigateway:PATCH',
512
+ 'apigateway:POST',
513
+ 'apigateway:PUT',
514
+ ],
515
+ Resource: [
516
+ 'arn:aws:apigateway:*::/apis',
517
+ 'arn:aws:apigateway:*::/apis/*',
518
+ 'arn:aws:apigateway:*::/apis/*/stages',
519
+ 'arn:aws:apigateway:*::/apis/*/stages/*',
520
+ 'arn:aws:apigateway:*::/domainnames',
521
+ 'arn:aws:apigateway:*::/domainnames/*',
522
+ 'arn:aws:apigateway:*::/domainnames/*/apimappings',
523
+ ],
524
+ },
525
+ ];
526
+
527
+ template.Resources.FriggCoreDeploymentPolicy = {
528
+ Type: 'AWS::IAM::ManagedPolicy',
529
+ Properties: {
530
+ ManagedPolicyName: 'FriggCoreDeploymentPolicy',
531
+ Description: 'Core permissions for deploying Frigg applications',
532
+ PolicyDocument: {
533
+ Version: '2012-10-17',
534
+ Statement: coreStatements,
535
+ },
536
+ },
537
+ };
538
+
539
+ // Add feature-specific policies only if needed
540
+ if (features.vpc) {
541
+ template.Resources.FriggVPCPolicy = {
542
+ Type: 'AWS::IAM::ManagedPolicy',
543
+ Condition: 'CreateVPCPermissions',
544
+ Properties: {
545
+ ManagedPolicyName: 'FriggVPCPolicy',
546
+ Description: 'VPC-related permissions for Frigg applications',
547
+ PolicyDocument: {
548
+ Version: '2012-10-17',
549
+ Statement: [
550
+ {
551
+ Sid: 'FriggVPCEndpointManagement',
552
+ Effect: 'Allow',
553
+ Action: [
554
+ 'ec2:CreateVpcEndpoint',
555
+ 'ec2:DeleteVpcEndpoint',
556
+ 'ec2:DescribeVpcEndpoints',
557
+ 'ec2:ModifyVpcEndpoint',
558
+ 'ec2:CreateNatGateway',
559
+ 'ec2:DeleteNatGateway',
560
+ 'ec2:DescribeNatGateways',
561
+ 'ec2:AllocateAddress',
562
+ 'ec2:ReleaseAddress',
563
+ 'ec2:DescribeAddresses',
564
+ 'ec2:CreateRouteTable',
565
+ 'ec2:DeleteRouteTable',
566
+ 'ec2:DescribeRouteTables',
567
+ 'ec2:CreateRoute',
568
+ 'ec2:DeleteRoute',
569
+ 'ec2:ReplaceRoute',
570
+ 'ec2:AssociateRouteTable',
571
+ 'ec2:DisassociateRouteTable',
572
+ 'ec2:CreateSecurityGroup',
573
+ 'ec2:DeleteSecurityGroup',
574
+ 'ec2:AuthorizeSecurityGroupEgress',
575
+ 'ec2:AuthorizeSecurityGroupIngress',
576
+ 'ec2:RevokeSecurityGroupEgress',
577
+ 'ec2:RevokeSecurityGroupIngress',
578
+ 'ec2:DetachInternetGateway',
579
+ 'ec2:DeleteSubnet',
580
+ ],
581
+ Resource: '*',
582
+ },
583
+ ],
584
+ },
585
+ },
586
+ };
587
+ }
588
+
589
+ if (features.kms) {
590
+ template.Resources.FriggKMSPolicy = {
591
+ Type: 'AWS::IAM::ManagedPolicy',
592
+ Condition: 'CreateKMSPermissions',
593
+ Properties: {
594
+ ManagedPolicyName: 'FriggKMSPolicy',
595
+ Description:
596
+ 'KMS encryption permissions for Frigg applications',
597
+ PolicyDocument: {
598
+ Version: '2012-10-17',
599
+ Statement: [
600
+ {
601
+ Sid: 'FriggKMSEncryptionRuntime',
602
+ Effect: 'Allow',
603
+ Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
604
+ Resource: [
605
+ {
606
+ 'Fn::Sub':
607
+ 'arn:aws:kms:*:${AWS::AccountId}:key/*',
608
+ },
609
+ ],
610
+ Condition: {
611
+ StringEquals: {
612
+ 'kms:ViaService': [
613
+ 'lambda.*.amazonaws.com',
614
+ 's3.*.amazonaws.com',
615
+ ],
616
+ },
617
+ },
618
+ },
619
+ {
620
+ Sid: 'FriggKMSManagement',
621
+ Effect: 'Allow',
622
+ Action: [
623
+ 'kms:CreateKey',
624
+ 'kms:PutKeyPolicy',
625
+ 'kms:EnableKeyRotation',
626
+ 'kms:TagResource',
627
+ 'kms:UntagResource',
628
+ 'kms:ListResourceTags',
629
+ 'kms:CreateAlias',
630
+ 'kms:UpdateAlias',
631
+ 'kms:DeleteAlias',
632
+ 'kms:ListAliases',
633
+ 'kms:DescribeKey',
634
+ ],
635
+ Resource: '*',
636
+ },
637
+ ],
638
+ },
639
+ },
640
+ };
641
+ }
642
+
643
+ template.Resources.FriggKMSKeyAlias = {
644
+ Type: 'AWS::KMS::Alias',
645
+ Condition: 'CreateKMSAlias',
646
+ DeletionPolicy: 'Retain',
647
+ UpdateReplacePolicy: 'Retain',
648
+ Properties: {
649
+ AliasName: { Ref: 'DeploymentKmsAliasName' },
650
+ TargetKeyId: { Ref: 'DeploymentKmsTargetKeyArn' },
651
+ },
652
+ };
653
+
654
+ if (features.ssm) {
655
+ template.Resources.FriggSSMPolicy = {
656
+ Type: 'AWS::IAM::ManagedPolicy',
657
+ Condition: 'CreateSSMPermissions',
658
+ Properties: {
659
+ ManagedPolicyName: 'FriggSSMPolicy',
660
+ Description:
661
+ 'SSM Parameter Store permissions for Frigg applications',
662
+ PolicyDocument: {
663
+ Version: '2012-10-17',
664
+ Statement: [
665
+ {
666
+ Sid: 'FriggSSMParameterAccess',
667
+ Effect: 'Allow',
668
+ Action: [
669
+ 'ssm:GetParameter',
670
+ 'ssm:GetParameters',
671
+ 'ssm:GetParametersByPath',
672
+ 'ssm:PutParameter',
673
+ 'ssm:DeleteParameter',
674
+ 'ssm:AddTagsToResource',
675
+ ],
676
+ Resource: [
677
+ {
678
+ 'Fn::Sub':
679
+ 'arn:aws:ssm:*:${AWS::AccountId}:parameter/*frigg*',
680
+ },
681
+ {
682
+ 'Fn::Sub':
683
+ 'arn:aws:ssm:*:${AWS::AccountId}:parameter/*frigg*/*',
684
+ },
685
+ ],
686
+ },
687
+ {
688
+ // SecureString offload: SSM performs the KMS encrypt/decrypt
689
+ // on the caller's behalf, so the grant is scoped to ViaService ssm.*
690
+ Sid: 'FriggSSMParameterKMSEncryption',
691
+ Effect: 'Allow',
692
+ Action: [
693
+ 'kms:Encrypt',
694
+ 'kms:GenerateDataKey',
695
+ 'kms:Decrypt',
696
+ ],
697
+ Resource: ssmKmsKeyArn
698
+ ? [ssmKmsKeyArn]
699
+ : [
700
+ {
701
+ 'Fn::Sub':
702
+ 'arn:aws:kms:*:${AWS::AccountId}:key/*',
703
+ },
704
+ ],
705
+ Condition: {
706
+ // StringLike: kms:ViaService is regional
707
+ // (ssm.us-east-1.amazonaws.com), so the wildcard
708
+ // must be matched, not compared literally.
709
+ StringLike: {
710
+ 'kms:ViaService': [
711
+ 'ssm.*.amazonaws.com',
712
+ ],
713
+ },
714
+ },
715
+ },
716
+ ],
717
+ },
718
+ },
719
+ };
720
+ }
721
+
722
+ // Add Secrets Manager for credentials
723
+ template.Resources.FriggDeploymentCredentials = {
724
+ Type: 'AWS::SecretsManager::Secret',
725
+ Properties: {
726
+ Name: 'frigg-deployment-credentials',
727
+ Description: 'Access credentials for Frigg deployment user',
728
+ SecretString: {
729
+ 'Fn::Sub': JSON.stringify({
730
+ AccessKeyId: '${FriggDeploymentAccessKey}',
731
+ SecretAccessKey:
732
+ '${FriggDeploymentAccessKey.SecretAccessKey}',
733
+ }),
734
+ },
735
+ },
736
+ };
737
+
738
+ // Add Outputs
739
+ template.Outputs = {
740
+ DeploymentUserArn: {
741
+ Description: 'ARN of the Frigg deployment user',
742
+ Value: { 'Fn::GetAtt': ['FriggDeploymentUser', 'Arn'] },
743
+ Export: {
744
+ Name: { 'Fn::Sub': '${AWS::StackName}-UserArn' },
745
+ },
746
+ },
747
+ AccessKeyId: {
748
+ Description: 'Access Key ID for the deployment user',
749
+ Value: { Ref: 'FriggDeploymentAccessKey' },
750
+ Export: {
751
+ Name: { 'Fn::Sub': '${AWS::StackName}-AccessKeyId' },
752
+ },
753
+ },
754
+ SecretAccessKeyCommand: {
755
+ Description: 'Command to retrieve the secret access key',
756
+ Value: {
757
+ 'Fn::Sub':
758
+ 'aws secretsmanager get-secret-value --secret-id frigg-deployment-credentials --query SecretString --output text | jq -r .SecretAccessKey',
759
+ },
760
+ },
761
+ CredentialsSecretArn: {
762
+ Description: 'ARN of the secret containing deployment credentials',
763
+ Value: { Ref: 'FriggDeploymentCredentials' },
764
+ Export: {
765
+ Name: { 'Fn::Sub': '${AWS::StackName}-CredentialsSecretArn' },
766
+ },
767
+ },
768
+ };
769
+
770
+ // Convert to YAML
771
+ return convertToYAML(template);
772
+ }
773
+
774
+ /**
775
+ * Convert JavaScript object to CloudFormation YAML
776
+ * @param {Object} obj - JavaScript object
777
+ * @returns {string} YAML string
778
+ */
779
+ function convertToYAML(obj) {
780
+ const yaml = require('js-yaml');
781
+ return yaml.dump(obj, {
782
+ indent: 2,
783
+ lineWidth: 120,
784
+ noRefs: true,
785
+ sortKeys: false,
786
+ });
787
+ }
788
+
789
+ /**
790
+ * Generate summary of what features will be included in the IAM policy
791
+ * @param {Object} appDefinition - Application definition
792
+ * @returns {Object} Feature summary
793
+ */
794
+ function getFeatureSummary(appDefinition) {
795
+ const features = {
796
+ core: true, // Always enabled
797
+ vpc: appDefinition.vpc?.enable === true,
798
+ kms:
799
+ appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms',
800
+ ssm: appDefinition.ssm?.enable === true,
801
+ websockets: appDefinition.websockets?.enable === true,
802
+ };
803
+
804
+ const integrationCount = appDefinition.integrations?.length || 0;
805
+
806
+ return {
807
+ features,
808
+ integrationCount,
809
+ appName: appDefinition.name || 'Unnamed Frigg App',
810
+ ssmKmsKeyArn: appDefinition.ssm?.kmsKeyArn,
811
+ };
812
+ }
813
+
814
+ /**
815
+ * Generate basic IAM policy (JSON format) - Core Frigg permissions only
816
+ * @returns {Object} Basic IAM policy document
817
+ */
818
+ function generateBasicIAMPolicy() {
819
+ const basicPolicyPath = path.join(__dirname, 'templates/iam-policy-basic.json');
820
+ return require(basicPolicyPath);
821
+ }
822
+
823
+ /**
824
+ * Generate full IAM policy (JSON format) - All features enabled
825
+ * @returns {Object} Full IAM policy document
826
+ */
827
+ function generateFullIAMPolicy() {
828
+ const fullPolicyPath = path.join(__dirname, 'templates/iam-policy-full.json');
829
+ return require(fullPolicyPath);
830
+ }
831
+
832
+ /**
833
+ * Generate IAM policy based on mode
834
+ * @param {string} mode - 'basic' or 'full'
835
+ * @returns {Object} IAM policy document
836
+ */
837
+ function generateIAMPolicy(mode = 'basic') {
838
+ if (mode === 'full') {
839
+ return generateFullIAMPolicy();
840
+ }
841
+ return generateBasicIAMPolicy();
842
+ }
843
+
844
+ module.exports = {
845
+ generateIAMCloudFormation,
846
+ generateCloudFormationTemplate: generateIAMCloudFormation, // Alias for generate-command/index.js compatibility
847
+ getFeatureSummary,
848
+ generateBasicIAMPolicy,
849
+ generateFullIAMPolicy,
850
+ generateIAMPolicy,
851
+ };