@stacksjs/ts-cloud-core 0.1.1

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 (251) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +321 -0
  3. package/package.json +31 -0
  4. package/src/advanced-features.test.ts +465 -0
  5. package/src/aws/cloudformation.ts +421 -0
  6. package/src/aws/cloudfront.ts +158 -0
  7. package/src/aws/credentials.test.ts +132 -0
  8. package/src/aws/credentials.ts +545 -0
  9. package/src/aws/index.ts +87 -0
  10. package/src/aws/s3.test.ts +188 -0
  11. package/src/aws/s3.ts +1088 -0
  12. package/src/aws/signature.test.ts +670 -0
  13. package/src/aws/signature.ts +1155 -0
  14. package/src/backup/disaster-recovery.test.ts +726 -0
  15. package/src/backup/disaster-recovery.ts +500 -0
  16. package/src/backup/index.ts +34 -0
  17. package/src/backup/manager.test.ts +498 -0
  18. package/src/backup/manager.ts +432 -0
  19. package/src/cicd/circleci.ts +430 -0
  20. package/src/cicd/github-actions.ts +424 -0
  21. package/src/cicd/gitlab-ci.ts +255 -0
  22. package/src/cicd/index.ts +8 -0
  23. package/src/cli/history.ts +396 -0
  24. package/src/cli/index.ts +10 -0
  25. package/src/cli/progress.ts +458 -0
  26. package/src/cli/repl.ts +454 -0
  27. package/src/cli/suggestions.ts +327 -0
  28. package/src/cli/table.test.ts +319 -0
  29. package/src/cli/table.ts +332 -0
  30. package/src/cloudformation/builder.test.ts +327 -0
  31. package/src/cloudformation/builder.ts +378 -0
  32. package/src/cloudformation/builders/api-gateway.ts +449 -0
  33. package/src/cloudformation/builders/cache.ts +334 -0
  34. package/src/cloudformation/builders/cdn.ts +278 -0
  35. package/src/cloudformation/builders/compute.ts +485 -0
  36. package/src/cloudformation/builders/database.ts +392 -0
  37. package/src/cloudformation/builders/functions.ts +343 -0
  38. package/src/cloudformation/builders/messaging.ts +140 -0
  39. package/src/cloudformation/builders/monitoring.ts +300 -0
  40. package/src/cloudformation/builders/network.ts +264 -0
  41. package/src/cloudformation/builders/queue.ts +147 -0
  42. package/src/cloudformation/builders/security.ts +399 -0
  43. package/src/cloudformation/builders/storage.ts +285 -0
  44. package/src/cloudformation/index.ts +30 -0
  45. package/src/cloudformation/types.ts +173 -0
  46. package/src/compliance/aws-config.ts +543 -0
  47. package/src/compliance/cloudtrail.ts +376 -0
  48. package/src/compliance/compliance.test.ts +423 -0
  49. package/src/compliance/guardduty.ts +446 -0
  50. package/src/compliance/index.ts +66 -0
  51. package/src/compliance/security-hub.ts +456 -0
  52. package/src/containers/build-optimization.ts +416 -0
  53. package/src/containers/containers.test.ts +508 -0
  54. package/src/containers/image-scanning.ts +360 -0
  55. package/src/containers/index.ts +9 -0
  56. package/src/containers/registry.ts +293 -0
  57. package/src/containers/service-mesh.ts +520 -0
  58. package/src/database/database.test.ts +762 -0
  59. package/src/database/index.ts +9 -0
  60. package/src/database/migrations.ts +444 -0
  61. package/src/database/performance.ts +528 -0
  62. package/src/database/replicas.ts +534 -0
  63. package/src/database/users.ts +494 -0
  64. package/src/dependency-graph.ts +143 -0
  65. package/src/deployment/ab-testing.ts +582 -0
  66. package/src/deployment/blue-green.ts +452 -0
  67. package/src/deployment/canary.ts +500 -0
  68. package/src/deployment/deployment.test.ts +526 -0
  69. package/src/deployment/index.ts +61 -0
  70. package/src/deployment/progressive.ts +62 -0
  71. package/src/dns/dns.test.ts +641 -0
  72. package/src/dns/dnssec.ts +315 -0
  73. package/src/dns/index.ts +8 -0
  74. package/src/dns/resolver.ts +496 -0
  75. package/src/dns/routing.ts +593 -0
  76. package/src/email/advanced/analytics.ts +445 -0
  77. package/src/email/advanced/index.ts +11 -0
  78. package/src/email/advanced/rules.ts +465 -0
  79. package/src/email/advanced/scheduling.ts +352 -0
  80. package/src/email/advanced/search.ts +412 -0
  81. package/src/email/advanced/shared-mailboxes.ts +404 -0
  82. package/src/email/advanced/templates.ts +455 -0
  83. package/src/email/advanced/threading.ts +281 -0
  84. package/src/email/analytics.ts +467 -0
  85. package/src/email/bounce-handling.ts +425 -0
  86. package/src/email/email.test.ts +431 -0
  87. package/src/email/handlers/__tests__/inbound.test.ts +38 -0
  88. package/src/email/handlers/__tests__/outbound.test.ts +37 -0
  89. package/src/email/handlers/converter.ts +227 -0
  90. package/src/email/handlers/feedback.ts +228 -0
  91. package/src/email/handlers/inbound.ts +169 -0
  92. package/src/email/handlers/outbound.ts +178 -0
  93. package/src/email/index.ts +15 -0
  94. package/src/email/reputation.ts +303 -0
  95. package/src/email/templates.ts +352 -0
  96. package/src/errors/index.test.ts +434 -0
  97. package/src/errors/index.ts +416 -0
  98. package/src/health-checks/index.ts +40 -0
  99. package/src/index.ts +360 -0
  100. package/src/intrinsic-functions.ts +118 -0
  101. package/src/lambda/concurrency.ts +330 -0
  102. package/src/lambda/destinations.ts +345 -0
  103. package/src/lambda/dlq.ts +425 -0
  104. package/src/lambda/index.ts +11 -0
  105. package/src/lambda/lambda.test.ts +840 -0
  106. package/src/lambda/layers.ts +263 -0
  107. package/src/lambda/versions.ts +376 -0
  108. package/src/lambda/vpc.ts +399 -0
  109. package/src/local/config.ts +114 -0
  110. package/src/local/index.ts +6 -0
  111. package/src/local/mock-aws.ts +351 -0
  112. package/src/modules/ai.ts +340 -0
  113. package/src/modules/api.ts +478 -0
  114. package/src/modules/auth.ts +805 -0
  115. package/src/modules/cache.ts +417 -0
  116. package/src/modules/cdn.ts +1062 -0
  117. package/src/modules/communication.ts +1094 -0
  118. package/src/modules/compute.ts +3348 -0
  119. package/src/modules/database.ts +554 -0
  120. package/src/modules/deployment.ts +1079 -0
  121. package/src/modules/dns.ts +337 -0
  122. package/src/modules/email.ts +1538 -0
  123. package/src/modules/filesystem.ts +515 -0
  124. package/src/modules/index.ts +32 -0
  125. package/src/modules/messaging.ts +486 -0
  126. package/src/modules/monitoring.ts +2086 -0
  127. package/src/modules/network.ts +664 -0
  128. package/src/modules/parameter-store.ts +325 -0
  129. package/src/modules/permissions.ts +1081 -0
  130. package/src/modules/phone.ts +494 -0
  131. package/src/modules/queue.ts +1260 -0
  132. package/src/modules/redirects.ts +464 -0
  133. package/src/modules/registry.ts +699 -0
  134. package/src/modules/search.ts +401 -0
  135. package/src/modules/secrets.ts +416 -0
  136. package/src/modules/security.ts +731 -0
  137. package/src/modules/sms.ts +389 -0
  138. package/src/modules/storage.ts +1120 -0
  139. package/src/modules/workflow.ts +680 -0
  140. package/src/multi-account/config.ts +521 -0
  141. package/src/multi-account/index.ts +7 -0
  142. package/src/multi-account/manager.ts +427 -0
  143. package/src/multi-region/cross-region.ts +410 -0
  144. package/src/multi-region/index.ts +8 -0
  145. package/src/multi-region/manager.ts +483 -0
  146. package/src/multi-region/regions.ts +435 -0
  147. package/src/network-security/index.ts +48 -0
  148. package/src/observability/index.ts +9 -0
  149. package/src/observability/logs.ts +522 -0
  150. package/src/observability/metrics.ts +460 -0
  151. package/src/observability/observability.test.ts +782 -0
  152. package/src/observability/synthetics.ts +568 -0
  153. package/src/observability/xray.ts +358 -0
  154. package/src/phone/advanced/analytics.ts +349 -0
  155. package/src/phone/advanced/callbacks.ts +428 -0
  156. package/src/phone/advanced/index.ts +8 -0
  157. package/src/phone/advanced/ivr-builder.ts +504 -0
  158. package/src/phone/advanced/recording.ts +310 -0
  159. package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
  160. package/src/phone/handlers/incoming-call.ts +117 -0
  161. package/src/phone/handlers/missed-call.ts +116 -0
  162. package/src/phone/handlers/voicemail.ts +179 -0
  163. package/src/phone/index.ts +9 -0
  164. package/src/presets/api-backend.ts +134 -0
  165. package/src/presets/data-pipeline.ts +204 -0
  166. package/src/presets/extend.test.ts +295 -0
  167. package/src/presets/extend.ts +297 -0
  168. package/src/presets/fullstack-app.ts +144 -0
  169. package/src/presets/index.ts +27 -0
  170. package/src/presets/jamstack.ts +135 -0
  171. package/src/presets/microservices.ts +167 -0
  172. package/src/presets/ml-api.ts +208 -0
  173. package/src/presets/nodejs-server.ts +104 -0
  174. package/src/presets/nodejs-serverless.ts +114 -0
  175. package/src/presets/realtime-app.ts +184 -0
  176. package/src/presets/static-site.ts +64 -0
  177. package/src/presets/traditional-web-app.ts +339 -0
  178. package/src/presets/wordpress.ts +138 -0
  179. package/src/preview/github.test.ts +249 -0
  180. package/src/preview/github.ts +297 -0
  181. package/src/preview/index.ts +37 -0
  182. package/src/preview/manager.test.ts +440 -0
  183. package/src/preview/manager.ts +326 -0
  184. package/src/preview/notifications.test.ts +582 -0
  185. package/src/preview/notifications.ts +341 -0
  186. package/src/queue/batch-processing.ts +402 -0
  187. package/src/queue/dlq-monitoring.ts +402 -0
  188. package/src/queue/fifo.ts +342 -0
  189. package/src/queue/index.ts +9 -0
  190. package/src/queue/management.ts +428 -0
  191. package/src/queue/queue.test.ts +429 -0
  192. package/src/resource-mgmt/index.ts +39 -0
  193. package/src/resource-naming.ts +62 -0
  194. package/src/s3/index.ts +523 -0
  195. package/src/schema/cloud-config.schema.json +554 -0
  196. package/src/schema/index.ts +68 -0
  197. package/src/security/certificate-manager.ts +492 -0
  198. package/src/security/index.ts +9 -0
  199. package/src/security/scanning.ts +545 -0
  200. package/src/security/secrets-manager.ts +476 -0
  201. package/src/security/secrets-rotation.ts +456 -0
  202. package/src/security/security.test.ts +738 -0
  203. package/src/sms/advanced/ab-testing.ts +389 -0
  204. package/src/sms/advanced/analytics.ts +336 -0
  205. package/src/sms/advanced/campaigns.ts +523 -0
  206. package/src/sms/advanced/chatbot.ts +224 -0
  207. package/src/sms/advanced/index.ts +10 -0
  208. package/src/sms/advanced/link-tracking.ts +248 -0
  209. package/src/sms/advanced/mms.ts +308 -0
  210. package/src/sms/handlers/__tests__/send.test.ts +40 -0
  211. package/src/sms/handlers/delivery-status.ts +133 -0
  212. package/src/sms/handlers/receive.ts +162 -0
  213. package/src/sms/handlers/send.ts +174 -0
  214. package/src/sms/index.ts +9 -0
  215. package/src/stack-diff.ts +389 -0
  216. package/src/static-site/index.ts +85 -0
  217. package/src/template-builder.ts +110 -0
  218. package/src/template-validator.ts +574 -0
  219. package/src/utils/cache.ts +291 -0
  220. package/src/utils/diff.ts +269 -0
  221. package/src/utils/hash.ts +227 -0
  222. package/src/utils/index.ts +8 -0
  223. package/src/utils/parallel.ts +294 -0
  224. package/src/validators/credentials.test.ts +274 -0
  225. package/src/validators/credentials.ts +233 -0
  226. package/src/validators/quotas.test.ts +434 -0
  227. package/src/validators/quotas.ts +217 -0
  228. package/test/ai.test.ts +327 -0
  229. package/test/api.test.ts +511 -0
  230. package/test/auth.test.ts +632 -0
  231. package/test/cache.test.ts +406 -0
  232. package/test/cdn.test.ts +247 -0
  233. package/test/compute.test.ts +861 -0
  234. package/test/database.test.ts +523 -0
  235. package/test/deployment.test.ts +499 -0
  236. package/test/dns.test.ts +270 -0
  237. package/test/email.test.ts +439 -0
  238. package/test/filesystem.test.ts +382 -0
  239. package/test/integration.test.ts +350 -0
  240. package/test/messaging.test.ts +514 -0
  241. package/test/monitoring.test.ts +634 -0
  242. package/test/network.test.ts +425 -0
  243. package/test/permissions.test.ts +488 -0
  244. package/test/queue.test.ts +484 -0
  245. package/test/registry.test.ts +306 -0
  246. package/test/security.test.ts +462 -0
  247. package/test/storage.test.ts +463 -0
  248. package/test/template-validator.test.ts +559 -0
  249. package/test/workflow.test.ts +592 -0
  250. package/tsconfig.json +16 -0
  251. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Caching utilities for performance optimization
3
+ * Caches CloudFormation templates, credentials, and other data
4
+ */
5
+
6
+ import { createHash } from 'node:crypto'
7
+ import fs from 'node:fs'
8
+ import path from 'node:path'
9
+
10
+ export interface CacheOptions {
11
+ ttl?: number // Time to live in milliseconds
12
+ maxSize?: number // Maximum cache size
13
+ }
14
+
15
+ export interface CacheEntry<T> {
16
+ value: T
17
+ timestamp: number
18
+ hash?: string
19
+ }
20
+
21
+ /**
22
+ * Simple in-memory cache with TTL support
23
+ */
24
+ export class Cache<T = any> {
25
+ private cache: Map<string, CacheEntry<T>>
26
+ private ttl: number
27
+ private maxSize: number
28
+
29
+ constructor(options: CacheOptions = {}) {
30
+ this.cache = new Map()
31
+ this.ttl = options.ttl || 5 * 60 * 1000 // Default: 5 minutes
32
+ this.maxSize = options.maxSize || 100 // Default: 100 entries
33
+ }
34
+
35
+ /**
36
+ * Get value from cache
37
+ */
38
+ get(key: string): T | undefined {
39
+ const entry = this.cache.get(key)
40
+
41
+ if (!entry) {
42
+ return undefined
43
+ }
44
+
45
+ // Check if entry has expired
46
+ if (Date.now() - entry.timestamp > this.ttl) {
47
+ this.cache.delete(key)
48
+ return undefined
49
+ }
50
+
51
+ return entry.value
52
+ }
53
+
54
+ /**
55
+ * Set value in cache
56
+ */
57
+ set(key: string, value: T, hash?: string): void {
58
+ // If cache is full, remove oldest entry
59
+ if (this.cache.size >= this.maxSize) {
60
+ const firstKey = this.cache.keys().next().value
61
+ if (firstKey !== undefined) {
62
+ this.cache.delete(firstKey)
63
+ }
64
+ }
65
+
66
+ this.cache.set(key, {
67
+ value,
68
+ timestamp: Date.now(),
69
+ hash,
70
+ })
71
+ }
72
+
73
+ /**
74
+ * Check if cache has key and it's not expired
75
+ */
76
+ has(key: string): boolean {
77
+ return this.get(key) !== undefined
78
+ }
79
+
80
+ /**
81
+ * Clear cache
82
+ */
83
+ clear(): void {
84
+ this.cache.clear()
85
+ }
86
+
87
+ /**
88
+ * Remove expired entries
89
+ */
90
+ prune(): void {
91
+ const now = Date.now()
92
+ for (const [key, entry] of this.cache.entries()) {
93
+ if (now - entry.timestamp > this.ttl) {
94
+ this.cache.delete(key)
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Get cache stats
101
+ */
102
+ stats(): { size: number, ttl: number, maxSize: number } {
103
+ return {
104
+ size: this.cache.size,
105
+ ttl: this.ttl,
106
+ maxSize: this.maxSize,
107
+ }
108
+ }
109
+ }
110
+
111
+ /**
112
+ * File-based cache for persistent caching
113
+ */
114
+ export class FileCache<T = any> {
115
+ private cacheDir: string
116
+ private ttl: number
117
+
118
+ constructor(cacheDir: string, options: CacheOptions = {}) {
119
+ this.cacheDir = cacheDir
120
+ this.ttl = options.ttl || 24 * 60 * 60 * 1000 // Default: 24 hours
121
+
122
+ // Create cache directory if it doesn't exist
123
+ if (!fs.existsSync(cacheDir)) {
124
+ fs.mkdirSync(cacheDir, { recursive: true })
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Get cache file path for key
130
+ */
131
+ private getCachePath(key: string): string {
132
+ const hash = createHash('sha256').update(key).digest('hex')
133
+ return path.join(this.cacheDir, `${hash}.json`)
134
+ }
135
+
136
+ /**
137
+ * Get value from cache
138
+ */
139
+ get(key: string): T | undefined {
140
+ const cachePath = this.getCachePath(key)
141
+
142
+ if (!fs.existsSync(cachePath)) {
143
+ return undefined
144
+ }
145
+
146
+ try {
147
+ const data = fs.readFileSync(cachePath, 'utf-8')
148
+ const entry: CacheEntry<T> = JSON.parse(data)
149
+
150
+ // Check if entry has expired
151
+ if (Date.now() - entry.timestamp > this.ttl) {
152
+ fs.unlinkSync(cachePath)
153
+ return undefined
154
+ }
155
+
156
+ return entry.value
157
+ }
158
+ catch {
159
+ // If cache file is corrupted, delete it
160
+ fs.unlinkSync(cachePath)
161
+ return undefined
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Set value in cache
167
+ */
168
+ set(key: string, value: T, hash?: string): void {
169
+ const cachePath = this.getCachePath(key)
170
+
171
+ const entry: CacheEntry<T> = {
172
+ value,
173
+ timestamp: Date.now(),
174
+ hash,
175
+ }
176
+
177
+ fs.writeFileSync(cachePath, JSON.stringify(entry), 'utf-8')
178
+ }
179
+
180
+ /**
181
+ * Check if cache has key and it's not expired
182
+ */
183
+ has(key: string): boolean {
184
+ return this.get(key) !== undefined
185
+ }
186
+
187
+ /**
188
+ * Clear all cache files
189
+ */
190
+ clear(): void {
191
+ const files = fs.readdirSync(this.cacheDir)
192
+ for (const file of files) {
193
+ fs.unlinkSync(path.join(this.cacheDir, file))
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Remove expired entries
199
+ */
200
+ prune(): void {
201
+ const files = fs.readdirSync(this.cacheDir)
202
+ const now = Date.now()
203
+
204
+ for (const file of files) {
205
+ const filePath = path.join(this.cacheDir, file)
206
+
207
+ try {
208
+ const data = fs.readFileSync(filePath, 'utf-8')
209
+ const entry: CacheEntry<any> = JSON.parse(data)
210
+
211
+ if (now - entry.timestamp > this.ttl) {
212
+ fs.unlinkSync(filePath)
213
+ }
214
+ }
215
+ catch {
216
+ // If file is corrupted, delete it
217
+ fs.unlinkSync(filePath)
218
+ }
219
+ }
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Template cache for CloudFormation templates
225
+ */
226
+ export class TemplateCache {
227
+ private cache: FileCache<string>
228
+
229
+ constructor(cacheDir: string = '.ts-cloud/cache/templates') {
230
+ this.cache = new FileCache<string>(cacheDir, {
231
+ ttl: 24 * 60 * 60 * 1000, // 24 hours
232
+ })
233
+ }
234
+
235
+ /**
236
+ * Get template from cache
237
+ */
238
+ getTemplate(stackName: string): string | undefined {
239
+ return this.cache.get(`template:${stackName}`)
240
+ }
241
+
242
+ /**
243
+ * Save template to cache
244
+ */
245
+ setTemplate(stackName: string, template: string): void {
246
+ const hash = this.hashTemplate(template)
247
+ this.cache.set(`template:${stackName}`, template, hash)
248
+ }
249
+
250
+ /**
251
+ * Check if template has changed
252
+ */
253
+ hasChanged(stackName: string, newTemplate: string): boolean {
254
+ const cached = this.cache.get(`template:${stackName}`)
255
+
256
+ if (!cached) {
257
+ return true
258
+ }
259
+
260
+ const cachedHash = this.hashTemplate(cached)
261
+ const newHash = this.hashTemplate(newTemplate)
262
+
263
+ return cachedHash !== newHash
264
+ }
265
+
266
+ /**
267
+ * Hash template for comparison
268
+ */
269
+ private hashTemplate(template: string): string {
270
+ return createHash('sha256').update(template).digest('hex')
271
+ }
272
+
273
+ /**
274
+ * Clear all templates
275
+ */
276
+ clear(): void {
277
+ this.cache.clear()
278
+ }
279
+
280
+ /**
281
+ * Prune expired templates
282
+ */
283
+ prune(): void {
284
+ this.cache.prune()
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Global template cache instance
290
+ */
291
+ export const templateCache: TemplateCache = new TemplateCache()
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Template diff utilities for incremental deployments
3
+ * Detect changes between CloudFormation templates to optimize deployments
4
+ */
5
+
6
+ import type { CloudFormationTemplate } from '../cloudformation/types'
7
+
8
+ export interface TemplateDiff {
9
+ added: string[] // Resource logical IDs
10
+ modified: string[] // Resource logical IDs
11
+ deleted: string[] // Resource logical IDs
12
+ unchanged: string[] // Resource logical IDs
13
+ parametersChanged: boolean
14
+ outputsChanged: boolean
15
+ hasChanges: boolean
16
+ }
17
+
18
+ /**
19
+ * Compare two CloudFormation templates
20
+ */
21
+ export function diffTemplates(
22
+ oldTemplate: CloudFormationTemplate,
23
+ newTemplate: CloudFormationTemplate,
24
+ ): TemplateDiff {
25
+ const added: string[] = []
26
+ const modified: string[] = []
27
+ const deleted: string[] = []
28
+ const unchanged: string[] = []
29
+
30
+ const oldResources = oldTemplate.Resources || {}
31
+ const newResources = newTemplate.Resources || {}
32
+
33
+ const oldResourceIds = new Set(Object.keys(oldResources))
34
+ const newResourceIds = new Set(Object.keys(newResources))
35
+
36
+ // Find added resources
37
+ for (const id of newResourceIds) {
38
+ if (!oldResourceIds.has(id)) {
39
+ added.push(id)
40
+ }
41
+ }
42
+
43
+ // Find deleted resources
44
+ for (const id of oldResourceIds) {
45
+ if (!newResourceIds.has(id)) {
46
+ deleted.push(id)
47
+ }
48
+ }
49
+
50
+ // Find modified resources
51
+ for (const id of newResourceIds) {
52
+ if (oldResourceIds.has(id)) {
53
+ const oldResource = oldResources[id]
54
+ const newResource = newResources[id]
55
+
56
+ if (JSON.stringify(oldResource) !== JSON.stringify(newResource)) {
57
+ modified.push(id)
58
+ }
59
+ else {
60
+ unchanged.push(id)
61
+ }
62
+ }
63
+ }
64
+
65
+ // Check if parameters changed
66
+ const parametersChanged = JSON.stringify(oldTemplate.Parameters || {})
67
+ !== JSON.stringify(newTemplate.Parameters || {})
68
+
69
+ // Check if outputs changed
70
+ const outputsChanged = JSON.stringify(oldTemplate.Outputs || {})
71
+ !== JSON.stringify(newTemplate.Outputs || {})
72
+
73
+ return {
74
+ added,
75
+ modified,
76
+ deleted,
77
+ unchanged,
78
+ parametersChanged,
79
+ outputsChanged,
80
+ hasChanges: added.length > 0 || modified.length > 0 || deleted.length > 0 || parametersChanged || outputsChanged,
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Get diff summary string
86
+ */
87
+ export function getDiffSummary(diff: TemplateDiff): string {
88
+ const lines: string[] = []
89
+
90
+ if (!diff.hasChanges) {
91
+ return 'No changes detected'
92
+ }
93
+
94
+ if (diff.added.length > 0) {
95
+ lines.push(`Added resources (${diff.added.length}):`)
96
+ for (const id of diff.added) {
97
+ lines.push(` + ${id}`)
98
+ }
99
+ }
100
+
101
+ if (diff.modified.length > 0) {
102
+ lines.push(`Modified resources (${diff.modified.length}):`)
103
+ for (const id of diff.modified) {
104
+ lines.push(` ~ ${id}`)
105
+ }
106
+ }
107
+
108
+ if (diff.deleted.length > 0) {
109
+ lines.push(`Deleted resources (${diff.deleted.length}):`)
110
+ for (const id of diff.deleted) {
111
+ lines.push(` - ${id}`)
112
+ }
113
+ }
114
+
115
+ if (diff.parametersChanged) {
116
+ lines.push('Parameters changed')
117
+ }
118
+
119
+ if (diff.outputsChanged) {
120
+ lines.push('Outputs changed')
121
+ }
122
+
123
+ return lines.join('\n')
124
+ }
125
+
126
+ /**
127
+ * Check if diff requires replacement (destructive changes)
128
+ */
129
+ export function requiresReplacement(
130
+ diff: TemplateDiff,
131
+ oldTemplate: CloudFormationTemplate,
132
+ newTemplate: CloudFormationTemplate,
133
+ ): boolean {
134
+ // If any resources are deleted, it requires replacement
135
+ if (diff.deleted.length > 0) {
136
+ return true
137
+ }
138
+
139
+ // Check modified resources for replacement-requiring changes
140
+ for (const id of diff.modified) {
141
+ const oldResource = oldTemplate.Resources[id]
142
+ const newResource = newTemplate.Resources[id]
143
+
144
+ // If resource type changed, it requires replacement
145
+ if (oldResource.Type !== newResource.Type) {
146
+ return true
147
+ }
148
+
149
+ // Check if properties that require replacement changed
150
+ // This is a simplified check - in reality, each resource type has specific properties
151
+ const replacementProperties = getReplacementProperties(newResource.Type)
152
+
153
+ for (const prop of replacementProperties) {
154
+ const oldValue = oldResource.Properties?.[prop]
155
+ const newValue = newResource.Properties?.[prop]
156
+
157
+ if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
158
+ return true
159
+ }
160
+ }
161
+ }
162
+
163
+ return false
164
+ }
165
+
166
+ /**
167
+ * Get properties that require replacement when changed
168
+ * This is a simplified version - production code should use CloudFormation property docs
169
+ */
170
+ function getReplacementProperties(resourceType: string): string[] {
171
+ const replacementProps: Record<string, string[]> = {
172
+ 'AWS::S3::Bucket': ['BucketName'],
173
+ 'AWS::EC2::Instance': ['ImageId', 'InstanceType', 'KeyName'],
174
+ 'AWS::RDS::DBInstance': ['DBInstanceIdentifier', 'Engine'],
175
+ 'AWS::DynamoDB::Table': ['TableName', 'KeySchema'],
176
+ 'AWS::Lambda::Function': ['FunctionName'],
177
+ 'AWS::ECS::Service': ['ServiceName'],
178
+ 'AWS::ElasticLoadBalancingV2::LoadBalancer': ['Name', 'Type'],
179
+ }
180
+
181
+ return replacementProps[resourceType] || []
182
+ }
183
+
184
+ /**
185
+ * Categorize changes by risk level
186
+ */
187
+ export function categorizeChanges(diff: TemplateDiff): {
188
+ safe: string[]
189
+ caution: string[]
190
+ dangerous: string[]
191
+ } {
192
+ const safe: string[] = []
193
+ const caution: string[] = []
194
+ const dangerous: string[] = []
195
+
196
+ // Additions are generally safe
197
+ safe.push(...diff.added)
198
+
199
+ // Modifications need to be categorized
200
+ // This is simplified - real implementation would analyze specific property changes
201
+ for (const id of diff.modified) {
202
+ // For now, mark all modifications as caution
203
+ caution.push(id)
204
+ }
205
+
206
+ // Deletions are dangerous
207
+ dangerous.push(...diff.deleted)
208
+
209
+ return { safe, caution, dangerous }
210
+ }
211
+
212
+ /**
213
+ * Get deployment strategy based on diff
214
+ */
215
+ export function getDeploymentStrategy(diff: TemplateDiff): {
216
+ strategy: 'create' | 'update' | 'replace' | 'skip'
217
+ reason: string
218
+ } {
219
+ if (!diff.hasChanges) {
220
+ return {
221
+ strategy: 'skip',
222
+ reason: 'No changes detected',
223
+ }
224
+ }
225
+
226
+ if (diff.deleted.length > 0) {
227
+ return {
228
+ strategy: 'replace',
229
+ reason: 'Resources will be deleted',
230
+ }
231
+ }
232
+
233
+ if (diff.added.length > 0 && diff.modified.length === 0) {
234
+ return {
235
+ strategy: 'update',
236
+ reason: 'Only new resources added',
237
+ }
238
+ }
239
+
240
+ return {
241
+ strategy: 'update',
242
+ reason: 'Resources will be updated',
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Calculate diff statistics
248
+ */
249
+ export function getDiffStats(diff: TemplateDiff): {
250
+ total: number
251
+ added: number
252
+ modified: number
253
+ deleted: number
254
+ unchanged: number
255
+ changePercentage: number
256
+ } {
257
+ const total = diff.added.length + diff.modified.length + diff.deleted.length + diff.unchanged.length
258
+ const changes = diff.added.length + diff.modified.length + diff.deleted.length
259
+ const changePercentage = total > 0 ? (changes / total) * 100 : 0
260
+
261
+ return {
262
+ total,
263
+ added: diff.added.length,
264
+ modified: diff.modified.length,
265
+ deleted: diff.deleted.length,
266
+ unchanged: diff.unchanged.length,
267
+ changePercentage,
268
+ }
269
+ }