@stacksjs/ts-cloud 0.1.7 → 0.1.9

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 (77) hide show
  1. package/dist/aws/s3.d.ts +1 -1
  2. package/dist/bin/cli.js +223 -222
  3. package/dist/index.js +132 -132
  4. package/package.json +18 -16
  5. package/src/aws/acm.ts +768 -0
  6. package/src/aws/application-autoscaling.ts +845 -0
  7. package/src/aws/bedrock.ts +4074 -0
  8. package/src/aws/client.ts +891 -0
  9. package/src/aws/cloudformation.ts +896 -0
  10. package/src/aws/cloudfront.ts +1531 -0
  11. package/src/aws/cloudwatch-logs.ts +154 -0
  12. package/src/aws/comprehend.ts +839 -0
  13. package/src/aws/connect.ts +1056 -0
  14. package/src/aws/deploy-imap.ts +384 -0
  15. package/src/aws/dynamodb.ts +340 -0
  16. package/src/aws/ec2.ts +1385 -0
  17. package/src/aws/ecr.ts +621 -0
  18. package/src/aws/ecs.ts +615 -0
  19. package/src/aws/elasticache.ts +301 -0
  20. package/src/aws/elbv2.ts +942 -0
  21. package/src/aws/email.ts +928 -0
  22. package/src/aws/eventbridge.ts +248 -0
  23. package/src/aws/iam.ts +1689 -0
  24. package/src/aws/imap-server.ts +2100 -0
  25. package/src/aws/index.ts +213 -0
  26. package/src/aws/kendra.ts +1097 -0
  27. package/src/aws/lambda.ts +786 -0
  28. package/src/aws/opensearch.ts +158 -0
  29. package/src/aws/personalize.ts +977 -0
  30. package/src/aws/polly.ts +559 -0
  31. package/src/aws/rds.ts +888 -0
  32. package/src/aws/rekognition.ts +846 -0
  33. package/src/aws/route53-domains.ts +359 -0
  34. package/src/aws/route53.ts +1046 -0
  35. package/src/aws/s3.ts +2334 -0
  36. package/src/aws/scheduler.ts +571 -0
  37. package/src/aws/secrets-manager.ts +769 -0
  38. package/src/aws/ses.ts +1081 -0
  39. package/src/aws/setup-phone.ts +104 -0
  40. package/src/aws/setup-sms.ts +580 -0
  41. package/src/aws/sms.ts +1735 -0
  42. package/src/aws/smtp-server.ts +531 -0
  43. package/src/aws/sns.ts +758 -0
  44. package/src/aws/sqs.ts +382 -0
  45. package/src/aws/ssm.ts +807 -0
  46. package/src/aws/sts.ts +92 -0
  47. package/src/aws/support.ts +391 -0
  48. package/src/aws/test-imap.ts +86 -0
  49. package/src/aws/textract.ts +780 -0
  50. package/src/aws/transcribe.ts +108 -0
  51. package/src/aws/translate.ts +641 -0
  52. package/src/aws/voice.ts +1379 -0
  53. package/src/config.ts +35 -0
  54. package/src/deploy/index.ts +7 -0
  55. package/src/deploy/static-site-external-dns.ts +945 -0
  56. package/src/deploy/static-site.ts +1175 -0
  57. package/src/dns/cloudflare.ts +548 -0
  58. package/src/dns/godaddy.ts +412 -0
  59. package/src/dns/index.ts +205 -0
  60. package/src/dns/porkbun.ts +362 -0
  61. package/src/dns/route53-adapter.ts +414 -0
  62. package/src/dns/types.ts +119 -0
  63. package/src/dns/validator.ts +369 -0
  64. package/src/generators/index.ts +5 -0
  65. package/src/generators/infrastructure.ts +1660 -0
  66. package/src/index.ts +163 -0
  67. package/src/push/apns.ts +452 -0
  68. package/src/push/fcm.ts +506 -0
  69. package/src/push/index.ts +58 -0
  70. package/src/security/pre-deploy-scanner.ts +655 -0
  71. package/src/ssl/acme-client.ts +478 -0
  72. package/src/ssl/index.ts +7 -0
  73. package/src/ssl/letsencrypt.ts +747 -0
  74. package/src/types.ts +2 -0
  75. package/src/utils/cli.ts +398 -0
  76. package/src/validation/index.ts +5 -0
  77. package/src/validation/template.ts +405 -0
@@ -0,0 +1,412 @@
1
+ /**
2
+ * GoDaddy DNS Provider
3
+ * API documentation: https://developer.godaddy.com/doc/endpoint/domains
4
+ */
5
+
6
+ import type {
7
+ CreateRecordResult,
8
+ DeleteRecordResult,
9
+ DnsProvider,
10
+ DnsRecord,
11
+ DnsRecordResult,
12
+ DnsRecordType,
13
+ ListRecordsResult,
14
+ } from './types'
15
+
16
+ const GODADDY_API_URL = 'https://api.godaddy.com'
17
+ const GODADDY_OTE_API_URL = 'https://api.ote-godaddy.com' // Test environment
18
+
19
+ interface GoDaddyRecord {
20
+ type: string
21
+ name: string
22
+ data: string
23
+ ttl: number
24
+ priority?: number
25
+ }
26
+
27
+ export class GoDaddyProvider implements DnsProvider {
28
+ readonly name = 'godaddy'
29
+ private apiKey: string
30
+ private apiSecret: string
31
+ private baseUrl: string
32
+
33
+ constructor(
34
+ apiKey: string,
35
+ apiSecret: string,
36
+ environment: 'production' | 'ote' = 'production',
37
+ ) {
38
+ this.apiKey = apiKey
39
+ this.apiSecret = apiSecret
40
+ this.baseUrl = environment === 'ote' ? GODADDY_OTE_API_URL : GODADDY_API_URL
41
+ }
42
+
43
+ /**
44
+ * Make an authenticated API request to GoDaddy
45
+ */
46
+ private async request<T>(
47
+ method: string,
48
+ endpoint: string,
49
+ body?: any,
50
+ ): Promise<T> {
51
+ const url = `${this.baseUrl}${endpoint}`
52
+
53
+ const headers: Record<string, string> = {
54
+ 'Authorization': `sso-key ${this.apiKey}:${this.apiSecret}`,
55
+ 'Content-Type': 'application/json',
56
+ 'Accept': 'application/json',
57
+ }
58
+
59
+ const options: RequestInit = {
60
+ method,
61
+ headers,
62
+ }
63
+
64
+ if (body) {
65
+ options.body = JSON.stringify(body)
66
+ }
67
+
68
+ const response = await fetch(url, options)
69
+
70
+ // GoDaddy returns 204 for successful DELETE operations
71
+ if (response.status === 204) {
72
+ return {} as T
73
+ }
74
+
75
+ // For successful requests with content
76
+ if (response.ok) {
77
+ const text = await response.text()
78
+ if (text) {
79
+ return JSON.parse(text) as T
80
+ }
81
+ return {} as T
82
+ }
83
+
84
+ // Handle errors
85
+ let errorMessage = `GoDaddy API error: ${response.status} ${response.statusText}`
86
+ try {
87
+ const errorData = await response.json() as Record<string, any>
88
+ if (errorData.message) {
89
+ errorMessage = `GoDaddy API error: ${errorData.message}`
90
+ }
91
+ if (errorData.fields) {
92
+ errorMessage += ` - Fields: ${JSON.stringify(errorData.fields)}`
93
+ }
94
+ }
95
+ catch {
96
+ // Ignore JSON parse errors for error response
97
+ }
98
+
99
+ throw new Error(errorMessage)
100
+ }
101
+
102
+ /**
103
+ * Extract the subdomain from a full record name
104
+ * GoDaddy uses @ for root domain
105
+ */
106
+ private getSubdomain(recordName: string, domain: string): string {
107
+ // Remove trailing dots
108
+ const cleanName = recordName.replace(/\.$/, '')
109
+ const cleanDomain = domain.replace(/\.$/, '')
110
+
111
+ // If the record name equals the domain, return @ (root)
112
+ if (cleanName === cleanDomain) {
113
+ return '@'
114
+ }
115
+
116
+ // Remove the domain suffix to get the subdomain
117
+ if (cleanName.endsWith(`.${cleanDomain}`)) {
118
+ return cleanName.slice(0, -(cleanDomain.length + 1))
119
+ }
120
+
121
+ // If no match, return the full name as subdomain
122
+ return cleanName
123
+ }
124
+
125
+ /**
126
+ * Get the root domain from a full domain name
127
+ */
128
+ private getRootDomain(domain: string): string {
129
+ const parts = domain.replace(/\.$/, '').split('.')
130
+ if (parts.length >= 2) {
131
+ return parts.slice(-2).join('.')
132
+ }
133
+ return domain
134
+ }
135
+
136
+ /**
137
+ * Convert DnsRecord to GoDaddy record format
138
+ */
139
+ private toGoDaddyRecord(record: DnsRecord, domain: string): GoDaddyRecord {
140
+ const rootDomain = this.getRootDomain(domain)
141
+ const subdomain = this.getSubdomain(record.name, rootDomain)
142
+
143
+ const gdRecord: GoDaddyRecord = {
144
+ type: record.type,
145
+ name: subdomain,
146
+ data: record.content,
147
+ ttl: record.ttl || 600,
148
+ }
149
+
150
+ if (record.type === 'MX' && record.priority !== undefined) {
151
+ gdRecord.priority = record.priority
152
+ }
153
+
154
+ return gdRecord
155
+ }
156
+
157
+ /**
158
+ * Convert GoDaddy record to DnsRecordResult format
159
+ */
160
+ private fromGoDaddyRecord(record: GoDaddyRecord, domain: string): DnsRecordResult {
161
+ const rootDomain = this.getRootDomain(domain)
162
+ let name = record.name
163
+
164
+ // Convert @ to domain name and subdomain to full name
165
+ if (name === '@') {
166
+ name = rootDomain
167
+ }
168
+ else if (!name.endsWith(rootDomain)) {
169
+ name = `${name}.${rootDomain}`
170
+ }
171
+
172
+ return {
173
+ name,
174
+ type: record.type as DnsRecordType,
175
+ content: record.data,
176
+ ttl: record.ttl,
177
+ priority: record.priority,
178
+ }
179
+ }
180
+
181
+ async createRecord(domain: string, record: DnsRecord): Promise<CreateRecordResult> {
182
+ try {
183
+ const rootDomain = this.getRootDomain(domain)
184
+ const gdRecord = this.toGoDaddyRecord(record, domain)
185
+
186
+ // GoDaddy's PATCH endpoint adds records
187
+ await this.request(
188
+ 'PATCH',
189
+ `/v1/domains/${rootDomain}/records`,
190
+ [gdRecord],
191
+ )
192
+
193
+ return {
194
+ success: true,
195
+ message: 'Record created successfully',
196
+ }
197
+ }
198
+ catch (error) {
199
+ return {
200
+ success: false,
201
+ message: error instanceof Error ? error.message : 'Unknown error',
202
+ }
203
+ }
204
+ }
205
+
206
+ async upsertRecord(domain: string, record: DnsRecord): Promise<CreateRecordResult> {
207
+ try {
208
+ const rootDomain = this.getRootDomain(domain)
209
+ const gdRecord = this.toGoDaddyRecord(record, domain)
210
+
211
+ // GoDaddy's PUT replaces all records of a specific type/name
212
+ await this.request(
213
+ 'PUT',
214
+ `/v1/domains/${rootDomain}/records/${record.type}/${gdRecord.name}`,
215
+ [gdRecord],
216
+ )
217
+
218
+ return {
219
+ success: true,
220
+ message: 'Record upserted successfully',
221
+ }
222
+ }
223
+ catch (error) {
224
+ // If PUT fails (record doesn't exist), try PATCH
225
+ return this.createRecord(domain, record)
226
+ }
227
+ }
228
+
229
+ async deleteRecord(domain: string, record: DnsRecord): Promise<DeleteRecordResult> {
230
+ try {
231
+ const rootDomain = this.getRootDomain(domain)
232
+ const subdomain = this.getSubdomain(record.name, rootDomain)
233
+
234
+ // GoDaddy doesn't have a direct delete endpoint
235
+ // We need to get all records of this type/name and PUT back without the target
236
+
237
+ const existingRecords = await this.request<GoDaddyRecord[]>(
238
+ 'GET',
239
+ `/v1/domains/${rootDomain}/records/${record.type}/${subdomain}`,
240
+ )
241
+
242
+ // Filter out the record to delete
243
+ const remainingRecords = existingRecords.filter(
244
+ r => r.data !== record.content,
245
+ )
246
+
247
+ if (remainingRecords.length === existingRecords.length) {
248
+ // Record not found
249
+ return {
250
+ success: false,
251
+ message: 'Record not found',
252
+ }
253
+ }
254
+
255
+ if (remainingRecords.length === 0) {
256
+ // GoDaddy doesn't allow empty record sets for some types
257
+ // Use DELETE endpoint if available, otherwise PUT an empty array
258
+ try {
259
+ await this.request(
260
+ 'DELETE',
261
+ `/v1/domains/${rootDomain}/records/${record.type}/${subdomain}`,
262
+ )
263
+ }
264
+ catch {
265
+ // Some record types can't be fully deleted, that's OK
266
+ }
267
+ }
268
+ else {
269
+ // Replace with remaining records
270
+ await this.request(
271
+ 'PUT',
272
+ `/v1/domains/${rootDomain}/records/${record.type}/${subdomain}`,
273
+ remainingRecords,
274
+ )
275
+ }
276
+
277
+ return {
278
+ success: true,
279
+ message: 'Record deleted successfully',
280
+ }
281
+ }
282
+ catch (error) {
283
+ return {
284
+ success: false,
285
+ message: error instanceof Error ? error.message : 'Unknown error',
286
+ }
287
+ }
288
+ }
289
+
290
+ async listRecords(domain: string, type?: DnsRecordType): Promise<ListRecordsResult> {
291
+ try {
292
+ const rootDomain = this.getRootDomain(domain)
293
+
294
+ let endpoint = `/v1/domains/${rootDomain}/records`
295
+ if (type) {
296
+ endpoint = `/v1/domains/${rootDomain}/records/${type}`
297
+ }
298
+
299
+ const records = await this.request<GoDaddyRecord[]>('GET', endpoint)
300
+
301
+ return {
302
+ success: true,
303
+ records: records.map(r => this.fromGoDaddyRecord(r, domain)),
304
+ }
305
+ }
306
+ catch (error) {
307
+ return {
308
+ success: false,
309
+ records: [],
310
+ message: error instanceof Error ? error.message : 'Unknown error',
311
+ }
312
+ }
313
+ }
314
+
315
+ async canManageDomain(domain: string): Promise<boolean> {
316
+ try {
317
+ const rootDomain = this.getRootDomain(domain)
318
+ // Try to get domain details
319
+ await this.request('GET', `/v1/domains/${rootDomain}`)
320
+ return true
321
+ }
322
+ catch {
323
+ return false
324
+ }
325
+ }
326
+
327
+ /**
328
+ * List all domains managed by this GoDaddy account
329
+ */
330
+ async listDomains(): Promise<string[]> {
331
+ try {
332
+ const response = await this.request<Array<{ domain: string }>>('GET', '/v1/domains')
333
+ return response.map(d => d.domain)
334
+ }
335
+ catch {
336
+ return []
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Get domain details (GoDaddy-specific)
342
+ */
343
+ async getDomainDetails(domain: string): Promise<{
344
+ domain: string
345
+ status: string
346
+ nameServers?: string[]
347
+ expires?: string
348
+ } | null> {
349
+ try {
350
+ const rootDomain = this.getRootDomain(domain)
351
+ const details = await this.request<any>('GET', `/v1/domains/${rootDomain}`)
352
+
353
+ return {
354
+ domain: details.domain,
355
+ status: details.status,
356
+ nameServers: details.nameServers,
357
+ expires: details.expires,
358
+ }
359
+ }
360
+ catch {
361
+ return null
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Update nameservers for a domain (GoDaddy-specific)
367
+ */
368
+ async updateNameServers(domain: string, nameservers: string[]): Promise<boolean> {
369
+ try {
370
+ const rootDomain = this.getRootDomain(domain)
371
+ await this.request(
372
+ 'PUT',
373
+ `/v1/domains/${rootDomain}/records/NS`,
374
+ nameservers.map(ns => ({
375
+ type: 'NS',
376
+ name: '@',
377
+ data: ns,
378
+ ttl: 3600,
379
+ })),
380
+ )
381
+ return true
382
+ }
383
+ catch {
384
+ return false
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Check domain availability (GoDaddy-specific)
390
+ */
391
+ async checkDomainAvailability(domain: string): Promise<{
392
+ available: boolean
393
+ price?: number
394
+ currency?: string
395
+ }> {
396
+ try {
397
+ const result = await this.request<any>(
398
+ 'GET',
399
+ `/v1/domains/available?domain=${encodeURIComponent(domain)}`,
400
+ )
401
+
402
+ return {
403
+ available: result.available,
404
+ price: result.price,
405
+ currency: result.currency,
406
+ }
407
+ }
408
+ catch {
409
+ return { available: false }
410
+ }
411
+ }
412
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * DNS Provider Module
3
+ * Unified DNS management for Route53, Porkbun, and GoDaddy
4
+ */
5
+
6
+ export * from './types'
7
+ export { PorkbunProvider } from './porkbun'
8
+ export { GoDaddyProvider } from './godaddy'
9
+ export { CloudflareProvider } from './cloudflare'
10
+ export { Route53Provider } from './route53-adapter'
11
+ export {
12
+ UnifiedDnsValidator,
13
+ createPorkbunValidator,
14
+ createGoDaddyValidator,
15
+ createRoute53Validator,
16
+ } from './validator'
17
+
18
+ import type { DnsProvider, DnsProviderConfig } from './types'
19
+ import { CloudflareProvider } from './cloudflare'
20
+ import { GoDaddyProvider } from './godaddy'
21
+ import { PorkbunProvider } from './porkbun'
22
+ import { Route53Provider } from './route53-adapter'
23
+
24
+ /**
25
+ * Create a DNS provider from configuration
26
+ */
27
+ export function createDnsProvider(config: DnsProviderConfig): DnsProvider {
28
+ switch (config.provider) {
29
+ case 'route53':
30
+ return new Route53Provider(config.region, config.hostedZoneId)
31
+
32
+ case 'porkbun':
33
+ return new PorkbunProvider(config.apiKey, config.secretKey)
34
+
35
+ case 'godaddy':
36
+ return new GoDaddyProvider(config.apiKey, config.apiSecret, config.environment)
37
+
38
+ case 'cloudflare':
39
+ return new CloudflareProvider(config.apiToken)
40
+
41
+ default:
42
+ throw new Error(`Unknown DNS provider: ${(config as any).provider}`)
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Auto-detect DNS provider for a domain
48
+ * Tries each provider to see which one can manage the domain
49
+ */
50
+ export async function detectDnsProvider(
51
+ domain: string,
52
+ configs: DnsProviderConfig[],
53
+ ): Promise<DnsProvider | null> {
54
+ for (const config of configs) {
55
+ const provider = createDnsProvider(config)
56
+ if (await provider.canManageDomain(domain)) {
57
+ return provider
58
+ }
59
+ }
60
+ return null
61
+ }
62
+
63
+ /**
64
+ * DNS Provider factory with environment variable support
65
+ */
66
+ export class DnsProviderFactory {
67
+ private providers: Map<string, DnsProvider> = new Map()
68
+ private configs: DnsProviderConfig[] = []
69
+
70
+ /**
71
+ * Add provider configuration
72
+ */
73
+ addConfig(config: DnsProviderConfig): this {
74
+ this.configs.push(config)
75
+ return this
76
+ }
77
+
78
+ /**
79
+ * Add Route53 provider
80
+ */
81
+ addRoute53(region?: string, hostedZoneId?: string): this {
82
+ this.configs.push({
83
+ provider: 'route53',
84
+ region,
85
+ hostedZoneId,
86
+ })
87
+ return this
88
+ }
89
+
90
+ /**
91
+ * Add Porkbun provider
92
+ */
93
+ addPorkbun(apiKey: string, secretKey: string): this {
94
+ this.configs.push({
95
+ provider: 'porkbun',
96
+ apiKey,
97
+ secretKey,
98
+ })
99
+ return this
100
+ }
101
+
102
+ /**
103
+ * Add GoDaddy provider
104
+ */
105
+ addGoDaddy(apiKey: string, apiSecret: string, environment?: 'production' | 'ote'): this {
106
+ this.configs.push({
107
+ provider: 'godaddy',
108
+ apiKey,
109
+ apiSecret,
110
+ environment,
111
+ })
112
+ return this
113
+ }
114
+
115
+ /**
116
+ * Add Cloudflare provider
117
+ */
118
+ addCloudflare(apiToken: string): this {
119
+ this.configs.push({
120
+ provider: 'cloudflare',
121
+ apiToken,
122
+ })
123
+ return this
124
+ }
125
+
126
+ /**
127
+ * Load providers from environment variables
128
+ */
129
+ loadFromEnv(): this {
130
+ // Route53 (uses AWS credentials from environment)
131
+ if (process.env.AWS_ACCESS_KEY_ID || process.env.AWS_REGION) {
132
+ this.addRoute53(process.env.AWS_REGION)
133
+ }
134
+
135
+ // Porkbun
136
+ const porkbunApiKey = process.env.PORKBUN_API_KEY
137
+ const porkbunSecretKey = process.env.PORKBUN_SECRET_KEY
138
+ if (porkbunApiKey && porkbunSecretKey) {
139
+ this.addPorkbun(porkbunApiKey, porkbunSecretKey)
140
+ }
141
+
142
+ // GoDaddy
143
+ const godaddyApiKey = process.env.GODADDY_API_KEY
144
+ const godaddyApiSecret = process.env.GODADDY_API_SECRET
145
+ if (godaddyApiKey && godaddyApiSecret) {
146
+ const env = process.env.GODADDY_ENVIRONMENT as 'production' | 'ote' | undefined
147
+ this.addGoDaddy(godaddyApiKey, godaddyApiSecret, env)
148
+ }
149
+
150
+ // Cloudflare
151
+ const cloudflareApiToken = process.env.CLOUDFLARE_API_TOKEN
152
+ if (cloudflareApiToken) {
153
+ this.addCloudflare(cloudflareApiToken)
154
+ }
155
+
156
+ return this
157
+ }
158
+
159
+ /**
160
+ * Get a provider by name
161
+ */
162
+ getProvider(name: 'route53' | 'porkbun' | 'godaddy' | 'cloudflare'): DnsProvider | null {
163
+ // Check cache
164
+ const cached = this.providers.get(name)
165
+ if (cached) {
166
+ return cached
167
+ }
168
+
169
+ // Find config
170
+ const config = this.configs.find(c => c.provider === name)
171
+ if (!config) {
172
+ return null
173
+ }
174
+
175
+ // Create and cache provider
176
+ const provider = createDnsProvider(config)
177
+ this.providers.set(name, provider)
178
+ return provider
179
+ }
180
+
181
+ /**
182
+ * Auto-detect provider for a domain
183
+ */
184
+ async getProviderForDomain(domain: string): Promise<DnsProvider | null> {
185
+ for (const config of this.configs) {
186
+ const provider = createDnsProvider(config)
187
+ if (await provider.canManageDomain(domain)) {
188
+ return provider
189
+ }
190
+ }
191
+ return null
192
+ }
193
+
194
+ /**
195
+ * Get all configured providers
196
+ */
197
+ getAllProviders(): DnsProvider[] {
198
+ return this.configs.map(config => createDnsProvider(config))
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Default factory instance (can be configured globally)
204
+ */
205
+ export const dnsProviders: DnsProviderFactory = new DnsProviderFactory()