@stacksjs/ts-cloud 0.1.3 → 0.1.5

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 (187) hide show
  1. package/README.md +98 -13
  2. package/dist/aws/acm.d.ts +129 -0
  3. package/dist/aws/application-autoscaling.d.ts +282 -0
  4. package/dist/aws/bedrock.d.ts +2292 -0
  5. package/dist/aws/client.d.ts +79 -0
  6. package/dist/aws/cloudformation.d.ts +105 -0
  7. package/dist/aws/cloudfront.d.ts +265 -0
  8. package/dist/aws/cloudwatch-logs.d.ts +48 -0
  9. package/dist/aws/comprehend.d.ts +505 -0
  10. package/dist/aws/connect.d.ts +377 -0
  11. package/dist/aws/deploy-imap.d.ts +14 -0
  12. package/dist/aws/dynamodb.d.ts +176 -0
  13. package/dist/aws/ec2.d.ts +272 -0
  14. package/dist/aws/ecr.d.ts +149 -0
  15. package/dist/aws/ecs.d.ts +162 -0
  16. package/dist/aws/elasticache.d.ts +71 -0
  17. package/dist/aws/elbv2.d.ts +248 -0
  18. package/dist/aws/email.d.ts +175 -0
  19. package/dist/aws/eventbridge.d.ts +142 -0
  20. package/dist/aws/iam.d.ts +638 -0
  21. package/dist/aws/imap-server.d.ts +119 -0
  22. package/{src/aws/index.ts → dist/aws/index.d.ts} +62 -83
  23. package/{src/aws/kendra.ts → dist/aws/kendra.d.ts} +71 -386
  24. package/dist/aws/lambda.d.ts +232 -0
  25. package/dist/aws/opensearch.d.ts +87 -0
  26. package/dist/aws/personalize.d.ts +516 -0
  27. package/dist/aws/polly.d.ts +214 -0
  28. package/dist/aws/rds.d.ts +240 -0
  29. package/dist/aws/rekognition.d.ts +543 -0
  30. package/dist/aws/route53-domains.d.ts +113 -0
  31. package/dist/aws/route53.d.ts +215 -0
  32. package/dist/aws/s3.d.ts +212 -0
  33. package/dist/aws/scheduler.d.ts +140 -0
  34. package/dist/aws/secrets-manager.d.ts +170 -0
  35. package/dist/aws/ses.d.ts +288 -0
  36. package/dist/aws/setup-phone.d.ts +0 -0
  37. package/dist/aws/setup-sms.d.ts +115 -0
  38. package/dist/aws/sms.d.ts +304 -0
  39. package/dist/aws/smtp-server.d.ts +61 -0
  40. package/dist/aws/sns.d.ts +117 -0
  41. package/dist/aws/sqs.d.ts +65 -0
  42. package/dist/aws/ssm.d.ts +179 -0
  43. package/dist/aws/sts.d.ts +15 -0
  44. package/dist/aws/support.d.ts +104 -0
  45. package/dist/aws/test-imap.d.ts +0 -0
  46. package/dist/aws/textract.d.ts +403 -0
  47. package/dist/aws/transcribe.d.ts +60 -0
  48. package/dist/aws/translate.d.ts +358 -0
  49. package/dist/aws/voice.d.ts +219 -0
  50. package/dist/bin/cli.js +1724 -0
  51. package/dist/config.d.ts +7 -0
  52. package/dist/deploy/index.d.ts +2 -0
  53. package/dist/deploy/static-site-external-dns.d.ts +51 -0
  54. package/dist/deploy/static-site.d.ts +71 -0
  55. package/dist/dns/cloudflare.d.ts +52 -0
  56. package/dist/dns/godaddy.d.ts +38 -0
  57. package/dist/dns/index.d.ts +45 -0
  58. package/dist/dns/porkbun.d.ts +18 -0
  59. package/dist/dns/route53-adapter.d.ts +38 -0
  60. package/{src/dns/types.ts → dist/dns/types.d.ts} +26 -63
  61. package/dist/dns/validator.d.ts +78 -0
  62. package/dist/generators/index.d.ts +1 -0
  63. package/dist/generators/infrastructure.d.ts +30 -0
  64. package/{src/index.ts → dist/index.d.ts} +70 -93
  65. package/dist/index.js +7881 -0
  66. package/dist/push/apns.d.ts +60 -0
  67. package/dist/push/fcm.d.ts +117 -0
  68. package/dist/push/index.d.ts +14 -0
  69. package/dist/security/pre-deploy-scanner.d.ts +69 -0
  70. package/dist/ssl/acme-client.d.ts +67 -0
  71. package/dist/ssl/index.d.ts +2 -0
  72. package/dist/ssl/letsencrypt.d.ts +48 -0
  73. package/dist/types.d.ts +1 -0
  74. package/dist/utils/cli.d.ts +123 -0
  75. package/dist/validation/index.d.ts +1 -0
  76. package/dist/validation/template.d.ts +23 -0
  77. package/package.json +8 -8
  78. package/bin/cli.ts +0 -133
  79. package/bin/commands/analytics.ts +0 -328
  80. package/bin/commands/api.ts +0 -379
  81. package/bin/commands/assets.ts +0 -221
  82. package/bin/commands/audit.ts +0 -501
  83. package/bin/commands/backup.ts +0 -682
  84. package/bin/commands/cache.ts +0 -294
  85. package/bin/commands/cdn.ts +0 -281
  86. package/bin/commands/config.ts +0 -202
  87. package/bin/commands/container.ts +0 -105
  88. package/bin/commands/cost.ts +0 -208
  89. package/bin/commands/database.ts +0 -401
  90. package/bin/commands/deploy.ts +0 -674
  91. package/bin/commands/domain.ts +0 -397
  92. package/bin/commands/email.ts +0 -423
  93. package/bin/commands/environment.ts +0 -285
  94. package/bin/commands/events.ts +0 -424
  95. package/bin/commands/firewall.ts +0 -145
  96. package/bin/commands/function.ts +0 -116
  97. package/bin/commands/generate.ts +0 -280
  98. package/bin/commands/git.ts +0 -139
  99. package/bin/commands/iam.ts +0 -464
  100. package/bin/commands/index.ts +0 -48
  101. package/bin/commands/init.ts +0 -120
  102. package/bin/commands/logs.ts +0 -148
  103. package/bin/commands/network.ts +0 -579
  104. package/bin/commands/notify.ts +0 -489
  105. package/bin/commands/queue.ts +0 -407
  106. package/bin/commands/scheduler.ts +0 -370
  107. package/bin/commands/secrets.ts +0 -54
  108. package/bin/commands/server.ts +0 -629
  109. package/bin/commands/shared.ts +0 -97
  110. package/bin/commands/ssl.ts +0 -138
  111. package/bin/commands/stack.ts +0 -325
  112. package/bin/commands/status.ts +0 -385
  113. package/bin/commands/storage.ts +0 -450
  114. package/bin/commands/team.ts +0 -96
  115. package/bin/commands/tunnel.ts +0 -489
  116. package/bin/commands/utils.ts +0 -202
  117. package/build.ts +0 -15
  118. package/cloud +0 -2
  119. package/src/aws/acm.ts +0 -768
  120. package/src/aws/application-autoscaling.ts +0 -845
  121. package/src/aws/bedrock.ts +0 -4074
  122. package/src/aws/client.ts +0 -878
  123. package/src/aws/cloudformation.ts +0 -896
  124. package/src/aws/cloudfront.ts +0 -1531
  125. package/src/aws/cloudwatch-logs.ts +0 -154
  126. package/src/aws/comprehend.ts +0 -839
  127. package/src/aws/connect.ts +0 -1056
  128. package/src/aws/deploy-imap.ts +0 -384
  129. package/src/aws/dynamodb.ts +0 -340
  130. package/src/aws/ec2.ts +0 -1385
  131. package/src/aws/ecr.ts +0 -621
  132. package/src/aws/ecs.ts +0 -615
  133. package/src/aws/elasticache.ts +0 -301
  134. package/src/aws/elbv2.ts +0 -942
  135. package/src/aws/email.ts +0 -928
  136. package/src/aws/eventbridge.ts +0 -248
  137. package/src/aws/iam.ts +0 -1689
  138. package/src/aws/imap-server.ts +0 -2100
  139. package/src/aws/lambda.ts +0 -786
  140. package/src/aws/opensearch.ts +0 -158
  141. package/src/aws/personalize.ts +0 -977
  142. package/src/aws/polly.ts +0 -559
  143. package/src/aws/rds.ts +0 -888
  144. package/src/aws/rekognition.ts +0 -846
  145. package/src/aws/route53-domains.ts +0 -359
  146. package/src/aws/route53.ts +0 -1046
  147. package/src/aws/s3.ts +0 -2318
  148. package/src/aws/scheduler.ts +0 -571
  149. package/src/aws/secrets-manager.ts +0 -769
  150. package/src/aws/ses.ts +0 -1081
  151. package/src/aws/setup-phone.ts +0 -104
  152. package/src/aws/setup-sms.ts +0 -580
  153. package/src/aws/sms.ts +0 -1735
  154. package/src/aws/smtp-server.ts +0 -531
  155. package/src/aws/sns.ts +0 -758
  156. package/src/aws/sqs.ts +0 -382
  157. package/src/aws/ssm.ts +0 -807
  158. package/src/aws/sts.ts +0 -92
  159. package/src/aws/support.ts +0 -391
  160. package/src/aws/test-imap.ts +0 -86
  161. package/src/aws/textract.ts +0 -780
  162. package/src/aws/transcribe.ts +0 -108
  163. package/src/aws/translate.ts +0 -641
  164. package/src/aws/voice.ts +0 -1379
  165. package/src/config.ts +0 -35
  166. package/src/deploy/index.ts +0 -7
  167. package/src/deploy/static-site-external-dns.ts +0 -906
  168. package/src/deploy/static-site.ts +0 -1125
  169. package/src/dns/godaddy.ts +0 -412
  170. package/src/dns/index.ts +0 -183
  171. package/src/dns/porkbun.ts +0 -362
  172. package/src/dns/route53-adapter.ts +0 -414
  173. package/src/dns/validator.ts +0 -369
  174. package/src/generators/index.ts +0 -5
  175. package/src/generators/infrastructure.ts +0 -1660
  176. package/src/push/apns.ts +0 -452
  177. package/src/push/fcm.ts +0 -506
  178. package/src/push/index.ts +0 -58
  179. package/src/ssl/acme-client.ts +0 -478
  180. package/src/ssl/index.ts +0 -7
  181. package/src/ssl/letsencrypt.ts +0 -747
  182. package/src/types.ts +0 -2
  183. package/src/utils/cli.ts +0 -398
  184. package/src/validation/index.ts +0 -5
  185. package/src/validation/template.ts +0 -405
  186. package/test/index.test.ts +0 -128
  187. package/tsconfig.json +0 -18
package/src/aws/acm.ts DELETED
@@ -1,768 +0,0 @@
1
- /**
2
- * ACM (AWS Certificate Manager) Client
3
- * For requesting and managing SSL/TLS certificates
4
- */
5
-
6
- import { AWSClient } from './client'
7
-
8
- export interface CertificateDetail {
9
- CertificateArn: string
10
- DomainName: string
11
- SubjectAlternativeNames?: string[]
12
- Status: 'PENDING_VALIDATION' | 'ISSUED' | 'INACTIVE' | 'EXPIRED' | 'VALIDATION_TIMED_OUT' | 'REVOKED' | 'FAILED'
13
- Type?: 'IMPORTED' | 'AMAZON_ISSUED' | 'PRIVATE'
14
- DomainValidationOptions?: {
15
- DomainName: string
16
- ValidationDomain?: string
17
- ValidationStatus?: 'PENDING_VALIDATION' | 'SUCCESS' | 'FAILED'
18
- ResourceRecord?: {
19
- Name: string
20
- Type: string
21
- Value: string
22
- }
23
- ValidationMethod?: 'EMAIL' | 'DNS'
24
- }[]
25
- CreatedAt?: string
26
- IssuedAt?: string
27
- NotBefore?: string
28
- NotAfter?: string
29
- }
30
-
31
- export class ACMClient {
32
- private client: AWSClient
33
- private region: string
34
-
35
- constructor(region: string = 'us-east-1') {
36
- this.client = new AWSClient()
37
- this.region = region
38
- }
39
-
40
- /**
41
- * Request a new certificate
42
- */
43
- async requestCertificate(params: {
44
- DomainName: string
45
- SubjectAlternativeNames?: string[]
46
- ValidationMethod?: 'EMAIL' | 'DNS'
47
- }): Promise<{
48
- CertificateArn: string
49
- }> {
50
- const requestBody: Record<string, any> = {
51
- DomainName: params.DomainName,
52
- ValidationMethod: params.ValidationMethod || 'DNS',
53
- }
54
-
55
- if (params.SubjectAlternativeNames) {
56
- requestBody.SubjectAlternativeNames = params.SubjectAlternativeNames
57
- }
58
-
59
- const result = await this.client.request({
60
- service: 'acm',
61
- region: this.region,
62
- method: 'POST',
63
- path: '/',
64
- headers: {
65
- 'content-type': 'application/x-amz-json-1.1',
66
- 'x-amz-target': 'CertificateManager.RequestCertificate',
67
- },
68
- body: JSON.stringify(requestBody),
69
- })
70
-
71
- return {
72
- CertificateArn: result.CertificateArn || '',
73
- }
74
- }
75
-
76
- /**
77
- * Describe a certificate to get its details and validation options
78
- */
79
- async describeCertificate(params: {
80
- CertificateArn: string
81
- }): Promise<CertificateDetail> {
82
- const result = await this.client.request({
83
- service: 'acm',
84
- region: this.region,
85
- method: 'POST',
86
- path: '/',
87
- headers: {
88
- 'content-type': 'application/x-amz-json-1.1',
89
- 'x-amz-target': 'CertificateManager.DescribeCertificate',
90
- },
91
- body: JSON.stringify({
92
- CertificateArn: params.CertificateArn,
93
- }),
94
- })
95
-
96
- const cert = result.Certificate || {}
97
- return {
98
- CertificateArn: cert.CertificateArn || '',
99
- DomainName: cert.DomainName || '',
100
- SubjectAlternativeNames: cert.SubjectAlternativeNames,
101
- Status: cert.Status || 'PENDING_VALIDATION',
102
- Type: cert.Type,
103
- DomainValidationOptions: cert.DomainValidationOptions?.map((opt: any) => ({
104
- DomainName: opt.DomainName,
105
- ValidationDomain: opt.ValidationDomain,
106
- ValidationStatus: opt.ValidationStatus,
107
- ResourceRecord: opt.ResourceRecord ? {
108
- Name: opt.ResourceRecord.Name,
109
- Type: opt.ResourceRecord.Type,
110
- Value: opt.ResourceRecord.Value,
111
- } : undefined,
112
- ValidationMethod: opt.ValidationMethod,
113
- })),
114
- CreatedAt: cert.CreatedAt,
115
- IssuedAt: cert.IssuedAt,
116
- NotBefore: cert.NotBefore,
117
- NotAfter: cert.NotAfter,
118
- }
119
- }
120
-
121
- /**
122
- * List certificates
123
- */
124
- async listCertificates(params?: {
125
- CertificateStatuses?: ('PENDING_VALIDATION' | 'ISSUED' | 'INACTIVE' | 'EXPIRED' | 'VALIDATION_TIMED_OUT' | 'REVOKED' | 'FAILED')[]
126
- MaxItems?: number
127
- NextToken?: string
128
- }): Promise<{
129
- CertificateSummaryList: { CertificateArn: string, DomainName: string }[]
130
- NextToken?: string
131
- }> {
132
- const requestBody: Record<string, any> = {}
133
-
134
- if (params?.CertificateStatuses) {
135
- requestBody.CertificateStatuses = params.CertificateStatuses
136
- }
137
- if (params?.MaxItems) {
138
- requestBody.MaxItems = params.MaxItems
139
- }
140
- if (params?.NextToken) {
141
- requestBody.NextToken = params.NextToken
142
- }
143
-
144
- const result = await this.client.request({
145
- service: 'acm',
146
- region: this.region,
147
- method: 'POST',
148
- path: '/',
149
- headers: {
150
- 'content-type': 'application/x-amz-json-1.1',
151
- 'x-amz-target': 'CertificateManager.ListCertificates',
152
- },
153
- body: JSON.stringify(requestBody),
154
- })
155
-
156
- return {
157
- CertificateSummaryList: (result.CertificateSummaryList || []).map((cert: any) => ({
158
- CertificateArn: cert.CertificateArn,
159
- DomainName: cert.DomainName,
160
- })),
161
- NextToken: result.NextToken,
162
- }
163
- }
164
-
165
- /**
166
- * Delete a certificate
167
- */
168
- async deleteCertificate(params: {
169
- CertificateArn: string
170
- }): Promise<void> {
171
- await this.client.request({
172
- service: 'acm',
173
- region: this.region,
174
- method: 'POST',
175
- path: '/',
176
- headers: {
177
- 'content-type': 'application/x-amz-json-1.1',
178
- 'x-amz-target': 'CertificateManager.DeleteCertificate',
179
- },
180
- body: JSON.stringify({
181
- CertificateArn: params.CertificateArn,
182
- }),
183
- })
184
- }
185
-
186
- /**
187
- * Get certificate tags
188
- */
189
- async listTagsForCertificate(params: {
190
- CertificateArn: string
191
- }): Promise<{ Tags: Array<{ Key: string, Value?: string }> }> {
192
- const result = await this.client.request({
193
- service: 'acm',
194
- region: this.region,
195
- method: 'POST',
196
- path: '/',
197
- headers: {
198
- 'content-type': 'application/x-amz-json-1.1',
199
- 'x-amz-target': 'CertificateManager.ListTagsForCertificate',
200
- },
201
- body: JSON.stringify({
202
- CertificateArn: params.CertificateArn,
203
- }),
204
- })
205
-
206
- return {
207
- Tags: result.Tags || [],
208
- }
209
- }
210
-
211
- /**
212
- * Add tags to a certificate
213
- */
214
- async addTagsToCertificate(params: {
215
- CertificateArn: string
216
- Tags: Array<{ Key: string, Value?: string }>
217
- }): Promise<void> {
218
- await this.client.request({
219
- service: 'acm',
220
- region: this.region,
221
- method: 'POST',
222
- path: '/',
223
- headers: {
224
- 'content-type': 'application/x-amz-json-1.1',
225
- 'x-amz-target': 'CertificateManager.AddTagsToCertificate',
226
- },
227
- body: JSON.stringify({
228
- CertificateArn: params.CertificateArn,
229
- Tags: params.Tags,
230
- }),
231
- })
232
- }
233
-
234
- /**
235
- * Resend validation email
236
- */
237
- async resendValidationEmail(params: {
238
- CertificateArn: string
239
- Domain: string
240
- ValidationDomain: string
241
- }): Promise<void> {
242
- await this.client.request({
243
- service: 'acm',
244
- region: this.region,
245
- method: 'POST',
246
- path: '/',
247
- headers: {
248
- 'content-type': 'application/x-amz-json-1.1',
249
- 'x-amz-target': 'CertificateManager.ResendValidationEmail',
250
- },
251
- body: JSON.stringify({
252
- CertificateArn: params.CertificateArn,
253
- Domain: params.Domain,
254
- ValidationDomain: params.ValidationDomain,
255
- }),
256
- })
257
- }
258
-
259
- // Helper methods
260
-
261
- /**
262
- * Find certificate by domain name
263
- */
264
- async findCertificateByDomain(domainName: string): Promise<CertificateDetail | null> {
265
- // List all issued certificates
266
- const result = await this.listCertificates({
267
- CertificateStatuses: ['ISSUED'],
268
- })
269
-
270
- // Find certificate matching domain
271
- const summary = result.CertificateSummaryList.find(c =>
272
- c.DomainName === domainName ||
273
- c.DomainName === `*.${domainName.split('.').slice(1).join('.')}`,
274
- )
275
-
276
- if (!summary) {
277
- return null
278
- }
279
-
280
- // Get full details
281
- return this.describeCertificate({ CertificateArn: summary.CertificateArn })
282
- }
283
-
284
- /**
285
- * Wait for certificate to be issued
286
- */
287
- async waitForCertificateValidation(
288
- certificateArn: string,
289
- maxAttempts = 60,
290
- delayMs = 30000,
291
- ): Promise<CertificateDetail | null> {
292
- for (let i = 0; i < maxAttempts; i++) {
293
- const cert = await this.describeCertificate({ CertificateArn: certificateArn })
294
-
295
- if (cert.Status === 'ISSUED') {
296
- return cert
297
- }
298
-
299
- if (cert.Status === 'FAILED' || cert.Status === 'VALIDATION_TIMED_OUT') {
300
- return null
301
- }
302
-
303
- await new Promise(resolve => setTimeout(resolve, delayMs))
304
- }
305
-
306
- return null
307
- }
308
-
309
- /**
310
- * Get DNS validation records for a certificate
311
- */
312
- async getDnsValidationRecords(certificateArn: string): Promise<Array<{
313
- domainName: string
314
- recordName: string
315
- recordType: string
316
- recordValue: string
317
- }>> {
318
- const cert = await this.describeCertificate({ CertificateArn: certificateArn })
319
-
320
- if (!cert.DomainValidationOptions) {
321
- return []
322
- }
323
-
324
- return cert.DomainValidationOptions
325
- .filter(opt => opt.ResourceRecord && opt.ValidationMethod === 'DNS')
326
- .map(opt => ({
327
- domainName: opt.DomainName,
328
- recordName: opt.ResourceRecord!.Name,
329
- recordType: opt.ResourceRecord!.Type,
330
- recordValue: opt.ResourceRecord!.Value,
331
- }))
332
- }
333
-
334
- /**
335
- * Request certificate for a domain with common SANs
336
- * Automatically includes www and wildcard
337
- */
338
- async requestCertificateWithSans(params: {
339
- DomainName: string
340
- IncludeWww?: boolean
341
- IncludeWildcard?: boolean
342
- AdditionalSans?: string[]
343
- }): Promise<{ CertificateArn: string }> {
344
- const sans = new Set<string>()
345
-
346
- // Always include the main domain
347
- sans.add(params.DomainName)
348
-
349
- // Add www subdomain
350
- if (params.IncludeWww !== false) {
351
- sans.add(`www.${params.DomainName}`)
352
- }
353
-
354
- // Add wildcard
355
- if (params.IncludeWildcard) {
356
- sans.add(`*.${params.DomainName}`)
357
- }
358
-
359
- // Add additional SANs
360
- if (params.AdditionalSans) {
361
- for (const san of params.AdditionalSans) {
362
- sans.add(san)
363
- }
364
- }
365
-
366
- return this.requestCertificate({
367
- DomainName: params.DomainName,
368
- SubjectAlternativeNames: Array.from(sans),
369
- ValidationMethod: 'DNS',
370
- })
371
- }
372
-
373
- /**
374
- * Check if certificate is valid for a given domain
375
- */
376
- async isCertificateValidForDomain(
377
- certificateArn: string,
378
- domainName: string,
379
- ): Promise<boolean> {
380
- const cert = await this.describeCertificate({ CertificateArn: certificateArn })
381
-
382
- if (cert.Status !== 'ISSUED') {
383
- return false
384
- }
385
-
386
- // Check if domain matches
387
- if (cert.DomainName === domainName) {
388
- return true
389
- }
390
-
391
- // Check wildcard match
392
- if (cert.DomainName?.startsWith('*.')) {
393
- const baseDomain = cert.DomainName.slice(2)
394
- const domainParts = domainName.split('.')
395
- const baseParts = baseDomain.split('.')
396
-
397
- if (domainParts.slice(-baseParts.length).join('.') === baseDomain) {
398
- return true
399
- }
400
- }
401
-
402
- // Check SANs
403
- if (cert.SubjectAlternativeNames) {
404
- for (const san of cert.SubjectAlternativeNames) {
405
- if (san === domainName) {
406
- return true
407
- }
408
-
409
- if (san.startsWith('*.')) {
410
- const baseDomain = san.slice(2)
411
- const domainParts = domainName.split('.')
412
- const baseParts = baseDomain.split('.')
413
-
414
- if (domainParts.slice(-baseParts.length).join('.') === baseDomain) {
415
- return true
416
- }
417
- }
418
- }
419
- }
420
-
421
- return false
422
- }
423
- }
424
-
425
- import { Route53Client } from './route53'
426
- import type { DnsProvider, DnsProviderConfig } from '../dns/types'
427
- import { createDnsProvider } from '../dns'
428
-
429
- /**
430
- * Helper class for ACM DNS validation with Route53 integration
431
- * @deprecated Use UnifiedDnsValidator from 'ts-cloud/dns' for multi-provider support (Route53, Porkbun, GoDaddy)
432
- */
433
- export class ACMDnsValidator {
434
- private acm: ACMClient
435
- private route53: Route53Client
436
- private dnsProvider?: DnsProvider
437
-
438
- /**
439
- * Create ACM DNS validator
440
- * @param region - AWS region for ACM (default: us-east-1)
441
- * @param dnsProviderConfig - Optional external DNS provider config (Porkbun, GoDaddy)
442
- */
443
- constructor(region: string = 'us-east-1', dnsProviderConfig?: DnsProviderConfig) {
444
- this.acm = new ACMClient(region)
445
- this.route53 = new Route53Client()
446
-
447
- // Initialize external DNS provider if config provided
448
- if (dnsProviderConfig && dnsProviderConfig.provider !== 'route53') {
449
- this.dnsProvider = createDnsProvider(dnsProviderConfig)
450
- }
451
- }
452
-
453
- /**
454
- * Request certificate and automatically create DNS validation records
455
- * @param params.domainName - Primary domain name for the certificate
456
- * @param params.hostedZoneId - Route53 hosted zone ID (required if no external DNS provider configured)
457
- * @param params.subjectAlternativeNames - Additional domain names (SANs)
458
- * @param params.waitForValidation - Wait for certificate to be issued
459
- * @param params.maxWaitMinutes - Maximum wait time in minutes
460
- */
461
- async requestAndValidate(params: {
462
- domainName: string
463
- hostedZoneId?: string
464
- subjectAlternativeNames?: string[]
465
- waitForValidation?: boolean
466
- maxWaitMinutes?: number
467
- }): Promise<{
468
- certificateArn: string
469
- validationRecords: Array<{
470
- domainName: string
471
- recordName: string
472
- recordValue: string
473
- }>
474
- }> {
475
- const {
476
- domainName,
477
- hostedZoneId,
478
- subjectAlternativeNames = [],
479
- waitForValidation = false,
480
- maxWaitMinutes = 30,
481
- } = params
482
-
483
- // Validate that we have a DNS provider
484
- if (!this.dnsProvider && !hostedZoneId) {
485
- throw new Error('Either hostedZoneId or external DNS provider configuration is required')
486
- }
487
-
488
- // Request certificate
489
- const { CertificateArn } = await this.acm.requestCertificate({
490
- DomainName: domainName,
491
- SubjectAlternativeNames: subjectAlternativeNames.length > 0
492
- ? [domainName, ...subjectAlternativeNames]
493
- : undefined,
494
- ValidationMethod: 'DNS',
495
- })
496
-
497
- // Wait for DNS validation options to be available
498
- await this.waitForValidationOptions(CertificateArn)
499
-
500
- // Get validation records
501
- const validationRecords = await this.acm.getDnsValidationRecords(CertificateArn)
502
-
503
- // Create DNS records using the appropriate provider
504
- if (this.dnsProvider) {
505
- // Use external DNS provider (Porkbun, GoDaddy, etc.)
506
- for (const record of validationRecords) {
507
- const result = await this.dnsProvider.upsertRecord(domainName, {
508
- name: record.recordName,
509
- type: record.recordType as any,
510
- content: record.recordValue,
511
- ttl: 300,
512
- })
513
-
514
- if (!result.success) {
515
- console.warn(`Failed to create validation record for ${record.domainName}: ${result.message}`)
516
- }
517
- }
518
- }
519
- else if (hostedZoneId) {
520
- // Use Route53
521
- for (const record of validationRecords) {
522
- await this.route53.changeResourceRecordSets({
523
- HostedZoneId: hostedZoneId,
524
- ChangeBatch: {
525
- Comment: `ACM DNS validation for ${record.domainName}`,
526
- Changes: [{
527
- Action: 'UPSERT',
528
- ResourceRecordSet: {
529
- Name: record.recordName,
530
- Type: record.recordType as any,
531
- TTL: 300,
532
- ResourceRecords: [{ Value: record.recordValue }],
533
- },
534
- }],
535
- },
536
- })
537
- }
538
- }
539
-
540
- // Wait for validation if requested
541
- if (waitForValidation) {
542
- const cert = await this.acm.waitForCertificateValidation(
543
- CertificateArn,
544
- maxWaitMinutes * 2, // attempts (every 30 seconds)
545
- 30000, // 30 seconds between checks
546
- )
547
-
548
- if (!cert) {
549
- throw new Error(`Certificate validation timed out after ${maxWaitMinutes} minutes`)
550
- }
551
- }
552
-
553
- return {
554
- certificateArn: CertificateArn,
555
- validationRecords,
556
- }
557
- }
558
-
559
- /**
560
- * Wait for validation options to become available
561
- */
562
- private async waitForValidationOptions(certificateArn: string, maxAttempts = 30): Promise<void> {
563
- for (let i = 0; i < maxAttempts; i++) {
564
- const cert = await this.acm.describeCertificate({ CertificateArn: certificateArn })
565
-
566
- if (cert.DomainValidationOptions &&
567
- cert.DomainValidationOptions.length > 0 &&
568
- cert.DomainValidationOptions[0].ResourceRecord) {
569
- return
570
- }
571
-
572
- await new Promise(resolve => setTimeout(resolve, 2000))
573
- }
574
-
575
- throw new Error('Timeout waiting for DNS validation options')
576
- }
577
-
578
- /**
579
- * Create validation records for an existing certificate
580
- * Uses external DNS provider if configured, otherwise Route53
581
- */
582
- async createValidationRecords(params: {
583
- certificateArn: string
584
- hostedZoneId?: string
585
- domain?: string
586
- }): Promise<Array<{
587
- domainName: string
588
- recordName: string
589
- recordValue: string
590
- changeId?: string
591
- }>> {
592
- const { certificateArn, hostedZoneId, domain } = params
593
-
594
- // Validate DNS provider availability
595
- if (!this.dnsProvider && !hostedZoneId) {
596
- throw new Error('Either hostedZoneId or external DNS provider configuration is required')
597
- }
598
-
599
- // Get validation records
600
- const validationRecords = await this.acm.getDnsValidationRecords(certificateArn)
601
- const results: Array<{
602
- domainName: string
603
- recordName: string
604
- recordValue: string
605
- changeId?: string
606
- }> = []
607
-
608
- if (this.dnsProvider) {
609
- // Use external DNS provider
610
- const targetDomain = domain || validationRecords[0]?.domainName
611
- for (const record of validationRecords) {
612
- const result = await this.dnsProvider.upsertRecord(targetDomain, {
613
- name: record.recordName,
614
- type: record.recordType as any,
615
- content: record.recordValue,
616
- ttl: 300,
617
- })
618
-
619
- results.push({
620
- ...record,
621
- changeId: result.success ? result.id : undefined,
622
- })
623
- }
624
- }
625
- else if (hostedZoneId) {
626
- // Use Route53
627
- for (const record of validationRecords) {
628
- const result = await this.route53.changeResourceRecordSets({
629
- HostedZoneId: hostedZoneId,
630
- ChangeBatch: {
631
- Comment: `ACM DNS validation for ${record.domainName}`,
632
- Changes: [{
633
- Action: 'UPSERT',
634
- ResourceRecordSet: {
635
- Name: record.recordName,
636
- Type: record.recordType as any,
637
- TTL: 300,
638
- ResourceRecords: [{ Value: record.recordValue }],
639
- },
640
- }],
641
- },
642
- })
643
-
644
- results.push({
645
- ...record,
646
- changeId: result.ChangeInfo?.Id,
647
- })
648
- }
649
- }
650
-
651
- return results
652
- }
653
-
654
- /**
655
- * Delete validation records after certificate is issued
656
- * Uses external DNS provider if configured, otherwise Route53
657
- */
658
- async deleteValidationRecords(params: {
659
- certificateArn: string
660
- hostedZoneId?: string
661
- domain?: string
662
- }): Promise<void> {
663
- const { certificateArn, hostedZoneId, domain } = params
664
-
665
- // Get validation records
666
- const validationRecords = await this.acm.getDnsValidationRecords(certificateArn)
667
-
668
- if (this.dnsProvider) {
669
- // Use external DNS provider
670
- const targetDomain = domain || validationRecords[0]?.domainName
671
- for (const record of validationRecords) {
672
- try {
673
- await this.dnsProvider.deleteRecord(targetDomain, {
674
- name: record.recordName,
675
- type: record.recordType as any,
676
- content: record.recordValue,
677
- })
678
- }
679
- catch {
680
- // Ignore errors if record doesn't exist
681
- }
682
- }
683
- }
684
- else if (hostedZoneId) {
685
- // Use Route53
686
- for (const record of validationRecords) {
687
- try {
688
- await this.route53.changeResourceRecordSets({
689
- HostedZoneId: hostedZoneId,
690
- ChangeBatch: {
691
- Comment: `Cleanup ACM DNS validation for ${record.domainName}`,
692
- Changes: [{
693
- Action: 'DELETE',
694
- ResourceRecordSet: {
695
- Name: record.recordName,
696
- Type: record.recordType as any,
697
- TTL: 300,
698
- ResourceRecords: [{ Value: record.recordValue }],
699
- },
700
- }],
701
- },
702
- })
703
- }
704
- catch {
705
- // Ignore errors if record doesn't exist
706
- }
707
- }
708
- }
709
- }
710
-
711
- /**
712
- * Find or create a certificate for a domain
713
- * Uses external DNS provider if configured, otherwise Route53
714
- */
715
- async findOrCreateCertificate(params: {
716
- domainName: string
717
- hostedZoneId?: string
718
- subjectAlternativeNames?: string[]
719
- waitForValidation?: boolean
720
- }): Promise<{
721
- certificateArn: string
722
- isNew: boolean
723
- }> {
724
- const { domainName, hostedZoneId, subjectAlternativeNames, waitForValidation = true } = params
725
-
726
- // Validate DNS provider availability
727
- if (!this.dnsProvider && !hostedZoneId) {
728
- throw new Error('Either hostedZoneId or external DNS provider configuration is required')
729
- }
730
-
731
- // Try to find existing certificate
732
- const existing = await this.acm.findCertificateByDomain(domainName)
733
-
734
- if (existing && existing.Status === 'ISSUED') {
735
- return {
736
- certificateArn: existing.CertificateArn,
737
- isNew: false,
738
- }
739
- }
740
-
741
- // Request new certificate
742
- const { certificateArn } = await this.requestAndValidate({
743
- domainName,
744
- hostedZoneId,
745
- subjectAlternativeNames,
746
- waitForValidation,
747
- })
748
-
749
- return {
750
- certificateArn,
751
- isNew: true,
752
- }
753
- }
754
-
755
- /**
756
- * Check if using external DNS provider
757
- */
758
- hasExternalDnsProvider(): boolean {
759
- return this.dnsProvider !== undefined
760
- }
761
-
762
- /**
763
- * Get the DNS provider name if using external provider
764
- */
765
- getDnsProviderName(): string {
766
- return this.dnsProvider?.name || 'route53'
767
- }
768
- }