@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,548 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare DNS Provider
|
|
3
|
+
* API documentation: https://developers.cloudflare.com/api/resources/dns/subresources/records/
|
|
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 CLOUDFLARE_API_URL = 'https://api.cloudflare.com/client/v4'
|
|
17
|
+
|
|
18
|
+
interface CloudflareApiResponse<T = any> {
|
|
19
|
+
success: boolean
|
|
20
|
+
errors: Array<{ code: number, message: string }>
|
|
21
|
+
messages: string[]
|
|
22
|
+
result: T
|
|
23
|
+
result_info?: {
|
|
24
|
+
page: number
|
|
25
|
+
per_page: number
|
|
26
|
+
total_pages: number
|
|
27
|
+
count: number
|
|
28
|
+
total_count: number
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface CloudflareRecord {
|
|
33
|
+
id: string
|
|
34
|
+
zone_id: string
|
|
35
|
+
zone_name: string
|
|
36
|
+
name: string
|
|
37
|
+
type: string
|
|
38
|
+
content: string
|
|
39
|
+
proxiable: boolean
|
|
40
|
+
proxied: boolean
|
|
41
|
+
ttl: number
|
|
42
|
+
locked: boolean
|
|
43
|
+
meta: Record<string, any>
|
|
44
|
+
comment?: string
|
|
45
|
+
tags?: string[]
|
|
46
|
+
created_on: string
|
|
47
|
+
modified_on: string
|
|
48
|
+
priority?: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface CloudflareZone {
|
|
52
|
+
id: string
|
|
53
|
+
name: string
|
|
54
|
+
status: string
|
|
55
|
+
paused: boolean
|
|
56
|
+
type: string
|
|
57
|
+
development_mode: number
|
|
58
|
+
name_servers: string[]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class CloudflareProvider implements DnsProvider {
|
|
62
|
+
readonly name = 'cloudflare'
|
|
63
|
+
private apiToken: string
|
|
64
|
+
private zoneCache: Map<string, string> = new Map()
|
|
65
|
+
|
|
66
|
+
constructor(apiToken: string) {
|
|
67
|
+
this.apiToken = apiToken
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Make an authenticated API request to Cloudflare
|
|
72
|
+
*/
|
|
73
|
+
private async request<T>(
|
|
74
|
+
method: string,
|
|
75
|
+
endpoint: string,
|
|
76
|
+
body?: any,
|
|
77
|
+
): Promise<CloudflareApiResponse<T>> {
|
|
78
|
+
const url = `${CLOUDFLARE_API_URL}${endpoint}`
|
|
79
|
+
|
|
80
|
+
const headers: Record<string, string> = {
|
|
81
|
+
'Authorization': `Bearer ${this.apiToken}`,
|
|
82
|
+
'Content-Type': 'application/json',
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const options: RequestInit = {
|
|
86
|
+
method,
|
|
87
|
+
headers,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (body) {
|
|
91
|
+
options.body = JSON.stringify(body)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const response = await fetch(url, options)
|
|
95
|
+
const data = await response.json() as CloudflareApiResponse<T>
|
|
96
|
+
|
|
97
|
+
if (!data.success) {
|
|
98
|
+
const errorMessages = data.errors.map(e => e.message).join(', ')
|
|
99
|
+
throw new Error(`Cloudflare API error: ${errorMessages}`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return data
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get the root domain from a full domain name
|
|
107
|
+
* e.g., "api.example.com" -> "example.com"
|
|
108
|
+
*/
|
|
109
|
+
private getRootDomain(domain: string): string {
|
|
110
|
+
const parts = domain.replace(/\.$/, '').split('.')
|
|
111
|
+
if (parts.length >= 2) {
|
|
112
|
+
return parts.slice(-2).join('.')
|
|
113
|
+
}
|
|
114
|
+
return domain
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get Zone ID for a domain (with caching)
|
|
119
|
+
*/
|
|
120
|
+
private async getZoneId(domain: string): Promise<string> {
|
|
121
|
+
const rootDomain = this.getRootDomain(domain)
|
|
122
|
+
|
|
123
|
+
// Check cache first
|
|
124
|
+
const cached = this.zoneCache.get(rootDomain)
|
|
125
|
+
if (cached) {
|
|
126
|
+
return cached
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Look up zone by name
|
|
130
|
+
const response = await this.request<CloudflareZone[]>(
|
|
131
|
+
'GET',
|
|
132
|
+
`/zones?name=${encodeURIComponent(rootDomain)}`,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if (!response.result || response.result.length === 0) {
|
|
136
|
+
throw new Error(`Zone not found for domain: ${rootDomain}`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const zoneId = response.result[0].id
|
|
140
|
+
this.zoneCache.set(rootDomain, zoneId)
|
|
141
|
+
return zoneId
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get the full record name
|
|
146
|
+
* Cloudflare stores records with full domain names
|
|
147
|
+
*/
|
|
148
|
+
private getFullRecordName(name: string, domain: string): string {
|
|
149
|
+
const rootDomain = this.getRootDomain(domain)
|
|
150
|
+
const cleanName = name.replace(/\.$/, '')
|
|
151
|
+
|
|
152
|
+
// If name is empty or equals root domain, return root domain
|
|
153
|
+
if (!cleanName || cleanName === rootDomain || cleanName === '@') {
|
|
154
|
+
return rootDomain
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// If name already ends with root domain, return as-is
|
|
158
|
+
if (cleanName.endsWith(`.${rootDomain}`)) {
|
|
159
|
+
return cleanName
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Otherwise, append root domain
|
|
163
|
+
return `${cleanName}.${rootDomain}`
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Convert DnsRecord to Cloudflare record format
|
|
168
|
+
*/
|
|
169
|
+
private toCloudflareRecord(record: DnsRecord, domain: string): Partial<CloudflareRecord> {
|
|
170
|
+
const cfRecord: Partial<CloudflareRecord> = {
|
|
171
|
+
type: record.type,
|
|
172
|
+
name: this.getFullRecordName(record.name, domain),
|
|
173
|
+
content: record.content || record.value || '',
|
|
174
|
+
ttl: record.ttl || 1, // 1 = automatic in Cloudflare
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// MX records require priority
|
|
178
|
+
if (record.type === 'MX' && record.priority !== undefined) {
|
|
179
|
+
cfRecord.priority = record.priority
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// SRV records have special format
|
|
183
|
+
if (record.type === 'SRV') {
|
|
184
|
+
if (record.priority !== undefined) {
|
|
185
|
+
cfRecord.priority = record.priority
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return cfRecord
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Convert Cloudflare record to DnsRecordResult format
|
|
194
|
+
*/
|
|
195
|
+
private fromCloudflareRecord(record: CloudflareRecord): DnsRecordResult {
|
|
196
|
+
return {
|
|
197
|
+
id: record.id,
|
|
198
|
+
name: record.name,
|
|
199
|
+
type: record.type as DnsRecordType,
|
|
200
|
+
content: record.content,
|
|
201
|
+
ttl: record.ttl,
|
|
202
|
+
priority: record.priority,
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async createRecord(domain: string, record: DnsRecord): Promise<CreateRecordResult> {
|
|
207
|
+
try {
|
|
208
|
+
const zoneId = await this.getZoneId(domain)
|
|
209
|
+
const cfRecord = this.toCloudflareRecord(record, domain)
|
|
210
|
+
|
|
211
|
+
const response = await this.request<CloudflareRecord>(
|
|
212
|
+
'POST',
|
|
213
|
+
`/zones/${zoneId}/dns_records`,
|
|
214
|
+
cfRecord,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
success: true,
|
|
219
|
+
id: response.result.id,
|
|
220
|
+
message: 'Record created successfully',
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async upsertRecord(domain: string, record: DnsRecord): Promise<CreateRecordResult> {
|
|
232
|
+
try {
|
|
233
|
+
const zoneId = await this.getZoneId(domain)
|
|
234
|
+
const fullName = this.getFullRecordName(record.name, domain)
|
|
235
|
+
|
|
236
|
+
// First, try to find existing record
|
|
237
|
+
const existingResponse = await this.request<CloudflareRecord[]>(
|
|
238
|
+
'GET',
|
|
239
|
+
`/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(fullName)}`,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
const cfRecord = this.toCloudflareRecord(record, domain)
|
|
243
|
+
|
|
244
|
+
if (existingResponse.result && existingResponse.result.length > 0) {
|
|
245
|
+
// Update existing record
|
|
246
|
+
const existingId = existingResponse.result[0].id
|
|
247
|
+
const response = await this.request<CloudflareRecord>(
|
|
248
|
+
'PUT',
|
|
249
|
+
`/zones/${zoneId}/dns_records/${existingId}`,
|
|
250
|
+
cfRecord,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
success: true,
|
|
255
|
+
id: response.result.id,
|
|
256
|
+
message: 'Record updated successfully',
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Create new record
|
|
261
|
+
const response = await this.request<CloudflareRecord>(
|
|
262
|
+
'POST',
|
|
263
|
+
`/zones/${zoneId}/dns_records`,
|
|
264
|
+
cfRecord,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
success: true,
|
|
269
|
+
id: response.result.id,
|
|
270
|
+
message: 'Record created successfully',
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
// If upsert fails, try create
|
|
275
|
+
return this.createRecord(domain, record)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async deleteRecord(domain: string, record: DnsRecord): Promise<DeleteRecordResult> {
|
|
280
|
+
try {
|
|
281
|
+
const zoneId = await this.getZoneId(domain)
|
|
282
|
+
const fullName = this.getFullRecordName(record.name, domain)
|
|
283
|
+
|
|
284
|
+
// Find the record to delete
|
|
285
|
+
const existingResponse = await this.request<CloudflareRecord[]>(
|
|
286
|
+
'GET',
|
|
287
|
+
`/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(fullName)}`,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if (!existingResponse.result || existingResponse.result.length === 0) {
|
|
291
|
+
return {
|
|
292
|
+
success: false,
|
|
293
|
+
message: 'Record not found',
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Find matching record by content
|
|
298
|
+
const matchingRecord = existingResponse.result.find(
|
|
299
|
+
r => r.content === record.content,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
if (!matchingRecord) {
|
|
303
|
+
return {
|
|
304
|
+
success: false,
|
|
305
|
+
message: 'Record with matching content not found',
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
await this.request(
|
|
310
|
+
'DELETE',
|
|
311
|
+
`/zones/${zoneId}/dns_records/${matchingRecord.id}`,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
success: true,
|
|
316
|
+
message: 'Record deleted successfully',
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
return {
|
|
321
|
+
success: false,
|
|
322
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async listRecords(domain: string, type?: DnsRecordType): Promise<ListRecordsResult> {
|
|
328
|
+
try {
|
|
329
|
+
const zoneId = await this.getZoneId(domain)
|
|
330
|
+
|
|
331
|
+
let endpoint = `/zones/${zoneId}/dns_records?per_page=100`
|
|
332
|
+
if (type) {
|
|
333
|
+
endpoint += `&type=${type}`
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const allRecords: CloudflareRecord[] = []
|
|
337
|
+
let page = 1
|
|
338
|
+
let hasMore = true
|
|
339
|
+
|
|
340
|
+
// Paginate through all records
|
|
341
|
+
while (hasMore) {
|
|
342
|
+
const response = await this.request<CloudflareRecord[]>(
|
|
343
|
+
'GET',
|
|
344
|
+
`${endpoint}&page=${page}`,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
allRecords.push(...(response.result || []))
|
|
348
|
+
|
|
349
|
+
if (response.result_info) {
|
|
350
|
+
hasMore = page < response.result_info.total_pages
|
|
351
|
+
page++
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
hasMore = false
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
success: true,
|
|
360
|
+
records: allRecords.map(r => this.fromCloudflareRecord(r)),
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
return {
|
|
365
|
+
success: false,
|
|
366
|
+
records: [],
|
|
367
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async canManageDomain(domain: string): Promise<boolean> {
|
|
373
|
+
try {
|
|
374
|
+
await this.getZoneId(domain)
|
|
375
|
+
return true
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return false
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* List all domains (zones) managed by this Cloudflare account
|
|
384
|
+
*/
|
|
385
|
+
async listDomains(): Promise<string[]> {
|
|
386
|
+
try {
|
|
387
|
+
const allZones: CloudflareZone[] = []
|
|
388
|
+
let page = 1
|
|
389
|
+
let hasMore = true
|
|
390
|
+
|
|
391
|
+
while (hasMore) {
|
|
392
|
+
const response = await this.request<CloudflareZone[]>(
|
|
393
|
+
'GET',
|
|
394
|
+
`/zones?per_page=50&page=${page}`,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
allZones.push(...(response.result || []))
|
|
398
|
+
|
|
399
|
+
if (response.result_info) {
|
|
400
|
+
hasMore = page < response.result_info.total_pages
|
|
401
|
+
page++
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
hasMore = false
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return allZones.map(z => z.name)
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
return []
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Get zone details (Cloudflare-specific)
|
|
417
|
+
*/
|
|
418
|
+
async getZoneDetails(domain: string): Promise<{
|
|
419
|
+
id: string
|
|
420
|
+
name: string
|
|
421
|
+
status: string
|
|
422
|
+
nameServers: string[]
|
|
423
|
+
paused: boolean
|
|
424
|
+
} | null> {
|
|
425
|
+
try {
|
|
426
|
+
const zoneId = await this.getZoneId(domain)
|
|
427
|
+
const response = await this.request<CloudflareZone>(
|
|
428
|
+
'GET',
|
|
429
|
+
`/zones/${zoneId}`,
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
id: response.result.id,
|
|
434
|
+
name: response.result.name,
|
|
435
|
+
status: response.result.status,
|
|
436
|
+
nameServers: response.result.name_servers,
|
|
437
|
+
paused: response.result.paused,
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
return null
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Purge cache for a domain (Cloudflare-specific)
|
|
447
|
+
*/
|
|
448
|
+
async purgeCache(domain: string, options?: {
|
|
449
|
+
purgeEverything?: boolean
|
|
450
|
+
files?: string[]
|
|
451
|
+
tags?: string[]
|
|
452
|
+
hosts?: string[]
|
|
453
|
+
}): Promise<boolean> {
|
|
454
|
+
try {
|
|
455
|
+
const zoneId = await this.getZoneId(domain)
|
|
456
|
+
|
|
457
|
+
const body: Record<string, any> = {}
|
|
458
|
+
|
|
459
|
+
if (options?.purgeEverything) {
|
|
460
|
+
body.purge_everything = true
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
if (options?.files) body.files = options.files
|
|
464
|
+
if (options?.tags) body.tags = options.tags
|
|
465
|
+
if (options?.hosts) body.hosts = options.hosts
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Default to purge everything if no options specified
|
|
469
|
+
if (Object.keys(body).length === 0) {
|
|
470
|
+
body.purge_everything = true
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
await this.request(
|
|
474
|
+
'POST',
|
|
475
|
+
`/zones/${zoneId}/purge_cache`,
|
|
476
|
+
body,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
return true
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
return false
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Get proxy status for a record (Cloudflare-specific)
|
|
488
|
+
* Returns whether a record is proxied through Cloudflare
|
|
489
|
+
*/
|
|
490
|
+
async getRecordProxyStatus(domain: string, record: DnsRecord): Promise<boolean | null> {
|
|
491
|
+
try {
|
|
492
|
+
const zoneId = await this.getZoneId(domain)
|
|
493
|
+
const fullName = this.getFullRecordName(record.name, domain)
|
|
494
|
+
|
|
495
|
+
const response = await this.request<CloudflareRecord[]>(
|
|
496
|
+
'GET',
|
|
497
|
+
`/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(fullName)}`,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
if (response.result && response.result.length > 0) {
|
|
501
|
+
const matchingRecord = response.result.find(r => r.content === record.content)
|
|
502
|
+
return matchingRecord?.proxied ?? null
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return null
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
508
|
+
return null
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Update proxy status for a record (Cloudflare-specific)
|
|
514
|
+
*/
|
|
515
|
+
async setRecordProxyStatus(domain: string, record: DnsRecord, proxied: boolean): Promise<boolean> {
|
|
516
|
+
try {
|
|
517
|
+
const zoneId = await this.getZoneId(domain)
|
|
518
|
+
const fullName = this.getFullRecordName(record.name, domain)
|
|
519
|
+
|
|
520
|
+
// Find the record
|
|
521
|
+
const response = await this.request<CloudflareRecord[]>(
|
|
522
|
+
'GET',
|
|
523
|
+
`/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(fullName)}`,
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
if (!response.result || response.result.length === 0) {
|
|
527
|
+
return false
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const matchingRecord = response.result.find(r => r.content === record.content)
|
|
531
|
+
if (!matchingRecord) {
|
|
532
|
+
return false
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Update the record with new proxy status
|
|
536
|
+
await this.request(
|
|
537
|
+
'PATCH',
|
|
538
|
+
`/zones/${zoneId}/dns_records/${matchingRecord.id}`,
|
|
539
|
+
{ proxied },
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
return true
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
return false
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|