@stacksjs/ts-cloud 0.1.9 → 0.1.14

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 (150) hide show
  1. package/README.md +39 -377
  2. package/dist/bin/cli.js +1047 -424
  3. package/dist/index.d.ts +36 -3
  4. package/dist/index.js +76430 -7096
  5. package/package.json +7 -8
  6. package/dist/aws/acm.d.ts +0 -129
  7. package/dist/aws/application-autoscaling.d.ts +0 -282
  8. package/dist/aws/bedrock.d.ts +0 -2292
  9. package/dist/aws/client.d.ts +0 -79
  10. package/dist/aws/cloudformation.d.ts +0 -105
  11. package/dist/aws/cloudfront.d.ts +0 -265
  12. package/dist/aws/cloudwatch-logs.d.ts +0 -48
  13. package/dist/aws/comprehend.d.ts +0 -505
  14. package/dist/aws/connect.d.ts +0 -377
  15. package/dist/aws/deploy-imap.d.ts +0 -14
  16. package/dist/aws/dynamodb.d.ts +0 -176
  17. package/dist/aws/ec2.d.ts +0 -272
  18. package/dist/aws/ecr.d.ts +0 -149
  19. package/dist/aws/ecs.d.ts +0 -162
  20. package/dist/aws/elasticache.d.ts +0 -71
  21. package/dist/aws/elbv2.d.ts +0 -248
  22. package/dist/aws/email.d.ts +0 -175
  23. package/dist/aws/eventbridge.d.ts +0 -142
  24. package/dist/aws/iam.d.ts +0 -638
  25. package/dist/aws/imap-server.d.ts +0 -119
  26. package/dist/aws/index.d.ts +0 -192
  27. package/dist/aws/kendra.d.ts +0 -782
  28. package/dist/aws/lambda.d.ts +0 -232
  29. package/dist/aws/opensearch.d.ts +0 -87
  30. package/dist/aws/personalize.d.ts +0 -516
  31. package/dist/aws/polly.d.ts +0 -214
  32. package/dist/aws/rds.d.ts +0 -240
  33. package/dist/aws/rekognition.d.ts +0 -543
  34. package/dist/aws/route53-domains.d.ts +0 -113
  35. package/dist/aws/route53.d.ts +0 -215
  36. package/dist/aws/s3.d.ts +0 -212
  37. package/dist/aws/scheduler.d.ts +0 -140
  38. package/dist/aws/secrets-manager.d.ts +0 -170
  39. package/dist/aws/ses.d.ts +0 -288
  40. package/dist/aws/setup-phone.d.ts +0 -0
  41. package/dist/aws/setup-sms.d.ts +0 -115
  42. package/dist/aws/sms.d.ts +0 -304
  43. package/dist/aws/smtp-server.d.ts +0 -61
  44. package/dist/aws/sns.d.ts +0 -117
  45. package/dist/aws/sqs.d.ts +0 -65
  46. package/dist/aws/ssm.d.ts +0 -179
  47. package/dist/aws/sts.d.ts +0 -15
  48. package/dist/aws/support.d.ts +0 -104
  49. package/dist/aws/test-imap.d.ts +0 -0
  50. package/dist/aws/textract.d.ts +0 -403
  51. package/dist/aws/transcribe.d.ts +0 -60
  52. package/dist/aws/translate.d.ts +0 -358
  53. package/dist/aws/voice.d.ts +0 -219
  54. package/dist/config.d.ts +0 -7
  55. package/dist/deploy/index.d.ts +0 -2
  56. package/dist/deploy/static-site-external-dns.d.ts +0 -51
  57. package/dist/deploy/static-site.d.ts +0 -71
  58. package/dist/dns/cloudflare.d.ts +0 -52
  59. package/dist/dns/godaddy.d.ts +0 -38
  60. package/dist/dns/index.d.ts +0 -45
  61. package/dist/dns/porkbun.d.ts +0 -18
  62. package/dist/dns/route53-adapter.d.ts +0 -38
  63. package/dist/dns/types.d.ts +0 -77
  64. package/dist/dns/validator.d.ts +0 -78
  65. package/dist/generators/index.d.ts +0 -1
  66. package/dist/generators/infrastructure.d.ts +0 -30
  67. package/dist/push/apns.d.ts +0 -60
  68. package/dist/push/fcm.d.ts +0 -117
  69. package/dist/push/index.d.ts +0 -14
  70. package/dist/security/pre-deploy-scanner.d.ts +0 -69
  71. package/dist/ssl/acme-client.d.ts +0 -67
  72. package/dist/ssl/index.d.ts +0 -2
  73. package/dist/ssl/letsencrypt.d.ts +0 -48
  74. package/dist/types.d.ts +0 -1
  75. package/dist/utils/cli.d.ts +0 -123
  76. package/dist/validation/index.d.ts +0 -1
  77. package/dist/validation/template.d.ts +0 -23
  78. package/src/aws/acm.ts +0 -768
  79. package/src/aws/application-autoscaling.ts +0 -845
  80. package/src/aws/bedrock.ts +0 -4074
  81. package/src/aws/client.ts +0 -891
  82. package/src/aws/cloudformation.ts +0 -896
  83. package/src/aws/cloudfront.ts +0 -1531
  84. package/src/aws/cloudwatch-logs.ts +0 -154
  85. package/src/aws/comprehend.ts +0 -839
  86. package/src/aws/connect.ts +0 -1056
  87. package/src/aws/deploy-imap.ts +0 -384
  88. package/src/aws/dynamodb.ts +0 -340
  89. package/src/aws/ec2.ts +0 -1385
  90. package/src/aws/ecr.ts +0 -621
  91. package/src/aws/ecs.ts +0 -615
  92. package/src/aws/elasticache.ts +0 -301
  93. package/src/aws/elbv2.ts +0 -942
  94. package/src/aws/email.ts +0 -928
  95. package/src/aws/eventbridge.ts +0 -248
  96. package/src/aws/iam.ts +0 -1689
  97. package/src/aws/imap-server.ts +0 -2100
  98. package/src/aws/index.ts +0 -213
  99. package/src/aws/kendra.ts +0 -1097
  100. package/src/aws/lambda.ts +0 -786
  101. package/src/aws/opensearch.ts +0 -158
  102. package/src/aws/personalize.ts +0 -977
  103. package/src/aws/polly.ts +0 -559
  104. package/src/aws/rds.ts +0 -888
  105. package/src/aws/rekognition.ts +0 -846
  106. package/src/aws/route53-domains.ts +0 -359
  107. package/src/aws/route53.ts +0 -1046
  108. package/src/aws/s3.ts +0 -2334
  109. package/src/aws/scheduler.ts +0 -571
  110. package/src/aws/secrets-manager.ts +0 -769
  111. package/src/aws/ses.ts +0 -1081
  112. package/src/aws/setup-phone.ts +0 -104
  113. package/src/aws/setup-sms.ts +0 -580
  114. package/src/aws/sms.ts +0 -1735
  115. package/src/aws/smtp-server.ts +0 -531
  116. package/src/aws/sns.ts +0 -758
  117. package/src/aws/sqs.ts +0 -382
  118. package/src/aws/ssm.ts +0 -807
  119. package/src/aws/sts.ts +0 -92
  120. package/src/aws/support.ts +0 -391
  121. package/src/aws/test-imap.ts +0 -86
  122. package/src/aws/textract.ts +0 -780
  123. package/src/aws/transcribe.ts +0 -108
  124. package/src/aws/translate.ts +0 -641
  125. package/src/aws/voice.ts +0 -1379
  126. package/src/config.ts +0 -35
  127. package/src/deploy/index.ts +0 -7
  128. package/src/deploy/static-site-external-dns.ts +0 -945
  129. package/src/deploy/static-site.ts +0 -1175
  130. package/src/dns/cloudflare.ts +0 -548
  131. package/src/dns/godaddy.ts +0 -412
  132. package/src/dns/index.ts +0 -205
  133. package/src/dns/porkbun.ts +0 -362
  134. package/src/dns/route53-adapter.ts +0 -414
  135. package/src/dns/types.ts +0 -119
  136. package/src/dns/validator.ts +0 -369
  137. package/src/generators/index.ts +0 -5
  138. package/src/generators/infrastructure.ts +0 -1660
  139. package/src/index.ts +0 -163
  140. package/src/push/apns.ts +0 -452
  141. package/src/push/fcm.ts +0 -506
  142. package/src/push/index.ts +0 -58
  143. package/src/security/pre-deploy-scanner.ts +0 -655
  144. package/src/ssl/acme-client.ts +0 -478
  145. package/src/ssl/index.ts +0 -7
  146. package/src/ssl/letsencrypt.ts +0 -747
  147. package/src/types.ts +0 -2
  148. package/src/utils/cli.ts +0 -398
  149. package/src/validation/index.ts +0 -5
  150. package/src/validation/template.ts +0 -405
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
- }