@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,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Porkbun DNS Provider
|
|
3
|
+
* API documentation: https://porkbun.com/api/json/v3/documentation
|
|
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 PORKBUN_API_URL = 'https://api.porkbun.com/api/json/v3'
|
|
17
|
+
|
|
18
|
+
interface PorkbunApiResponse {
|
|
19
|
+
status: 'SUCCESS' | 'ERROR'
|
|
20
|
+
message?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface PorkbunRecord {
|
|
24
|
+
id: string
|
|
25
|
+
name: string
|
|
26
|
+
type: string
|
|
27
|
+
content: string
|
|
28
|
+
ttl: string
|
|
29
|
+
prio?: string
|
|
30
|
+
notes?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface PorkbunListRecordsResponse extends PorkbunApiResponse {
|
|
34
|
+
records?: PorkbunRecord[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface PorkbunCreateRecordResponse extends PorkbunApiResponse {
|
|
38
|
+
id?: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class PorkbunProvider implements DnsProvider {
|
|
42
|
+
readonly name = 'porkbun'
|
|
43
|
+
private apiKey: string
|
|
44
|
+
private secretKey: string
|
|
45
|
+
|
|
46
|
+
constructor(apiKey: string, secretKey: string) {
|
|
47
|
+
this.apiKey = apiKey
|
|
48
|
+
this.secretKey = secretKey
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Make an authenticated API request to Porkbun
|
|
53
|
+
*/
|
|
54
|
+
private async request<T extends PorkbunApiResponse>(
|
|
55
|
+
endpoint: string,
|
|
56
|
+
additionalBody: Record<string, any> = {},
|
|
57
|
+
): Promise<T> {
|
|
58
|
+
const response = await fetch(`${PORKBUN_API_URL}${endpoint}`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
apikey: this.apiKey,
|
|
65
|
+
secretapikey: this.secretKey,
|
|
66
|
+
...additionalBody,
|
|
67
|
+
}),
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new Error(`Porkbun API error: ${response.status} ${response.statusText}`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const data = await response.json() as T
|
|
75
|
+
|
|
76
|
+
if (data.status === 'ERROR') {
|
|
77
|
+
throw new Error(`Porkbun API error: ${data.message || 'Unknown error'}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return data
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Extract the subdomain from a full record name
|
|
85
|
+
* e.g., "_acme-challenge.example.com" -> "_acme-challenge"
|
|
86
|
+
*/
|
|
87
|
+
private getSubdomain(recordName: string, domain: string): string {
|
|
88
|
+
// Remove trailing dots
|
|
89
|
+
const cleanName = recordName.replace(/\.$/, '')
|
|
90
|
+
const cleanDomain = domain.replace(/\.$/, '')
|
|
91
|
+
|
|
92
|
+
// If the record name equals the domain, return empty string (root)
|
|
93
|
+
if (cleanName === cleanDomain) {
|
|
94
|
+
return ''
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Remove the domain suffix to get the subdomain
|
|
98
|
+
if (cleanName.endsWith(`.${cleanDomain}`)) {
|
|
99
|
+
return cleanName.slice(0, -(cleanDomain.length + 1))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// If no match, return the full name as subdomain
|
|
103
|
+
return cleanName
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the root domain from a full domain name
|
|
108
|
+
* e.g., "api.example.com" -> "example.com"
|
|
109
|
+
*/
|
|
110
|
+
private getRootDomain(domain: string): string {
|
|
111
|
+
const parts = domain.replace(/\.$/, '').split('.')
|
|
112
|
+
// Handle common TLDs
|
|
113
|
+
if (parts.length >= 2) {
|
|
114
|
+
return parts.slice(-2).join('.')
|
|
115
|
+
}
|
|
116
|
+
return domain
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async createRecord(domain: string, record: DnsRecord): Promise<CreateRecordResult> {
|
|
120
|
+
try {
|
|
121
|
+
const rootDomain = this.getRootDomain(domain)
|
|
122
|
+
const subdomain = this.getSubdomain(record.name, rootDomain)
|
|
123
|
+
|
|
124
|
+
const body: Record<string, any> = {
|
|
125
|
+
type: record.type,
|
|
126
|
+
content: record.content,
|
|
127
|
+
ttl: String(record.ttl || 600),
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// For subdomain records
|
|
131
|
+
if (subdomain) {
|
|
132
|
+
body.name = subdomain
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// MX and SRV records require priority
|
|
136
|
+
if ((record.type === 'MX' || record.type === 'SRV') && record.priority !== undefined) {
|
|
137
|
+
body.prio = String(record.priority)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// SRV records: Porkbun expects content as "WEIGHT PORT TARGET"
|
|
141
|
+
// Override content format if weight and port are provided separately
|
|
142
|
+
if (record.type === 'SRV' && record.weight !== undefined && record.port !== undefined) {
|
|
143
|
+
body.content = `${record.weight} ${record.port} ${record.content}`
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const response = await this.request<PorkbunCreateRecordResponse>(
|
|
147
|
+
`/dns/create/${rootDomain}`,
|
|
148
|
+
body,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
success: true,
|
|
153
|
+
id: response.id?.toString(),
|
|
154
|
+
message: 'Record created successfully',
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
return {
|
|
159
|
+
success: false,
|
|
160
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async upsertRecord(domain: string, record: DnsRecord): Promise<CreateRecordResult> {
|
|
166
|
+
try {
|
|
167
|
+
const rootDomain = this.getRootDomain(domain)
|
|
168
|
+
const subdomain = this.getSubdomain(record.name, rootDomain)
|
|
169
|
+
|
|
170
|
+
// First, try to find existing record
|
|
171
|
+
const existing = await this.listRecords(domain, record.type)
|
|
172
|
+
|
|
173
|
+
if (existing.success) {
|
|
174
|
+
// Find matching record by name and type
|
|
175
|
+
const matchingRecord = existing.records.find((r) => {
|
|
176
|
+
const existingSubdomain = this.getSubdomain(r.name, rootDomain)
|
|
177
|
+
return existingSubdomain === subdomain && r.type === record.type
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
if (matchingRecord?.id) {
|
|
181
|
+
// Update existing record
|
|
182
|
+
const body: Record<string, any> = {
|
|
183
|
+
type: record.type,
|
|
184
|
+
content: record.content,
|
|
185
|
+
ttl: String(record.ttl || 600),
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (subdomain) {
|
|
189
|
+
body.name = subdomain
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if ((record.type === 'MX' || record.type === 'SRV') && record.priority !== undefined) {
|
|
193
|
+
body.prio = String(record.priority)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// SRV records: Porkbun expects content as "WEIGHT PORT TARGET"
|
|
197
|
+
if (record.type === 'SRV' && record.weight !== undefined && record.port !== undefined) {
|
|
198
|
+
body.content = `${record.weight} ${record.port} ${record.content}`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await this.request(
|
|
202
|
+
`/dns/edit/${rootDomain}/${matchingRecord.id}`,
|
|
203
|
+
body,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
success: true,
|
|
208
|
+
id: matchingRecord.id,
|
|
209
|
+
message: 'Record updated successfully',
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// No existing record found, create new one
|
|
215
|
+
return this.createRecord(domain, record)
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
// If update fails, try create
|
|
219
|
+
return this.createRecord(domain, record)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async deleteRecord(domain: string, record: DnsRecord): Promise<DeleteRecordResult> {
|
|
224
|
+
try {
|
|
225
|
+
const rootDomain = this.getRootDomain(domain)
|
|
226
|
+
const subdomain = this.getSubdomain(record.name, rootDomain)
|
|
227
|
+
|
|
228
|
+
// Find the record to delete
|
|
229
|
+
const existing = await this.listRecords(domain, record.type)
|
|
230
|
+
|
|
231
|
+
if (!existing.success) {
|
|
232
|
+
return {
|
|
233
|
+
success: false,
|
|
234
|
+
message: 'Failed to list records',
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Find matching record
|
|
239
|
+
const matchingRecord = existing.records.find((r) => {
|
|
240
|
+
const existingSubdomain = this.getSubdomain(r.name, rootDomain)
|
|
241
|
+
return existingSubdomain === subdomain
|
|
242
|
+
&& r.type === record.type
|
|
243
|
+
&& r.content === record.content
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
if (!matchingRecord?.id) {
|
|
247
|
+
return {
|
|
248
|
+
success: false,
|
|
249
|
+
message: 'Record not found',
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await this.request(`/dns/delete/${rootDomain}/${matchingRecord.id}`)
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
success: true,
|
|
257
|
+
message: 'Record deleted successfully',
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async listRecords(domain: string, type?: DnsRecordType): Promise<ListRecordsResult> {
|
|
269
|
+
try {
|
|
270
|
+
const rootDomain = this.getRootDomain(domain)
|
|
271
|
+
|
|
272
|
+
let endpoint = `/dns/retrieve/${rootDomain}`
|
|
273
|
+
if (type) {
|
|
274
|
+
endpoint = `/dns/retrieveByNameType/${rootDomain}/${type}`
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const response = await this.request<PorkbunListRecordsResponse>(endpoint)
|
|
278
|
+
|
|
279
|
+
const records: DnsRecordResult[] = (response.records || []).map(r => ({
|
|
280
|
+
id: r.id,
|
|
281
|
+
name: r.name || rootDomain,
|
|
282
|
+
type: r.type as DnsRecordType,
|
|
283
|
+
content: r.content,
|
|
284
|
+
ttl: Number.parseInt(r.ttl, 10),
|
|
285
|
+
priority: r.prio ? Number.parseInt(r.prio, 10) : undefined,
|
|
286
|
+
}))
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
success: true,
|
|
290
|
+
records,
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
return {
|
|
295
|
+
success: false,
|
|
296
|
+
records: [],
|
|
297
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async canManageDomain(domain: string): Promise<boolean> {
|
|
303
|
+
try {
|
|
304
|
+
const rootDomain = this.getRootDomain(domain)
|
|
305
|
+
// Ping API to check if we can access this domain
|
|
306
|
+
await this.request(`/dns/retrieve/${rootDomain}`)
|
|
307
|
+
return true
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return false
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* List all domains managed by this Porkbun account
|
|
316
|
+
* Uses the domain list API endpoint
|
|
317
|
+
*/
|
|
318
|
+
async listDomains(): Promise<string[]> {
|
|
319
|
+
try {
|
|
320
|
+
// Porkbun's /domain/listAll endpoint returns domains with API access enabled
|
|
321
|
+
const response = await this.request<PorkbunApiResponse & { domains?: Array<{ domain: string }> }>(
|
|
322
|
+
'/domain/listAll',
|
|
323
|
+
)
|
|
324
|
+
return (response.domains || []).map(d => d.domain)
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return []
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get nameservers for a domain (Porkbun-specific)
|
|
333
|
+
*/
|
|
334
|
+
async getNameServers(domain: string): Promise<string[]> {
|
|
335
|
+
try {
|
|
336
|
+
const rootDomain = this.getRootDomain(domain)
|
|
337
|
+
const response = await this.request<PorkbunApiResponse & { ns?: string[] }>(
|
|
338
|
+
`/dns/getNS/${rootDomain}`,
|
|
339
|
+
)
|
|
340
|
+
return response.ns || []
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
return []
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Update nameservers for a domain (Porkbun-specific)
|
|
349
|
+
*/
|
|
350
|
+
async updateNameServers(domain: string, nameservers: string[]): Promise<boolean> {
|
|
351
|
+
try {
|
|
352
|
+
const rootDomain = this.getRootDomain(domain)
|
|
353
|
+
await this.request(`/dns/updateNS/${rootDomain}`, {
|
|
354
|
+
ns: nameservers,
|
|
355
|
+
})
|
|
356
|
+
return true
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
return false
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|