@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.
- package/dist/aws/s3.d.ts +1 -1
- package/dist/bin/cli.js +223 -222
- package/dist/index.js +132 -132
- package/package.json +18 -16
- package/src/aws/acm.ts +768 -0
- package/src/aws/application-autoscaling.ts +845 -0
- package/src/aws/bedrock.ts +4074 -0
- package/src/aws/client.ts +891 -0
- package/src/aws/cloudformation.ts +896 -0
- package/src/aws/cloudfront.ts +1531 -0
- package/src/aws/cloudwatch-logs.ts +154 -0
- package/src/aws/comprehend.ts +839 -0
- package/src/aws/connect.ts +1056 -0
- package/src/aws/deploy-imap.ts +384 -0
- package/src/aws/dynamodb.ts +340 -0
- package/src/aws/ec2.ts +1385 -0
- package/src/aws/ecr.ts +621 -0
- package/src/aws/ecs.ts +615 -0
- package/src/aws/elasticache.ts +301 -0
- package/src/aws/elbv2.ts +942 -0
- package/src/aws/email.ts +928 -0
- package/src/aws/eventbridge.ts +248 -0
- package/src/aws/iam.ts +1689 -0
- package/src/aws/imap-server.ts +2100 -0
- package/src/aws/index.ts +213 -0
- package/src/aws/kendra.ts +1097 -0
- package/src/aws/lambda.ts +786 -0
- package/src/aws/opensearch.ts +158 -0
- package/src/aws/personalize.ts +977 -0
- package/src/aws/polly.ts +559 -0
- package/src/aws/rds.ts +888 -0
- package/src/aws/rekognition.ts +846 -0
- package/src/aws/route53-domains.ts +359 -0
- package/src/aws/route53.ts +1046 -0
- package/src/aws/s3.ts +2334 -0
- package/src/aws/scheduler.ts +571 -0
- package/src/aws/secrets-manager.ts +769 -0
- package/src/aws/ses.ts +1081 -0
- package/src/aws/setup-phone.ts +104 -0
- package/src/aws/setup-sms.ts +580 -0
- package/src/aws/sms.ts +1735 -0
- package/src/aws/smtp-server.ts +531 -0
- package/src/aws/sns.ts +758 -0
- package/src/aws/sqs.ts +382 -0
- package/src/aws/ssm.ts +807 -0
- package/src/aws/sts.ts +92 -0
- package/src/aws/support.ts +391 -0
- package/src/aws/test-imap.ts +86 -0
- package/src/aws/textract.ts +780 -0
- package/src/aws/transcribe.ts +108 -0
- package/src/aws/translate.ts +641 -0
- package/src/aws/voice.ts +1379 -0
- package/src/config.ts +35 -0
- package/src/deploy/index.ts +7 -0
- package/src/deploy/static-site-external-dns.ts +945 -0
- package/src/deploy/static-site.ts +1175 -0
- package/src/dns/cloudflare.ts +548 -0
- package/src/dns/godaddy.ts +412 -0
- package/src/dns/index.ts +205 -0
- package/src/dns/porkbun.ts +362 -0
- package/src/dns/route53-adapter.ts +414 -0
- package/src/dns/types.ts +119 -0
- package/src/dns/validator.ts +369 -0
- package/src/generators/index.ts +5 -0
- package/src/generators/infrastructure.ts +1660 -0
- package/src/index.ts +163 -0
- package/src/push/apns.ts +452 -0
- package/src/push/fcm.ts +506 -0
- package/src/push/index.ts +58 -0
- package/src/security/pre-deploy-scanner.ts +655 -0
- package/src/ssl/acme-client.ts +478 -0
- package/src/ssl/index.ts +7 -0
- package/src/ssl/letsencrypt.ts +747 -0
- package/src/types.ts +2 -0
- package/src/utils/cli.ts +398 -0
- package/src/validation/index.ts +5 -0
- 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
|
+
}
|
package/src/dns/index.ts
ADDED
|
@@ -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()
|