@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,382 @@
1
+ /**
2
+ * Property Mutability Configuration
3
+ *
4
+ * Defines which CloudFormation resource properties are immutable (require replacement),
5
+ * mutable (can be updated), or conditional (depends on other properties).
6
+ *
7
+ * Based on AWS CloudFormation documentation "Update requires" behavior:
8
+ * - IMMUTABLE: "Replacement" - Cannot be changed without replacing the resource
9
+ * - MUTABLE: "No interruption" or "Some interruptions" - Can be updated in place
10
+ * - CONDITIONAL: Depends on other property values or specific conditions
11
+ *
12
+ * References:
13
+ * - AWS CloudFormation Template Reference: https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/
14
+ * - Each resource type has "Update requires" documentation for each property
15
+ */
16
+
17
+ const PropertyMutability = require('../value-objects/property-mutability');
18
+
19
+ /**
20
+ * Property mutability configuration by resource type
21
+ *
22
+ * Key: CloudFormation resource type (e.g., 'AWS::Lambda::Function')
23
+ * Value: Object mapping property paths to PropertyMutability instances
24
+ *
25
+ * Property paths match AWS drift detection format (without 'Properties.' prefix):
26
+ * - Simple: 'BucketName'
27
+ * - Nested: 'VpcConfig.SubnetIds'
28
+ */
29
+ const PROPERTY_MUTABILITY_CONFIG = {
30
+ //
31
+ // AWS::EC2::* Resources
32
+ //
33
+
34
+ 'AWS::EC2::VPC': {
35
+ // Immutable properties
36
+ 'CidrBlock': PropertyMutability.IMMUTABLE, // Replacement required
37
+ 'InstanceTenancy': PropertyMutability.IMMUTABLE, // Replacement required
38
+
39
+ // Mutable properties
40
+ 'EnableDnsSupport': PropertyMutability.MUTABLE, // No interruption
41
+ 'EnableDnsHostnames': PropertyMutability.MUTABLE, // No interruption
42
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
43
+ },
44
+
45
+ 'AWS::EC2::Subnet': {
46
+ // Immutable properties
47
+ 'VpcId': PropertyMutability.IMMUTABLE, // Replacement required
48
+ 'CidrBlock': PropertyMutability.IMMUTABLE, // Replacement required
49
+ 'AvailabilityZone': PropertyMutability.IMMUTABLE, // Replacement required
50
+ 'AvailabilityZoneId': PropertyMutability.IMMUTABLE, // Replacement required
51
+ 'Ipv4IpamPoolId': PropertyMutability.IMMUTABLE, // Replacement required
52
+ 'Ipv4NetmaskLength': PropertyMutability.IMMUTABLE, // Replacement required
53
+ 'Ipv6IpamPoolId': PropertyMutability.IMMUTABLE, // Replacement required
54
+ 'Ipv6Native': PropertyMutability.IMMUTABLE, // Replacement required
55
+ 'Ipv6NetmaskLength': PropertyMutability.IMMUTABLE, // Replacement required
56
+ 'OutpostArn': PropertyMutability.IMMUTABLE, // Replacement required
57
+
58
+ // Mutable properties
59
+ 'AssignIpv6AddressOnCreation': PropertyMutability.MUTABLE, // No interruption
60
+ 'EnableDns64': PropertyMutability.MUTABLE, // No interruption
61
+ 'EnableLniAtDeviceIndex': PropertyMutability.MUTABLE, // No interruption
62
+ 'Ipv6CidrBlock': PropertyMutability.MUTABLE, // Some interruptions
63
+ 'MapPublicIpOnLaunch': PropertyMutability.MUTABLE, // No interruption
64
+ 'PrivateDnsNameOptionsOnLaunch': PropertyMutability.MUTABLE, // No interruption
65
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
66
+ },
67
+
68
+ 'AWS::EC2::SecurityGroup': {
69
+ // Immutable properties
70
+ 'VpcId': PropertyMutability.IMMUTABLE, // Replacement required
71
+ 'GroupName': PropertyMutability.IMMUTABLE, // Replacement required
72
+
73
+ // Mutable properties
74
+ 'GroupDescription': PropertyMutability.MUTABLE, // No interruption
75
+ 'SecurityGroupIngress': PropertyMutability.MUTABLE, // No interruption
76
+ 'SecurityGroupEgress': PropertyMutability.MUTABLE, // No interruption
77
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
78
+ },
79
+
80
+ 'AWS::EC2::RouteTable': {
81
+ // Immutable properties
82
+ 'VpcId': PropertyMutability.IMMUTABLE, // Replacement required
83
+
84
+ // Mutable properties
85
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
86
+ },
87
+
88
+ 'AWS::EC2::Instance': {
89
+ // Immutable properties
90
+ 'ImageId': PropertyMutability.IMMUTABLE, // Replacement required
91
+ 'InstanceType': PropertyMutability.IMMUTABLE, // Replacement required
92
+ 'KeyName': PropertyMutability.IMMUTABLE, // Replacement required
93
+ 'AvailabilityZone': PropertyMutability.IMMUTABLE, // Replacement required
94
+ 'PlacementGroupName': PropertyMutability.IMMUTABLE, // Replacement required
95
+ 'PrivateIpAddress': PropertyMutability.IMMUTABLE, // Replacement required
96
+ 'SubnetId': PropertyMutability.IMMUTABLE, // Replacement required
97
+
98
+ // Mutable properties
99
+ 'SecurityGroupIds': PropertyMutability.MUTABLE, // No interruption (VPC instances)
100
+ 'SecurityGroups': PropertyMutability.MUTABLE, // No interruption
101
+ 'UserData': PropertyMutability.MUTABLE, // Some interruptions
102
+ 'IamInstanceProfile': PropertyMutability.MUTABLE, // No interruption
103
+ 'Monitoring': PropertyMutability.MUTABLE, // No interruption
104
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
105
+ 'DisableApiTermination': PropertyMutability.MUTABLE, // No interruption
106
+ 'EbsOptimized': PropertyMutability.MUTABLE, // Some interruptions
107
+ },
108
+
109
+ //
110
+ // AWS::Lambda::* Resources
111
+ //
112
+
113
+ 'AWS::Lambda::Function': {
114
+ // Immutable properties
115
+ 'FunctionName': PropertyMutability.IMMUTABLE, // Replacement required
116
+
117
+ // Mutable properties
118
+ 'Code': PropertyMutability.MUTABLE, // No interruption
119
+ 'Runtime': PropertyMutability.MUTABLE, // No interruption
120
+ 'Handler': PropertyMutability.MUTABLE, // No interruption
121
+ 'Role': PropertyMutability.MUTABLE, // No interruption
122
+ 'Description': PropertyMutability.MUTABLE, // No interruption
123
+ 'Timeout': PropertyMutability.MUTABLE, // No interruption
124
+ 'MemorySize': PropertyMutability.MUTABLE, // No interruption
125
+ 'Environment': PropertyMutability.MUTABLE, // No interruption
126
+ 'Environment.Variables': PropertyMutability.MUTABLE, // No interruption
127
+ 'VpcConfig': PropertyMutability.MUTABLE, // No interruption
128
+ 'VpcConfig.SubnetIds': PropertyMutability.MUTABLE, // No interruption
129
+ 'VpcConfig.SecurityGroupIds': PropertyMutability.MUTABLE, // No interruption
130
+ 'VpcConfig.Ipv6AllowedForDualStack': PropertyMutability.MUTABLE, // No interruption
131
+ 'DeadLetterConfig': PropertyMutability.MUTABLE, // No interruption
132
+ 'TracingConfig': PropertyMutability.MUTABLE, // No interruption
133
+ 'KMSKeyArn': PropertyMutability.MUTABLE, // No interruption
134
+ 'Layers': PropertyMutability.MUTABLE, // No interruption
135
+ 'ReservedConcurrentExecutions': PropertyMutability.MUTABLE, // No interruption
136
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
137
+ 'FileSystemConfigs': PropertyMutability.MUTABLE, // No interruption
138
+ 'Architectures': PropertyMutability.MUTABLE, // No interruption
139
+ 'EphemeralStorage': PropertyMutability.MUTABLE, // No interruption
140
+ 'SnapStart': PropertyMutability.MUTABLE, // No interruption
141
+ 'RuntimeManagementConfig': PropertyMutability.MUTABLE, // No interruption
142
+ 'LoggingConfig': PropertyMutability.MUTABLE, // No interruption
143
+ },
144
+
145
+ //
146
+ // AWS::RDS::* Resources
147
+ //
148
+
149
+ 'AWS::RDS::DBInstance': {
150
+ // Immutable properties
151
+ 'DBInstanceIdentifier': PropertyMutability.IMMUTABLE, // Replacement required
152
+ 'Engine': PropertyMutability.IMMUTABLE, // Replacement required
153
+ 'DBName': PropertyMutability.IMMUTABLE, // Replacement required (for some engines)
154
+ 'AvailabilityZone': PropertyMutability.IMMUTABLE, // Replacement required
155
+
156
+ // Mutable properties
157
+ 'AllocatedStorage': PropertyMutability.MUTABLE, // No interruption
158
+ 'DBInstanceClass': PropertyMutability.MUTABLE, // Some interruptions
159
+ 'EngineVersion': PropertyMutability.MUTABLE, // Some interruptions
160
+ 'MasterUsername': PropertyMutability.MUTABLE, // No interruption (can't actually change)
161
+ 'MasterUserPassword': PropertyMutability.MUTABLE, // No interruption
162
+ 'BackupRetentionPeriod': PropertyMutability.MUTABLE, // No interruption
163
+ 'DBSecurityGroups': PropertyMutability.MUTABLE, // No interruption
164
+ 'VPCSecurityGroups': PropertyMutability.MUTABLE, // No interruption
165
+ 'MultiAZ': PropertyMutability.MUTABLE, // No interruption
166
+ 'PubliclyAccessible': PropertyMutability.MUTABLE, // No interruption
167
+ 'StorageEncrypted': PropertyMutability.MUTABLE, // Some interruptions
168
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
169
+ },
170
+
171
+ 'AWS::RDS::DBCluster': {
172
+ // Immutable properties
173
+ 'DBClusterIdentifier': PropertyMutability.IMMUTABLE, // Replacement required
174
+ 'Engine': PropertyMutability.IMMUTABLE, // Replacement required
175
+ 'DatabaseName': PropertyMutability.IMMUTABLE, // Replacement required
176
+
177
+ // Mutable properties
178
+ 'EngineVersion': PropertyMutability.MUTABLE, // No interruption
179
+ 'MasterUsername': PropertyMutability.MUTABLE, // No interruption (can't actually change)
180
+ 'MasterUserPassword': PropertyMutability.MUTABLE, // No interruption
181
+ 'BackupRetentionPeriod': PropertyMutability.MUTABLE, // No interruption
182
+ 'PreferredBackupWindow': PropertyMutability.MUTABLE, // No interruption
183
+ 'PreferredMaintenanceWindow': PropertyMutability.MUTABLE, // No interruption
184
+ 'VpcSecurityGroupIds': PropertyMutability.MUTABLE, // No interruption
185
+ 'DBSubnetGroupName': PropertyMutability.MUTABLE, // Some interruptions
186
+ 'StorageEncrypted': PropertyMutability.MUTABLE, // Some interruptions
187
+ 'KmsKeyId': PropertyMutability.MUTABLE, // Some interruptions
188
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
189
+ },
190
+
191
+ //
192
+ // AWS::S3::* Resources
193
+ //
194
+
195
+ 'AWS::S3::Bucket': {
196
+ // Immutable properties
197
+ 'BucketName': PropertyMutability.IMMUTABLE, // Replacement required
198
+
199
+ // Mutable properties
200
+ 'AccelerateConfiguration': PropertyMutability.MUTABLE, // No interruption
201
+ 'AccessControl': PropertyMutability.MUTABLE, // No interruption
202
+ 'AnalyticsConfigurations': PropertyMutability.MUTABLE, // No interruption
203
+ 'BucketEncryption': PropertyMutability.MUTABLE, // No interruption
204
+ 'CorsConfiguration': PropertyMutability.MUTABLE, // No interruption
205
+ 'IntelligentTieringConfigurations': PropertyMutability.MUTABLE, // No interruption
206
+ 'InventoryConfigurations': PropertyMutability.MUTABLE, // No interruption
207
+ 'LifecycleConfiguration': PropertyMutability.MUTABLE, // No interruption
208
+ 'LoggingConfiguration': PropertyMutability.MUTABLE, // No interruption
209
+ 'MetricsConfigurations': PropertyMutability.MUTABLE, // No interruption
210
+ 'NotificationConfiguration': PropertyMutability.MUTABLE, // No interruption
211
+ 'ObjectLockConfiguration': PropertyMutability.MUTABLE, // No interruption
212
+ 'ObjectLockEnabled': PropertyMutability.MUTABLE, // No interruption
213
+ 'OwnershipControls': PropertyMutability.MUTABLE, // No interruption
214
+ 'PublicAccessBlockConfiguration': PropertyMutability.MUTABLE, // No interruption
215
+ 'ReplicationConfiguration': PropertyMutability.MUTABLE, // No interruption
216
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
217
+ 'VersioningConfiguration': PropertyMutability.MUTABLE, // No interruption
218
+ 'WebsiteConfiguration': PropertyMutability.MUTABLE, // No interruption
219
+ },
220
+
221
+ //
222
+ // AWS::KMS::* Resources
223
+ //
224
+
225
+ 'AWS::KMS::Key': {
226
+ // All KMS key properties are mutable (updates don't require replacement)
227
+ 'Description': PropertyMutability.MUTABLE, // No interruption
228
+ 'Enabled': PropertyMutability.MUTABLE, // No interruption
229
+ 'EnableKeyRotation': PropertyMutability.MUTABLE, // No interruption
230
+ 'KeyPolicy': PropertyMutability.MUTABLE, // No interruption
231
+ 'KeyUsage': PropertyMutability.MUTABLE, // No interruption
232
+ 'MultiRegion': PropertyMutability.MUTABLE, // No interruption
233
+ 'PendingWindowInDays': PropertyMutability.MUTABLE, // No interruption
234
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
235
+ },
236
+
237
+ //
238
+ // AWS::DynamoDB::* Resources
239
+ //
240
+
241
+ 'AWS::DynamoDB::Table': {
242
+ // Immutable properties
243
+ 'TableName': PropertyMutability.IMMUTABLE, // Replacement required
244
+ 'KeySchema': PropertyMutability.IMMUTABLE, // Replacement required
245
+ 'AttributeDefinitions': PropertyMutability.CONDITIONAL, // Some changes require replacement
246
+
247
+ // Mutable properties
248
+ 'BillingMode': PropertyMutability.MUTABLE, // No interruption
249
+ 'ProvisionedThroughput': PropertyMutability.MUTABLE, // No interruption
250
+ 'GlobalSecondaryIndexes': PropertyMutability.MUTABLE, // No interruption
251
+ 'LocalSecondaryIndexes': PropertyMutability.IMMUTABLE, // Replacement required
252
+ 'StreamSpecification': PropertyMutability.MUTABLE, // No interruption
253
+ 'SSESpecification': PropertyMutability.MUTABLE, // No interruption
254
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
255
+ 'TimeToLiveSpecification': PropertyMutability.MUTABLE, // No interruption
256
+ 'PointInTimeRecoverySpecification': PropertyMutability.MUTABLE, // No interruption
257
+ 'ContributorInsightsSpecification': PropertyMutability.MUTABLE, // No interruption
258
+ 'KinesisStreamSpecification': PropertyMutability.MUTABLE, // No interruption
259
+ },
260
+
261
+ //
262
+ // AWS::SQS::* Resources
263
+ //
264
+
265
+ 'AWS::SQS::Queue': {
266
+ // Immutable properties
267
+ 'QueueName': PropertyMutability.IMMUTABLE, // Replacement required
268
+ 'FifoQueue': PropertyMutability.IMMUTABLE, // Replacement required
269
+
270
+ // Mutable properties
271
+ 'ContentBasedDeduplication': PropertyMutability.MUTABLE, // No interruption
272
+ 'DeduplicationScope': PropertyMutability.MUTABLE, // No interruption
273
+ 'DelaySeconds': PropertyMutability.MUTABLE, // No interruption
274
+ 'FifoThroughputLimit': PropertyMutability.MUTABLE, // No interruption
275
+ 'KmsMasterKeyId': PropertyMutability.MUTABLE, // No interruption
276
+ 'KmsDataKeyReusePeriodSeconds': PropertyMutability.MUTABLE, // No interruption
277
+ 'MaximumMessageSize': PropertyMutability.MUTABLE, // No interruption
278
+ 'MessageRetentionPeriod': PropertyMutability.MUTABLE, // No interruption
279
+ 'ReceiveMessageWaitTimeSeconds': PropertyMutability.MUTABLE, // No interruption
280
+ 'RedrivePolicy': PropertyMutability.MUTABLE, // No interruption
281
+ 'RedriveAllowPolicy': PropertyMutability.MUTABLE, // No interruption
282
+ 'SqsManagedSseEnabled': PropertyMutability.MUTABLE, // No interruption
283
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
284
+ 'VisibilityTimeout': PropertyMutability.MUTABLE, // No interruption
285
+ },
286
+
287
+ //
288
+ // AWS::SNS::* Resources
289
+ //
290
+
291
+ 'AWS::SNS::Topic': {
292
+ // Immutable properties
293
+ 'TopicName': PropertyMutability.IMMUTABLE, // Replacement required
294
+ 'FifoTopic': PropertyMutability.IMMUTABLE, // Replacement required
295
+
296
+ // Mutable properties
297
+ 'ContentBasedDeduplication': PropertyMutability.MUTABLE, // No interruption
298
+ 'DataProtectionPolicy': PropertyMutability.MUTABLE, // No interruption
299
+ 'DisplayName': PropertyMutability.MUTABLE, // No interruption
300
+ 'KmsMasterKeyId': PropertyMutability.MUTABLE, // No interruption
301
+ 'SignatureVersion': PropertyMutability.MUTABLE, // No interruption
302
+ 'Subscription': PropertyMutability.MUTABLE, // No interruption
303
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
304
+ 'TracingConfig': PropertyMutability.MUTABLE, // No interruption
305
+ },
306
+
307
+ //
308
+ // AWS::IAM::* Resources
309
+ //
310
+
311
+ 'AWS::IAM::Role': {
312
+ // Immutable properties
313
+ 'RoleName': PropertyMutability.IMMUTABLE, // Replacement required
314
+
315
+ // Mutable properties
316
+ 'AssumeRolePolicyDocument': PropertyMutability.MUTABLE, // No interruption
317
+ 'Description': PropertyMutability.MUTABLE, // No interruption
318
+ 'ManagedPolicyArns': PropertyMutability.MUTABLE, // No interruption
319
+ 'MaxSessionDuration': PropertyMutability.MUTABLE, // No interruption
320
+ 'Path': PropertyMutability.MUTABLE, // No interruption
321
+ 'PermissionsBoundary': PropertyMutability.MUTABLE, // No interruption
322
+ 'Policies': PropertyMutability.MUTABLE, // No interruption
323
+ 'Tags': PropertyMutability.MUTABLE, // No interruption
324
+ },
325
+ };
326
+
327
+ /**
328
+ * Get property mutability for a given resource type and property path
329
+ *
330
+ * @param {string} resourceType - CloudFormation resource type (e.g., 'AWS::Lambda::Function')
331
+ * @param {string} propertyPath - Property path from drift detection (e.g., 'VpcConfig.SubnetIds')
332
+ * @returns {PropertyMutability} Property mutability (defaults to MUTABLE if not found)
333
+ */
334
+ function getPropertyMutability(resourceType, propertyPath) {
335
+ // Check if resource type has configuration
336
+ if (!PROPERTY_MUTABILITY_CONFIG[resourceType]) {
337
+ // Unknown resource type - default to MUTABLE (safe, allows reconciliation attempt)
338
+ return PropertyMutability.MUTABLE;
339
+ }
340
+
341
+ const resourceConfig = PROPERTY_MUTABILITY_CONFIG[resourceType];
342
+
343
+ // Check exact property path match
344
+ if (resourceConfig[propertyPath]) {
345
+ return resourceConfig[propertyPath];
346
+ }
347
+
348
+ // Check parent property for nested paths (e.g., 'VpcConfig' for 'VpcConfig.SubnetIds')
349
+ const parentPath = propertyPath.split('.')[0];
350
+ if (resourceConfig[parentPath]) {
351
+ return resourceConfig[parentPath];
352
+ }
353
+
354
+ // Property not found in config - default to MUTABLE
355
+ return PropertyMutability.MUTABLE;
356
+ }
357
+
358
+ /**
359
+ * Check if a resource type is supported in the configuration
360
+ *
361
+ * @param {string} resourceType - CloudFormation resource type
362
+ * @returns {boolean} True if resource type is configured
363
+ */
364
+ function isResourceTypeConfigured(resourceType) {
365
+ return resourceType in PROPERTY_MUTABILITY_CONFIG;
366
+ }
367
+
368
+ /**
369
+ * Get all configured resource types
370
+ *
371
+ * @returns {string[]} Array of resource type names
372
+ */
373
+ function getConfiguredResourceTypes() {
374
+ return Object.keys(PROPERTY_MUTABILITY_CONFIG);
375
+ }
376
+
377
+ module.exports = {
378
+ PROPERTY_MUTABILITY_CONFIG,
379
+ getPropertyMutability,
380
+ isResourceTypeConfigured,
381
+ getConfiguredResourceTypes,
382
+ };
@@ -0,0 +1,245 @@
1
+ /**
2
+ * TemplateParser - Parse CloudFormation templates for resource extraction
3
+ *
4
+ * Purpose: Parse both build templates (.serverless/) and deployed templates (from AWS)
5
+ * to extract resource definitions, logical IDs, and Refs for import mapping.
6
+ */
7
+
8
+ class TemplateParser {
9
+ /**
10
+ * Parse CloudFormation template and extract resource definitions
11
+ * @param {string|object} template - Template path or parsed template object
12
+ * @returns {object} Parsed template with resources
13
+ */
14
+ parseTemplate(template) {
15
+ let parsedTemplate;
16
+
17
+ if (typeof template === 'string') {
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ if (!fs.existsSync(template)) {
22
+ throw new Error(`Template not found at path: ${template}`);
23
+ }
24
+
25
+ parsedTemplate = JSON.parse(fs.readFileSync(template, 'utf8'));
26
+ } else {
27
+ parsedTemplate = template;
28
+ }
29
+
30
+ return {
31
+ resources: parsedTemplate.Resources || {},
32
+ version: parsedTemplate.AWSTemplateFormatVersion,
33
+ description: parsedTemplate.Description,
34
+ outputs: parsedTemplate.Outputs || {},
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Extract VPC-related resource logical IDs from template
40
+ * @param {object} template - Parsed template object
41
+ * @returns {Array} VPC resources with logical IDs
42
+ */
43
+ getVpcResources(template) {
44
+ const vpcResourceTypes = [
45
+ 'AWS::EC2::VPC',
46
+ 'AWS::EC2::Subnet',
47
+ 'AWS::EC2::SecurityGroup',
48
+ 'AWS::EC2::InternetGateway',
49
+ 'AWS::EC2::NatGateway',
50
+ 'AWS::EC2::RouteTable',
51
+ 'AWS::EC2::VPCEndpoint',
52
+ ];
53
+
54
+ return Object.entries(template.resources)
55
+ .filter(([_, resource]) => vpcResourceTypes.includes(resource.Type))
56
+ .map(([logicalId, resource]) => ({
57
+ logicalId,
58
+ resourceType: resource.Type,
59
+ properties: resource.Properties || {},
60
+ }));
61
+ }
62
+
63
+ /**
64
+ * Extract hardcoded resource IDs from deployed template
65
+ * Finds physical IDs that are hardcoded instead of using Refs
66
+ * @param {object} template - Deployed CloudFormation template
67
+ * @returns {object} Extracted hardcoded IDs by type
68
+ */
69
+ extractHardcodedIds(template) {
70
+ const hardcodedIds = {
71
+ vpcIds: new Set(),
72
+ subnetIds: new Set(),
73
+ securityGroupIds: new Set(),
74
+ };
75
+
76
+ // Traverse template to find hardcoded IDs
77
+ Object.values(template.resources).forEach((resource) => {
78
+ this._extractIdsFromResource(resource, hardcodedIds);
79
+ });
80
+
81
+ return {
82
+ vpcIds: Array.from(hardcodedIds.vpcIds),
83
+ subnetIds: Array.from(hardcodedIds.subnetIds),
84
+ securityGroupIds: Array.from(hardcodedIds.securityGroupIds),
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Extract Refs from build template
90
+ * Finds logical IDs that are referenced via {Ref: "LogicalId"}
91
+ * @param {object} template - Build template with Refs
92
+ * @returns {object} Logical IDs mapped to expected resource types
93
+ */
94
+ extractRefs(template) {
95
+ const refs = {
96
+ vpcRefs: new Set(),
97
+ subnetRefs: new Set(),
98
+ securityGroupRefs: new Set(),
99
+ };
100
+
101
+ // Traverse template to find Ref expressions
102
+ Object.values(template.resources).forEach((resource) => {
103
+ this._extractRefsFromResource(resource, refs);
104
+ });
105
+
106
+ return {
107
+ vpcRefs: Array.from(refs.vpcRefs),
108
+ subnetRefs: Array.from(refs.subnetRefs),
109
+ securityGroupRefs: Array.from(refs.securityGroupRefs),
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Recursively extract hardcoded IDs from resource properties
115
+ * @private
116
+ */
117
+ _extractIdsFromResource(obj, hardcodedIds) {
118
+ if (typeof obj !== 'object' || obj === null) return;
119
+
120
+ Object.entries(obj).forEach(([key, value]) => {
121
+ // Check for VPC IDs
122
+ if (key === 'VpcId' && typeof value === 'string' && value.startsWith('vpc-')) {
123
+ hardcodedIds.vpcIds.add(value);
124
+ }
125
+
126
+ // Check for subnet IDs
127
+ if (
128
+ (key === 'SubnetIds' || key === 'SubnetId') &&
129
+ Array.isArray(value)
130
+ ) {
131
+ value.forEach((id) => {
132
+ if (typeof id === 'string' && id.startsWith('subnet-')) {
133
+ hardcodedIds.subnetIds.add(id);
134
+ }
135
+ });
136
+ } else if (
137
+ key === 'SubnetId' &&
138
+ typeof value === 'string' &&
139
+ value.startsWith('subnet-')
140
+ ) {
141
+ hardcodedIds.subnetIds.add(value);
142
+ }
143
+
144
+ // Check for security group IDs
145
+ if (key === 'SecurityGroupIds' && Array.isArray(value)) {
146
+ value.forEach((id) => {
147
+ if (typeof id === 'string' && id.startsWith('sg-')) {
148
+ hardcodedIds.securityGroupIds.add(id);
149
+ }
150
+ });
151
+ } else if (
152
+ key === 'GroupId' &&
153
+ typeof value === 'string' &&
154
+ value.startsWith('sg-')
155
+ ) {
156
+ hardcodedIds.securityGroupIds.add(value);
157
+ }
158
+
159
+ // Recurse into nested objects
160
+ if (typeof value === 'object') {
161
+ this._extractIdsFromResource(value, hardcodedIds);
162
+ }
163
+ });
164
+ }
165
+
166
+ /**
167
+ * Recursively extract Refs from resource properties
168
+ * @private
169
+ */
170
+ _extractRefsFromResource(obj, refs) {
171
+ if (typeof obj !== 'object' || obj === null) return;
172
+
173
+ Object.entries(obj).forEach(([key, value]) => {
174
+ // Check for Ref expressions
175
+ if (key === 'Ref' && typeof value === 'string') {
176
+ // Determine ref type based on logical ID naming
177
+ if (value.includes('VPC') && !value.includes('Endpoint')) {
178
+ refs.vpcRefs.add(value);
179
+ } else if (value.includes('Subnet')) {
180
+ refs.subnetRefs.add(value);
181
+ } else if (value.includes('SecurityGroup')) {
182
+ refs.securityGroupRefs.add(value);
183
+ }
184
+ }
185
+
186
+ // Recurse into nested objects and arrays
187
+ if (typeof value === 'object') {
188
+ this._extractRefsFromResource(value, refs);
189
+ }
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Find logical ID for a physical ID by comparing templates
195
+ * @param {string} physicalId - Physical resource ID from AWS
196
+ * @param {object} deployedTemplate - Template with hardcoded IDs
197
+ * @param {object} buildTemplate - Template with Refs
198
+ * @returns {string|null} Matching logical ID or null
199
+ */
200
+ findLogicalIdForPhysicalId(physicalId, deployedTemplate, buildTemplate) {
201
+ // Extract hardcoded IDs and their context
202
+ const hardcodedIds = this.extractHardcodedIds(deployedTemplate);
203
+ const refs = this.extractRefs(buildTemplate);
204
+
205
+ // Determine resource type from physical ID
206
+ let logicalIdCandidates = [];
207
+ if (physicalId.startsWith('vpc-')) {
208
+ logicalIdCandidates = refs.vpcRefs;
209
+ } else if (physicalId.startsWith('subnet-')) {
210
+ logicalIdCandidates = refs.subnetRefs;
211
+ } else if (physicalId.startsWith('sg-')) {
212
+ logicalIdCandidates = refs.securityGroupRefs;
213
+ }
214
+
215
+ // For now, return first candidate (will enhance with position matching)
216
+ return logicalIdCandidates[0] || null;
217
+ }
218
+
219
+ /**
220
+ * Get build template path from project directory
221
+ * @param {string} projectPath - Project root path
222
+ * @returns {string} Path to build template
223
+ */
224
+ static getBuildTemplatePath(projectPath = process.cwd()) {
225
+ const path = require('path');
226
+ return path.join(
227
+ projectPath,
228
+ '.serverless',
229
+ 'cloudformation-template-update-stack.json'
230
+ );
231
+ }
232
+
233
+ /**
234
+ * Check if build template exists
235
+ * @param {string} projectPath - Project root path
236
+ * @returns {boolean} True if template exists
237
+ */
238
+ static buildTemplateExists(projectPath = process.cwd()) {
239
+ const fs = require('fs');
240
+ const templatePath = this.getBuildTemplatePath(projectPath);
241
+ return fs.existsSync(templatePath);
242
+ }
243
+ }
244
+
245
+ module.exports = { TemplateParser };