@stacksjs/ts-cloud 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 (117) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +321 -0
  3. package/bin/cli.ts +133 -0
  4. package/bin/commands/analytics.ts +328 -0
  5. package/bin/commands/api.ts +379 -0
  6. package/bin/commands/assets.ts +221 -0
  7. package/bin/commands/audit.ts +501 -0
  8. package/bin/commands/backup.ts +682 -0
  9. package/bin/commands/cache.ts +294 -0
  10. package/bin/commands/cdn.ts +281 -0
  11. package/bin/commands/config.ts +202 -0
  12. package/bin/commands/container.ts +105 -0
  13. package/bin/commands/cost.ts +208 -0
  14. package/bin/commands/database.ts +401 -0
  15. package/bin/commands/deploy.ts +674 -0
  16. package/bin/commands/domain.ts +397 -0
  17. package/bin/commands/email.ts +423 -0
  18. package/bin/commands/environment.ts +285 -0
  19. package/bin/commands/events.ts +424 -0
  20. package/bin/commands/firewall.ts +145 -0
  21. package/bin/commands/function.ts +116 -0
  22. package/bin/commands/generate.ts +280 -0
  23. package/bin/commands/git.ts +139 -0
  24. package/bin/commands/iam.ts +464 -0
  25. package/bin/commands/index.ts +48 -0
  26. package/bin/commands/init.ts +120 -0
  27. package/bin/commands/logs.ts +148 -0
  28. package/bin/commands/network.ts +579 -0
  29. package/bin/commands/notify.ts +489 -0
  30. package/bin/commands/queue.ts +407 -0
  31. package/bin/commands/scheduler.ts +370 -0
  32. package/bin/commands/secrets.ts +54 -0
  33. package/bin/commands/server.ts +629 -0
  34. package/bin/commands/shared.ts +97 -0
  35. package/bin/commands/ssl.ts +138 -0
  36. package/bin/commands/stack.ts +325 -0
  37. package/bin/commands/status.ts +385 -0
  38. package/bin/commands/storage.ts +450 -0
  39. package/bin/commands/team.ts +96 -0
  40. package/bin/commands/tunnel.ts +489 -0
  41. package/bin/commands/utils.ts +202 -0
  42. package/build.ts +15 -0
  43. package/cloud +2 -0
  44. package/package.json +99 -0
  45. package/src/aws/acm.ts +768 -0
  46. package/src/aws/application-autoscaling.ts +845 -0
  47. package/src/aws/bedrock.ts +4074 -0
  48. package/src/aws/client.ts +878 -0
  49. package/src/aws/cloudformation.ts +896 -0
  50. package/src/aws/cloudfront.ts +1531 -0
  51. package/src/aws/cloudwatch-logs.ts +154 -0
  52. package/src/aws/comprehend.ts +839 -0
  53. package/src/aws/connect.ts +1056 -0
  54. package/src/aws/deploy-imap.ts +384 -0
  55. package/src/aws/dynamodb.ts +340 -0
  56. package/src/aws/ec2.ts +1385 -0
  57. package/src/aws/ecr.ts +621 -0
  58. package/src/aws/ecs.ts +615 -0
  59. package/src/aws/elasticache.ts +301 -0
  60. package/src/aws/elbv2.ts +942 -0
  61. package/src/aws/email.ts +928 -0
  62. package/src/aws/eventbridge.ts +248 -0
  63. package/src/aws/iam.ts +1689 -0
  64. package/src/aws/imap-server.ts +2100 -0
  65. package/src/aws/index.ts +213 -0
  66. package/src/aws/kendra.ts +1097 -0
  67. package/src/aws/lambda.ts +786 -0
  68. package/src/aws/opensearch.ts +158 -0
  69. package/src/aws/personalize.ts +977 -0
  70. package/src/aws/polly.ts +559 -0
  71. package/src/aws/rds.ts +888 -0
  72. package/src/aws/rekognition.ts +846 -0
  73. package/src/aws/route53-domains.ts +359 -0
  74. package/src/aws/route53.ts +1046 -0
  75. package/src/aws/s3.ts +2318 -0
  76. package/src/aws/scheduler.ts +571 -0
  77. package/src/aws/secrets-manager.ts +769 -0
  78. package/src/aws/ses.ts +1081 -0
  79. package/src/aws/setup-phone.ts +104 -0
  80. package/src/aws/setup-sms.ts +580 -0
  81. package/src/aws/sms.ts +1735 -0
  82. package/src/aws/smtp-server.ts +531 -0
  83. package/src/aws/sns.ts +758 -0
  84. package/src/aws/sqs.ts +382 -0
  85. package/src/aws/ssm.ts +807 -0
  86. package/src/aws/sts.ts +92 -0
  87. package/src/aws/support.ts +391 -0
  88. package/src/aws/test-imap.ts +86 -0
  89. package/src/aws/textract.ts +780 -0
  90. package/src/aws/transcribe.ts +108 -0
  91. package/src/aws/translate.ts +641 -0
  92. package/src/aws/voice.ts +1379 -0
  93. package/src/config.ts +35 -0
  94. package/src/deploy/index.ts +7 -0
  95. package/src/deploy/static-site-external-dns.ts +906 -0
  96. package/src/deploy/static-site.ts +1125 -0
  97. package/src/dns/godaddy.ts +412 -0
  98. package/src/dns/index.ts +183 -0
  99. package/src/dns/porkbun.ts +362 -0
  100. package/src/dns/route53-adapter.ts +414 -0
  101. package/src/dns/types.ts +114 -0
  102. package/src/dns/validator.ts +369 -0
  103. package/src/generators/index.ts +5 -0
  104. package/src/generators/infrastructure.ts +1660 -0
  105. package/src/index.ts +163 -0
  106. package/src/push/apns.ts +452 -0
  107. package/src/push/fcm.ts +506 -0
  108. package/src/push/index.ts +58 -0
  109. package/src/ssl/acme-client.ts +478 -0
  110. package/src/ssl/index.ts +7 -0
  111. package/src/ssl/letsencrypt.ts +747 -0
  112. package/src/types.ts +2 -0
  113. package/src/utils/cli.ts +398 -0
  114. package/src/validation/index.ts +5 -0
  115. package/src/validation/template.ts +405 -0
  116. package/test/index.test.ts +128 -0
  117. package/tsconfig.json +18 -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,183 @@
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 { Route53Provider } from './route53-adapter'
10
+ export {
11
+ UnifiedDnsValidator,
12
+ createPorkbunValidator,
13
+ createGoDaddyValidator,
14
+ createRoute53Validator,
15
+ } from './validator'
16
+
17
+ import type { DnsProvider, DnsProviderConfig } from './types'
18
+ import { GoDaddyProvider } from './godaddy'
19
+ import { PorkbunProvider } from './porkbun'
20
+ import { Route53Provider } from './route53-adapter'
21
+
22
+ /**
23
+ * Create a DNS provider from configuration
24
+ */
25
+ export function createDnsProvider(config: DnsProviderConfig): DnsProvider {
26
+ switch (config.provider) {
27
+ case 'route53':
28
+ return new Route53Provider(config.region, config.hostedZoneId)
29
+
30
+ case 'porkbun':
31
+ return new PorkbunProvider(config.apiKey, config.secretKey)
32
+
33
+ case 'godaddy':
34
+ return new GoDaddyProvider(config.apiKey, config.apiSecret, config.environment)
35
+
36
+ default:
37
+ throw new Error(`Unknown DNS provider: ${(config as any).provider}`)
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Auto-detect DNS provider for a domain
43
+ * Tries each provider to see which one can manage the domain
44
+ */
45
+ export async function detectDnsProvider(
46
+ domain: string,
47
+ configs: DnsProviderConfig[],
48
+ ): Promise<DnsProvider | null> {
49
+ for (const config of configs) {
50
+ const provider = createDnsProvider(config)
51
+ if (await provider.canManageDomain(domain)) {
52
+ return provider
53
+ }
54
+ }
55
+ return null
56
+ }
57
+
58
+ /**
59
+ * DNS Provider factory with environment variable support
60
+ */
61
+ export class DnsProviderFactory {
62
+ private providers: Map<string, DnsProvider> = new Map()
63
+ private configs: DnsProviderConfig[] = []
64
+
65
+ /**
66
+ * Add provider configuration
67
+ */
68
+ addConfig(config: DnsProviderConfig): this {
69
+ this.configs.push(config)
70
+ return this
71
+ }
72
+
73
+ /**
74
+ * Add Route53 provider
75
+ */
76
+ addRoute53(region?: string, hostedZoneId?: string): this {
77
+ this.configs.push({
78
+ provider: 'route53',
79
+ region,
80
+ hostedZoneId,
81
+ })
82
+ return this
83
+ }
84
+
85
+ /**
86
+ * Add Porkbun provider
87
+ */
88
+ addPorkbun(apiKey: string, secretKey: string): this {
89
+ this.configs.push({
90
+ provider: 'porkbun',
91
+ apiKey,
92
+ secretKey,
93
+ })
94
+ return this
95
+ }
96
+
97
+ /**
98
+ * Add GoDaddy provider
99
+ */
100
+ addGoDaddy(apiKey: string, apiSecret: string, environment?: 'production' | 'ote'): this {
101
+ this.configs.push({
102
+ provider: 'godaddy',
103
+ apiKey,
104
+ apiSecret,
105
+ environment,
106
+ })
107
+ return this
108
+ }
109
+
110
+ /**
111
+ * Load providers from environment variables
112
+ */
113
+ loadFromEnv(): this {
114
+ // Route53 (uses AWS credentials from environment)
115
+ if (process.env.AWS_ACCESS_KEY_ID || process.env.AWS_REGION) {
116
+ this.addRoute53(process.env.AWS_REGION)
117
+ }
118
+
119
+ // Porkbun
120
+ const porkbunApiKey = process.env.PORKBUN_API_KEY
121
+ const porkbunSecretKey = process.env.PORKBUN_SECRET_KEY
122
+ if (porkbunApiKey && porkbunSecretKey) {
123
+ this.addPorkbun(porkbunApiKey, porkbunSecretKey)
124
+ }
125
+
126
+ // GoDaddy
127
+ const godaddyApiKey = process.env.GODADDY_API_KEY
128
+ const godaddyApiSecret = process.env.GODADDY_API_SECRET
129
+ if (godaddyApiKey && godaddyApiSecret) {
130
+ const env = process.env.GODADDY_ENVIRONMENT as 'production' | 'ote' | undefined
131
+ this.addGoDaddy(godaddyApiKey, godaddyApiSecret, env)
132
+ }
133
+
134
+ return this
135
+ }
136
+
137
+ /**
138
+ * Get a provider by name
139
+ */
140
+ getProvider(name: 'route53' | 'porkbun' | 'godaddy'): DnsProvider | null {
141
+ // Check cache
142
+ const cached = this.providers.get(name)
143
+ if (cached) {
144
+ return cached
145
+ }
146
+
147
+ // Find config
148
+ const config = this.configs.find(c => c.provider === name)
149
+ if (!config) {
150
+ return null
151
+ }
152
+
153
+ // Create and cache provider
154
+ const provider = createDnsProvider(config)
155
+ this.providers.set(name, provider)
156
+ return provider
157
+ }
158
+
159
+ /**
160
+ * Auto-detect provider for a domain
161
+ */
162
+ async getProviderForDomain(domain: string): Promise<DnsProvider | null> {
163
+ for (const config of this.configs) {
164
+ const provider = createDnsProvider(config)
165
+ if (await provider.canManageDomain(domain)) {
166
+ return provider
167
+ }
168
+ }
169
+ return null
170
+ }
171
+
172
+ /**
173
+ * Get all configured providers
174
+ */
175
+ getAllProviders(): DnsProvider[] {
176
+ return this.configs.map(config => createDnsProvider(config))
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Default factory instance (can be configured globally)
182
+ */
183
+ export const dnsProviders: DnsProviderFactory = new DnsProviderFactory()