@stacksjs/ts-cloud 0.9.3 → 0.10.0

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 (255) hide show
  1. package/dist/aws/index.js +1 -269
  2. package/dist/bin/chunk-08c0pzkb.js +1 -0
  3. package/dist/bin/chunk-12210qvj.js +1 -0
  4. package/dist/bin/chunk-1v6bt68w.js +6 -0
  5. package/dist/bin/chunk-35ttwn26.js +70 -0
  6. package/dist/bin/chunk-3r3be05z.js +1 -0
  7. package/dist/bin/chunk-41yqpdv7.js +2 -0
  8. package/dist/bin/chunk-5rv31chh.js +1 -0
  9. package/dist/bin/chunk-6eat8ezk.js +2 -0
  10. package/dist/bin/chunk-6gktf2m2.js +1 -0
  11. package/dist/bin/chunk-6zjy949y.js +141 -0
  12. package/dist/bin/chunk-7wzdaqh6.js +2 -0
  13. package/dist/bin/chunk-80ar3aty.js +1 -0
  14. package/dist/bin/chunk-8rwhsp5b.js +2 -0
  15. package/dist/bin/chunk-936vp7dx.js +44 -0
  16. package/dist/bin/chunk-9px3rg4a.js +4 -0
  17. package/dist/bin/chunk-anm3ae6z.js +7364 -0
  18. package/dist/bin/{chunk-ynjvbphf.js → chunk-axvr7dwa.js} +6 -6
  19. package/dist/bin/chunk-b0hj9m5e.js +2 -0
  20. package/dist/bin/{chunk-mh0kdzqp.js → chunk-b2ymh8k0.js} +1 -1
  21. package/dist/bin/chunk-b35989e3.js +50 -0
  22. package/dist/bin/chunk-c2b6nk0q.js +37 -0
  23. package/dist/bin/chunk-ckxpgwxw.js +3 -0
  24. package/dist/bin/chunk-d6zzgvtn.js +92 -0
  25. package/dist/bin/chunk-da8zpf82.js +1 -0
  26. package/dist/bin/chunk-ddmjjstg.js +1 -0
  27. package/dist/bin/chunk-dhhz4zyj.js +1 -0
  28. package/dist/bin/chunk-dj9a67c3.js +9 -0
  29. package/dist/bin/chunk-ds1z880f.js +1 -0
  30. package/dist/bin/chunk-dvkj4k6h.js +111 -0
  31. package/dist/bin/chunk-eh40fpvj.js +1 -0
  32. package/dist/bin/chunk-ey9fagrr.js +6 -0
  33. package/dist/bin/chunk-ezftw2q4.js +1 -0
  34. package/dist/bin/chunk-g6a4n3rh.js +1 -0
  35. package/dist/bin/chunk-g90nqfgv.js +585 -0
  36. package/dist/bin/chunk-gs40j75w.js +1 -0
  37. package/dist/bin/chunk-jjjts1vt.js +1 -0
  38. package/dist/bin/{chunk-ry030a6b.js → chunk-jnsbzqzz.js} +30 -30
  39. package/dist/bin/chunk-jnx4jgw8.js +1 -0
  40. package/dist/bin/chunk-jt1tndm2.js +3 -0
  41. package/dist/bin/chunk-kybdb0ws.js +2 -0
  42. package/dist/bin/chunk-m3nqw76h.js +3 -0
  43. package/dist/bin/chunk-mn6j22zn.js +6 -0
  44. package/dist/bin/chunk-nb0yjkv5.js +2 -0
  45. package/dist/bin/chunk-pjpa4x52.js +2 -0
  46. package/dist/bin/chunk-pkgkfe6s.js +2 -0
  47. package/dist/bin/chunk-pnz5hk8n.js +60 -0
  48. package/dist/bin/chunk-q46zr2tv.js +1 -0
  49. package/dist/bin/chunk-qtdngvs8.js +1 -0
  50. package/dist/bin/chunk-sbejksrh.js +2 -0
  51. package/dist/bin/chunk-svyd4v6c.js +34 -0
  52. package/dist/bin/chunk-tna7ay8d.js +1 -0
  53. package/dist/bin/chunk-tp4c2mv1.js +1 -0
  54. package/dist/bin/chunk-w5d6tr2x.js +1 -0
  55. package/dist/bin/chunk-wewcjam5.js +1 -0
  56. package/dist/bin/chunk-xv4bscrm.js +1 -0
  57. package/dist/bin/chunk-y9eyc5pw.js +1 -0
  58. package/dist/bin/chunk-y9f8649w.js +512 -0
  59. package/dist/bin/cli.js +428 -418
  60. package/dist/bin/dashboard-server.js +1 -1
  61. package/dist/chunk-06jk7ba9.js +7355 -0
  62. package/dist/chunk-0e16qt6k.js +5 -0
  63. package/dist/chunk-13hxhzaz.js +34 -0
  64. package/dist/chunk-2p89vt3b.js +2 -0
  65. package/dist/chunk-77t2ctwn.js +2 -0
  66. package/dist/chunk-88z37ey4.js +114 -0
  67. package/dist/chunk-8c4715hz.js +2 -0
  68. package/dist/chunk-9a22225r.js +60 -0
  69. package/dist/chunk-as6by62d.js +1 -0
  70. package/dist/chunk-cd0zpxhf.js +44 -0
  71. package/dist/chunk-cp54a0qn.js +6 -0
  72. package/dist/chunk-d67xbk4v.js +141 -0
  73. package/dist/chunk-dckz9xve.js +2 -0
  74. package/dist/chunk-e1666vjv.js +2 -0
  75. package/dist/chunk-hhdx5k96.js +442 -0
  76. package/dist/chunk-kv2ztkp4.js +1617 -0
  77. package/dist/chunk-nrz6zypg.js +1 -0
  78. package/dist/chunk-q816js1y.js +1 -0
  79. package/dist/chunk-qh9ep11z.js +704 -0
  80. package/dist/chunk-rg720cqg.js +1 -0
  81. package/dist/chunk-snak8s7m.js +92 -0
  82. package/dist/chunk-vq737g82.js +1 -0
  83. package/dist/chunk-xd6w0c11.js +50 -0
  84. package/dist/chunk-xtwjt370.js +37 -0
  85. package/dist/chunk-y26yh5zp.js +1 -0
  86. package/dist/chunk-zmxffmmq.js +4 -0
  87. package/dist/deploy/attach-credentials.d.ts +4 -8
  88. package/dist/deploy/index.js +1 -168
  89. package/dist/dns/cloudflare.d.ts +27 -0
  90. package/dist/dns/godaddy.d.ts +18 -1
  91. package/dist/dns/index.d.ts +2 -0
  92. package/dist/dns/index.js +1 -41
  93. package/dist/dns/zone-migration.d.ts +92 -0
  94. package/dist/dns/zone-migration.test.d.ts +1 -0
  95. package/dist/drivers/hetzner/driver.d.ts +8 -1
  96. package/dist/drivers/index.js +1 -154
  97. package/dist/drivers/shared/compute-provision.d.ts +14 -2
  98. package/dist/drivers/shared/db-provision.d.ts +3 -2
  99. package/dist/drivers/shared/mail-provision.d.ts +116 -0
  100. package/dist/drivers/shared/mail-provision.test.d.ts +1 -0
  101. package/dist/drivers/shared/managed-services-probe.d.ts +47 -0
  102. package/dist/drivers/shared/managed-services-probe.test.d.ts +1 -0
  103. package/dist/drivers/shared/package-manager.d.ts +1 -0
  104. package/dist/drivers/shared/remote-failure.d.ts +21 -0
  105. package/dist/drivers/shared/remote-failure.test.d.ts +1 -0
  106. package/dist/drivers/shared/sqlite-shared-path.d.ts +50 -0
  107. package/dist/drivers/shared/sqlite-shared-path.test.d.ts +1 -0
  108. package/dist/index.d.ts +2 -0
  109. package/dist/index.js +100 -6129
  110. package/dist/operations/plan.d.ts +136 -0
  111. package/dist/operations/plan.test.d.ts +1 -0
  112. package/dist/operations/server-rename.d.ts +84 -0
  113. package/dist/operations/server-rename.test.d.ts +1 -0
  114. package/dist/operations/site-move.d.ts +263 -0
  115. package/dist/operations/site-move.test.d.ts +1 -0
  116. package/dist/protection/index.js +1 -89
  117. package/dist/push/index.js +1 -509
  118. package/dist/spend/index.js +1 -173
  119. package/dist/ui/404.html +1 -1
  120. package/dist/ui/accept-invitation.html +1 -1
  121. package/dist/ui/access-denied.html +1 -1
  122. package/dist/ui/account/automation.html +5 -5
  123. package/dist/ui/account/security.html +2 -2
  124. package/dist/ui/applications/compose.html +6 -6
  125. package/dist/ui/applications/new.html +2 -2
  126. package/dist/ui/data/backups.html +80 -80
  127. package/dist/ui/data/services.html +78 -78
  128. package/dist/ui/data/volumes.html +50 -50
  129. package/dist/ui/index.html +50 -50
  130. package/dist/ui/infrastructure/topology.html +118 -118
  131. package/dist/ui/integrations.html +1 -1
  132. package/dist/ui/operations/alerts.html +47 -47
  133. package/dist/ui/operations/configuration.html +5 -5
  134. package/dist/ui/operations/jobs.html +76 -76
  135. package/dist/ui/operations/maintenance.html +6 -6
  136. package/dist/ui/operations/observability.html +82 -82
  137. package/dist/ui/operations/previews.html +5 -5
  138. package/dist/ui/operations/queue.html +6 -6
  139. package/dist/ui/operations/regions.html +6 -6
  140. package/dist/ui/operations/releases.html +6 -6
  141. package/dist/ui/operations/spend.html +54 -54
  142. package/dist/ui/operations/workloads.html +104 -104
  143. package/dist/ui/security.html +2 -2
  144. package/dist/ui/server/actions.html +56 -56
  145. package/dist/ui/server/activity.html +1 -1
  146. package/dist/ui/server/capacity.html +6 -6
  147. package/dist/ui/server/database.html +106 -106
  148. package/dist/ui/server/deployments.html +50 -50
  149. package/dist/ui/server/diagnostics.html +2 -2
  150. package/dist/ui/server/firewall.html +76 -76
  151. package/dist/ui/server/fleet.html +34 -34
  152. package/dist/ui/server/logs.html +24 -24
  153. package/dist/ui/server/metrics.html +112 -112
  154. package/dist/ui/server/security.html +1 -1
  155. package/dist/ui/server/services.html +27 -27
  156. package/dist/ui/server/sites.html +120 -120
  157. package/dist/ui/server/ssh-keys.html +51 -51
  158. package/dist/ui/server/team.html +144 -144
  159. package/dist/ui/server/terminal.html +15 -15
  160. package/dist/ui/serverless/alarms.html +52 -52
  161. package/dist/ui/serverless/assets.html +27 -27
  162. package/dist/ui/serverless/cost.html +2 -2
  163. package/dist/ui/serverless/data.html +36 -36
  164. package/dist/ui/serverless/deployments.html +27 -27
  165. package/dist/ui/serverless/firewall.html +1 -1
  166. package/dist/ui/serverless/functions.html +40 -40
  167. package/dist/ui/serverless/logs.html +24 -24
  168. package/dist/ui/serverless/metrics.html +2 -2
  169. package/dist/ui/serverless/queues.html +47 -47
  170. package/dist/ui/serverless/secrets.html +52 -52
  171. package/dist/ui/serverless/traces.html +6 -6
  172. package/dist/ui/serverless.html +54 -54
  173. package/dist/ui-src/src/charts.ts +17499 -12662
  174. package/dist/ui-src/src/topology.ts +2 -2
  175. package/package.json +3 -3
  176. package/dist/bin/chunk-09nwf2gy.js +0 -34
  177. package/dist/bin/chunk-14cwr6eh.js +0 -70
  178. package/dist/bin/chunk-271gk1n2.js +0 -6
  179. package/dist/bin/chunk-38pq971s.js +0 -2
  180. package/dist/bin/chunk-3gykfyq4.js +0 -37
  181. package/dist/bin/chunk-3r4fb59r.js +0 -3
  182. package/dist/bin/chunk-41x17hw1.js +0 -1
  183. package/dist/bin/chunk-4vrqm9h0.js +0 -2
  184. package/dist/bin/chunk-57y1zxbf.js +0 -3
  185. package/dist/bin/chunk-598yf4bs.js +0 -1
  186. package/dist/bin/chunk-8v7ddyvf.js +0 -1
  187. package/dist/bin/chunk-92mea34a.js +0 -1
  188. package/dist/bin/chunk-9pvsff8g.js +0 -1
  189. package/dist/bin/chunk-9v39615t.js +0 -1
  190. package/dist/bin/chunk-avm7tnwe.js +0 -1
  191. package/dist/bin/chunk-ax9mr6wm.js +0 -44
  192. package/dist/bin/chunk-axhnkn5g.js +0 -2
  193. package/dist/bin/chunk-b5gd39s8.js +0 -512
  194. package/dist/bin/chunk-b9qbp8j1.js +0 -1
  195. package/dist/bin/chunk-bz828v8q.js +0 -3
  196. package/dist/bin/chunk-c1vcdgvr.js +0 -1
  197. package/dist/bin/chunk-ctd2vg9w.js +0 -2
  198. package/dist/bin/chunk-dbgffky8.js +0 -1
  199. package/dist/bin/chunk-dwr30vbv.js +0 -1
  200. package/dist/bin/chunk-e3xrwh9z.js +0 -1
  201. package/dist/bin/chunk-fhc6ntvd.js +0 -2
  202. package/dist/bin/chunk-fpm59y25.js +0 -6
  203. package/dist/bin/chunk-fvbxvq6b.js +0 -1
  204. package/dist/bin/chunk-fy8j3y8p.js +0 -92
  205. package/dist/bin/chunk-fz21b57w.js +0 -1
  206. package/dist/bin/chunk-gavvf0pg.js +0 -141
  207. package/dist/bin/chunk-ggn7ax8p.js +0 -4
  208. package/dist/bin/chunk-gpe2prkm.js +0 -1
  209. package/dist/bin/chunk-gqf6jsmy.js +0 -1
  210. package/dist/bin/chunk-h5pa5men.js +0 -2
  211. package/dist/bin/chunk-hm08xdxf.js +0 -2
  212. package/dist/bin/chunk-hzgz13nf.js +0 -1
  213. package/dist/bin/chunk-hzqw8xm5.js +0 -111
  214. package/dist/bin/chunk-j703btc5.js +0 -50
  215. package/dist/bin/chunk-jpjamjey.js +0 -6935
  216. package/dist/bin/chunk-m2zwtapr.js +0 -1
  217. package/dist/bin/chunk-m669wtgr.js +0 -1
  218. package/dist/bin/chunk-mr0atd2s.js +0 -2
  219. package/dist/bin/chunk-ngcyftm2.js +0 -1
  220. package/dist/bin/chunk-p66ptpmr.js +0 -2
  221. package/dist/bin/chunk-p8q9bq9g.js +0 -1
  222. package/dist/bin/chunk-r3kv0vm9.js +0 -6
  223. package/dist/bin/chunk-r9hjwcj9.js +0 -6
  224. package/dist/bin/chunk-rtczra13.js +0 -1
  225. package/dist/bin/chunk-rytk89az.js +0 -60
  226. package/dist/bin/chunk-sgyffpgm.js +0 -581
  227. package/dist/bin/chunk-vek8a0w6.js +0 -1
  228. package/dist/bin/chunk-waayf9ay.js +0 -2
  229. package/dist/bin/chunk-yv9sth30.js +0 -1
  230. package/dist/chunk-01d86gt1.js +0 -1324
  231. package/dist/chunk-050r7ycy.js +0 -46516
  232. package/dist/chunk-1c8dbv57.js +0 -7278
  233. package/dist/chunk-29rse4p1.js +0 -12203
  234. package/dist/chunk-32e7ya18.js +0 -642
  235. package/dist/chunk-4cjrg98a.js +0 -597
  236. package/dist/chunk-50jpda9q.js +0 -371
  237. package/dist/chunk-6a1t7fnq.js +0 -12121
  238. package/dist/chunk-728jvrr0.js +0 -1701
  239. package/dist/chunk-7m60qnc8.js +0 -1762
  240. package/dist/chunk-aa2dkn7b.js +0 -205
  241. package/dist/chunk-d48vtdj4.js +0 -433
  242. package/dist/chunk-f55nkpvb.js +0 -8
  243. package/dist/chunk-fgk8cqew.js +0 -152
  244. package/dist/chunk-he9a874b.js +0 -769
  245. package/dist/chunk-k2yyatfw.js +0 -10
  246. package/dist/chunk-k4px0bnv.js +0 -23
  247. package/dist/chunk-nsgqsmrh.js +0 -1779
  248. package/dist/chunk-q0st3se8.js +0 -42033
  249. package/dist/chunk-qr803fye.js +0 -2122
  250. package/dist/chunk-rds0sy87.js +0 -1771
  251. package/dist/chunk-ttz2ra5j.js +0 -12
  252. package/dist/chunk-v0bahtg2.js +0 -4
  253. package/dist/chunk-va2yd85b.js +0 -200
  254. package/dist/chunk-x9s0cqfx.js +0 -13
  255. package/dist/chunk-zqtpg06c.js +0 -1752
@@ -1,2122 +0,0 @@
1
- import {
2
- Route53Client
3
- } from "./chunk-32e7ya18.js";
4
- import {
5
- AWSClient
6
- } from "./chunk-zqtpg06c.js";
7
-
8
- // src/dns/types.ts
9
- var PROXIABLE_RECORD_TYPES = new Set(["A", "AAAA", "CNAME"]);
10
- // src/dns/porkbun.ts
11
- var PORKBUN_API_URL = "https://api.porkbun.com/api/json/v3";
12
- var PORKBUN_MAX_ATTEMPTS = 8;
13
- var PORKBUN_REQUEST_TIMEOUT_MS = 15000;
14
- function isRetryablePorkbunResponse(status, message = "") {
15
- return status === 408 || status === 409 || status === 425 || status === 429 || status >= 500 || /rate limit|temporar|timed? ?out|try again/i.test(message);
16
- }
17
- async function waitForPorkbunRetry(attempt, response) {
18
- const retryAfter = response?.headers.get("retry-after");
19
- const retryAfterMs = retryAfter === null || retryAfter === undefined ? undefined : Number(retryAfter) * 1000;
20
- const delay = Number.isFinite(retryAfterMs) ? Math.max(0, retryAfterMs) : Math.min(1000 * 2 ** (attempt - 1), 30000);
21
- await new Promise((resolve) => setTimeout(resolve, delay));
22
- }
23
-
24
- class PorkbunProvider {
25
- name = "porkbun";
26
- apiKey;
27
- secretKey;
28
- requestTimeoutMs;
29
- constructor(apiKey, secretKey, requestTimeoutMs = PORKBUN_REQUEST_TIMEOUT_MS) {
30
- this.apiKey = apiKey;
31
- this.secretKey = secretKey;
32
- this.requestTimeoutMs = requestTimeoutMs;
33
- }
34
- async request(endpoint, additionalBody = {}) {
35
- let lastError;
36
- for (let attempt = 1;attempt <= PORKBUN_MAX_ATTEMPTS; attempt += 1) {
37
- let response;
38
- const controller = new AbortController;
39
- const timeout = setTimeout(() => {
40
- controller.abort(new Error(`Porkbun API request timed out after ${this.requestTimeoutMs}ms`));
41
- }, this.requestTimeoutMs);
42
- try {
43
- response = await fetch(`${PORKBUN_API_URL}${endpoint}`, {
44
- method: "POST",
45
- headers: {
46
- "Content-Type": "application/json"
47
- },
48
- body: JSON.stringify({
49
- apikey: this.apiKey,
50
- secretapikey: this.secretKey,
51
- ...additionalBody
52
- }),
53
- signal: controller.signal
54
- });
55
- } catch (error) {
56
- lastError = error;
57
- if (attempt === PORKBUN_MAX_ATTEMPTS)
58
- throw error;
59
- await waitForPorkbunRetry(attempt);
60
- continue;
61
- } finally {
62
- clearTimeout(timeout);
63
- }
64
- if (!response.ok) {
65
- lastError = new Error(`Porkbun API error: ${response.status} ${response.statusText}`);
66
- if (!isRetryablePorkbunResponse(response.status) || attempt === PORKBUN_MAX_ATTEMPTS)
67
- throw lastError;
68
- await waitForPorkbunRetry(attempt, response);
69
- continue;
70
- }
71
- const data = await response.json();
72
- if (data.status === "ERROR") {
73
- lastError = new Error(`Porkbun API error: ${data.message || "Unknown error"}`);
74
- if (!isRetryablePorkbunResponse(0, data.message) || attempt === PORKBUN_MAX_ATTEMPTS)
75
- throw lastError;
76
- await waitForPorkbunRetry(attempt, response);
77
- continue;
78
- }
79
- return data;
80
- }
81
- throw lastError;
82
- }
83
- getSubdomain(recordName, domain) {
84
- const cleanName = recordName.replace(/\.$/, "");
85
- const cleanDomain = domain.replace(/\.$/, "");
86
- if (cleanName === cleanDomain) {
87
- return "";
88
- }
89
- if (cleanName.endsWith(`.${cleanDomain}`)) {
90
- return cleanName.slice(0, -(cleanDomain.length + 1));
91
- }
92
- return cleanName;
93
- }
94
- getRootDomain(domain) {
95
- const parts = domain.replace(/\.$/, "").split(".");
96
- if (parts.length >= 2) {
97
- return parts.slice(-2).join(".");
98
- }
99
- return domain;
100
- }
101
- static ADDRESS_TYPES = new Set(["A", "AAAA"]);
102
- async clearConflictingAliases(domain, record) {
103
- if (!PorkbunProvider.ADDRESS_TYPES.has(record.type))
104
- return [];
105
- const rootDomain = this.getRootDomain(domain);
106
- const subdomain = this.getSubdomain(record.name, rootDomain);
107
- const listed = await this.listRecords(domain);
108
- if (!listed.success)
109
- return [`could not list records to clear ALIAS/CNAME conflicts: ${listed.message ?? "unknown provider error"}`];
110
- const warnings = [];
111
- for (const existing of listed.records) {
112
- if (existing.type !== "ALIAS" && existing.type !== "CNAME")
113
- continue;
114
- if (this.getSubdomain(existing.name, rootDomain) !== subdomain)
115
- continue;
116
- const removed = await this.deleteRecord(domain, existing);
117
- if (!removed.success)
118
- warnings.push(`could not remove conflicting ${existing.type} on ${existing.name}: ${removed.message ?? "unknown provider error"}`);
119
- }
120
- return warnings;
121
- }
122
- async addressRecordExists(domain, record) {
123
- const rootDomain = this.getRootDomain(domain);
124
- const subdomain = this.getSubdomain(record.name, rootDomain);
125
- const listed = await this.listRecords(domain);
126
- if (!listed.success)
127
- return true;
128
- return listed.records.some((r) => r.type === record.type && this.getSubdomain(r.name, rootDomain) === subdomain && r.content === record.content);
129
- }
130
- async createRecord(domain, record) {
131
- try {
132
- const rootDomain = this.getRootDomain(domain);
133
- const subdomain = this.getSubdomain(record.name, rootDomain);
134
- const body = {
135
- type: record.type,
136
- content: record.content,
137
- ttl: String(record.ttl || 600)
138
- };
139
- if (subdomain) {
140
- body.name = subdomain;
141
- }
142
- if ((record.type === "MX" || record.type === "SRV") && record.priority !== undefined) {
143
- body.prio = String(record.priority);
144
- }
145
- if (record.type === "SRV" && record.weight !== undefined && record.port !== undefined) {
146
- body.content = `${record.weight} ${record.port} ${record.content}`;
147
- }
148
- const conflicts = await this.clearConflictingAliases(domain, record);
149
- const response = await this.request(`/dns/create/${rootDomain}`, body);
150
- if (PorkbunProvider.ADDRESS_TYPES.has(record.type) && !await this.addressRecordExists(domain, record)) {
151
- return {
152
- success: false,
153
- message: `Porkbun reported success but no ${record.type} record for ${record.name} exists${conflicts.length > 0 ? ` (${conflicts.join("; ")})` : ""}. A conflicting ALIAS/CNAME on the same name silently voids the write.`
154
- };
155
- }
156
- return {
157
- success: true,
158
- id: response.id?.toString(),
159
- message: "Record created successfully"
160
- };
161
- } catch (error) {
162
- return {
163
- success: false,
164
- message: error instanceof Error ? error.message : "Unknown error"
165
- };
166
- }
167
- }
168
- async upsertRecord(domain, record) {
169
- try {
170
- const rootDomain = this.getRootDomain(domain);
171
- const subdomain = this.getSubdomain(record.name, rootDomain);
172
- const existing = await this.listRecords(domain, record.type);
173
- if (existing.success) {
174
- const matchingRecord = existing.records.find((r) => {
175
- const existingSubdomain = this.getSubdomain(r.name, rootDomain);
176
- return existingSubdomain === subdomain && r.type === record.type;
177
- });
178
- if (matchingRecord?.id) {
179
- const body = {
180
- type: record.type,
181
- content: record.content,
182
- ttl: String(record.ttl || 600)
183
- };
184
- if (subdomain) {
185
- body.name = subdomain;
186
- }
187
- if ((record.type === "MX" || record.type === "SRV") && record.priority !== undefined) {
188
- body.prio = String(record.priority);
189
- }
190
- if (record.type === "SRV" && record.weight !== undefined && record.port !== undefined) {
191
- body.content = `${record.weight} ${record.port} ${record.content}`;
192
- }
193
- await this.request(`/dns/edit/${rootDomain}/${matchingRecord.id}`, body);
194
- return {
195
- success: true,
196
- id: matchingRecord.id,
197
- message: "Record updated successfully"
198
- };
199
- }
200
- }
201
- return this.createRecord(domain, record);
202
- } catch (error) {
203
- return this.createRecord(domain, record);
204
- }
205
- }
206
- async deleteRecord(domain, record) {
207
- try {
208
- const rootDomain = this.getRootDomain(domain);
209
- if ("id" in record && record.id) {
210
- await this.request(`/dns/delete/${rootDomain}/${record.id}`);
211
- return {
212
- success: true,
213
- message: "Record deleted successfully"
214
- };
215
- }
216
- const subdomain = this.getSubdomain(record.name, rootDomain);
217
- const existing = await this.listRecords(domain);
218
- if (!existing.success) {
219
- return {
220
- success: false,
221
- message: "Failed to list records"
222
- };
223
- }
224
- const matchingRecord = existing.records.find((r) => {
225
- const existingSubdomain = this.getSubdomain(r.name, rootDomain);
226
- return existingSubdomain === subdomain && r.type === record.type && r.content === record.content;
227
- });
228
- if (!matchingRecord?.id) {
229
- return {
230
- success: false,
231
- message: "Record not found"
232
- };
233
- }
234
- await this.request(`/dns/delete/${rootDomain}/${matchingRecord.id}`);
235
- return {
236
- success: true,
237
- message: "Record deleted successfully"
238
- };
239
- } catch (error) {
240
- return {
241
- success: false,
242
- message: error instanceof Error ? error.message : "Unknown error"
243
- };
244
- }
245
- }
246
- async listRecords(domain, type) {
247
- try {
248
- const rootDomain = this.getRootDomain(domain);
249
- let endpoint = `/dns/retrieve/${rootDomain}`;
250
- if (type) {
251
- endpoint = `/dns/retrieveByNameType/${rootDomain}/${type}`;
252
- }
253
- const response = await this.request(endpoint);
254
- const records = (response.records || []).map((r) => ({
255
- id: r.id,
256
- name: r.name || rootDomain,
257
- type: r.type,
258
- content: r.content,
259
- ttl: Number.parseInt(r.ttl, 10),
260
- priority: r.prio ? Number.parseInt(r.prio, 10) : undefined
261
- }));
262
- return {
263
- success: true,
264
- records
265
- };
266
- } catch (error) {
267
- return {
268
- success: false,
269
- records: [],
270
- message: error instanceof Error ? error.message : "Unknown error"
271
- };
272
- }
273
- }
274
- async canManageDomain(domain) {
275
- try {
276
- const rootDomain = this.getRootDomain(domain);
277
- await this.request(`/dns/retrieve/${rootDomain}`);
278
- return true;
279
- } catch {
280
- return false;
281
- }
282
- }
283
- async listDomains() {
284
- try {
285
- const response = await this.request("/domain/listAll");
286
- return (response.domains || []).map((d) => d.domain);
287
- } catch {
288
- return [];
289
- }
290
- }
291
- async getNameServers(domain) {
292
- try {
293
- const rootDomain = this.getRootDomain(domain);
294
- const response = await this.request(`/dns/getNS/${rootDomain}`);
295
- return response.ns || [];
296
- } catch {
297
- return [];
298
- }
299
- }
300
- async updateNameServers(domain, nameservers) {
301
- try {
302
- const rootDomain = this.getRootDomain(domain);
303
- await this.request(`/dns/updateNS/${rootDomain}`, {
304
- ns: nameservers
305
- });
306
- return true;
307
- } catch {
308
- return false;
309
- }
310
- }
311
- }
312
- // src/dns/godaddy.ts
313
- var GODADDY_API_URL = "https://api.godaddy.com";
314
- var GODADDY_OTE_API_URL = "https://api.ote-godaddy.com";
315
-
316
- class GoDaddyProvider {
317
- name = "godaddy";
318
- apiKey;
319
- apiSecret;
320
- baseUrl;
321
- constructor(apiKey, apiSecret, environment = "production") {
322
- this.apiKey = apiKey;
323
- this.apiSecret = apiSecret;
324
- this.baseUrl = environment === "ote" ? GODADDY_OTE_API_URL : GODADDY_API_URL;
325
- }
326
- async request(method, endpoint, body) {
327
- const url = `${this.baseUrl}${endpoint}`;
328
- const headers = {
329
- Authorization: `sso-key ${this.apiKey}:${this.apiSecret}`,
330
- "Content-Type": "application/json",
331
- Accept: "application/json"
332
- };
333
- const options = {
334
- method,
335
- headers
336
- };
337
- if (body) {
338
- options.body = JSON.stringify(body);
339
- }
340
- const response = await fetch(url, options);
341
- if (response.status === 204) {
342
- return {};
343
- }
344
- if (response.ok) {
345
- const text = await response.text();
346
- if (text) {
347
- return JSON.parse(text);
348
- }
349
- return {};
350
- }
351
- let errorMessage = `GoDaddy API error: ${response.status} ${response.statusText}`;
352
- try {
353
- const errorData = await response.json();
354
- if (errorData.message) {
355
- errorMessage = `GoDaddy API error: ${errorData.message}`;
356
- }
357
- if (errorData.fields) {
358
- errorMessage += ` - Fields: ${JSON.stringify(errorData.fields)}`;
359
- }
360
- } catch {}
361
- throw new Error(errorMessage);
362
- }
363
- getSubdomain(recordName, domain) {
364
- const cleanName = recordName.replace(/\.$/, "");
365
- const cleanDomain = domain.replace(/\.$/, "");
366
- if (cleanName === cleanDomain) {
367
- return "@";
368
- }
369
- if (cleanName.endsWith(`.${cleanDomain}`)) {
370
- return cleanName.slice(0, -(cleanDomain.length + 1));
371
- }
372
- return cleanName;
373
- }
374
- getRootDomain(domain) {
375
- const parts = domain.replace(/\.$/, "").split(".");
376
- if (parts.length >= 2) {
377
- return parts.slice(-2).join(".");
378
- }
379
- return domain;
380
- }
381
- toGoDaddyRecord(record, domain) {
382
- const rootDomain = this.getRootDomain(domain);
383
- const subdomain = this.getSubdomain(record.name, rootDomain);
384
- const gdRecord = {
385
- type: record.type,
386
- name: subdomain,
387
- data: record.content,
388
- ttl: record.ttl || 600
389
- };
390
- if (record.type === "MX" && record.priority !== undefined) {
391
- gdRecord.priority = record.priority;
392
- }
393
- return gdRecord;
394
- }
395
- fromGoDaddyRecord(record, domain) {
396
- const rootDomain = this.getRootDomain(domain);
397
- let name = record.name;
398
- if (name === "@") {
399
- name = rootDomain;
400
- } else if (!name.endsWith(rootDomain)) {
401
- name = `${name}.${rootDomain}`;
402
- }
403
- return {
404
- name,
405
- type: record.type,
406
- content: record.data,
407
- ttl: record.ttl,
408
- priority: record.priority
409
- };
410
- }
411
- async createRecord(domain, record) {
412
- try {
413
- const rootDomain = this.getRootDomain(domain);
414
- const gdRecord = this.toGoDaddyRecord(record, domain);
415
- await this.request("PATCH", `/v1/domains/${rootDomain}/records`, [gdRecord]);
416
- return {
417
- success: true,
418
- message: "Record created successfully"
419
- };
420
- } catch (error) {
421
- return {
422
- success: false,
423
- message: error instanceof Error ? error.message : "Unknown error"
424
- };
425
- }
426
- }
427
- async upsertRecord(domain, record) {
428
- try {
429
- const rootDomain = this.getRootDomain(domain);
430
- const gdRecord = this.toGoDaddyRecord(record, domain);
431
- await this.request("PUT", `/v1/domains/${rootDomain}/records/${record.type}/${gdRecord.name}`, [gdRecord]);
432
- return {
433
- success: true,
434
- message: "Record upserted successfully"
435
- };
436
- } catch (error) {
437
- return this.createRecord(domain, record);
438
- }
439
- }
440
- async deleteRecord(domain, record) {
441
- try {
442
- const rootDomain = this.getRootDomain(domain);
443
- const subdomain = this.getSubdomain(record.name, rootDomain);
444
- const existingRecords = await this.request("GET", `/v1/domains/${rootDomain}/records/${record.type}/${subdomain}`);
445
- const remainingRecords = existingRecords.filter((r) => r.data !== record.content);
446
- if (remainingRecords.length === existingRecords.length) {
447
- return {
448
- success: false,
449
- message: "Record not found"
450
- };
451
- }
452
- if (remainingRecords.length === 0) {
453
- try {
454
- await this.request("DELETE", `/v1/domains/${rootDomain}/records/${record.type}/${subdomain}`);
455
- } catch {}
456
- } else {
457
- await this.request("PUT", `/v1/domains/${rootDomain}/records/${record.type}/${subdomain}`, remainingRecords);
458
- }
459
- return {
460
- success: true,
461
- message: "Record deleted successfully"
462
- };
463
- } catch (error) {
464
- return {
465
- success: false,
466
- message: error instanceof Error ? error.message : "Unknown error"
467
- };
468
- }
469
- }
470
- async listRecords(domain, type) {
471
- try {
472
- const rootDomain = this.getRootDomain(domain);
473
- let endpoint = `/v1/domains/${rootDomain}/records`;
474
- if (type) {
475
- endpoint = `/v1/domains/${rootDomain}/records/${type}`;
476
- }
477
- const records = await this.request("GET", endpoint);
478
- return {
479
- success: true,
480
- records: records.map((r) => this.fromGoDaddyRecord(r, domain))
481
- };
482
- } catch (error) {
483
- return {
484
- success: false,
485
- records: [],
486
- message: error instanceof Error ? error.message : "Unknown error"
487
- };
488
- }
489
- }
490
- async canManageDomain(domain) {
491
- try {
492
- const rootDomain = this.getRootDomain(domain);
493
- await this.request("GET", `/v1/domains/${rootDomain}`);
494
- return true;
495
- } catch {
496
- return false;
497
- }
498
- }
499
- async listDomains() {
500
- try {
501
- const response = await this.request("GET", "/v1/domains");
502
- return response.map((d) => d.domain);
503
- } catch {
504
- return [];
505
- }
506
- }
507
- async getDomainDetails(domain) {
508
- try {
509
- const rootDomain = this.getRootDomain(domain);
510
- const details = await this.request("GET", `/v1/domains/${rootDomain}`);
511
- return {
512
- domain: details.domain,
513
- status: details.status,
514
- nameServers: details.nameServers,
515
- expires: details.expires
516
- };
517
- } catch {
518
- return null;
519
- }
520
- }
521
- async updateNameServers(domain, nameservers) {
522
- try {
523
- const rootDomain = this.getRootDomain(domain);
524
- await this.request("PUT", `/v1/domains/${rootDomain}/records/NS`, nameservers.map((ns) => ({
525
- type: "NS",
526
- name: "@",
527
- data: ns,
528
- ttl: 3600
529
- })));
530
- return true;
531
- } catch {
532
- return false;
533
- }
534
- }
535
- async checkDomainAvailability(domain) {
536
- try {
537
- const result = await this.request("GET", `/v1/domains/available?domain=${encodeURIComponent(domain)}`);
538
- return {
539
- available: result.available,
540
- price: result.price,
541
- currency: result.currency
542
- };
543
- } catch {
544
- return { available: false };
545
- }
546
- }
547
- }
548
- // src/dns/cloudflare.ts
549
- var CLOUDFLARE_API_URL = "https://api.cloudflare.com/client/v4";
550
- var CLOUDFLARE_MANAGED_RULE_PREFIX = "[ts-cloud]";
551
-
552
- class CloudflareProvider {
553
- name = "cloudflare";
554
- apiToken;
555
- zoneCache = new Map;
556
- configuredZoneId;
557
- accountId;
558
- configuredZoneName;
559
- constructor(apiToken, options = {}) {
560
- this.apiToken = apiToken;
561
- this.configuredZoneId = options.zoneId;
562
- this.accountId = options.accountId;
563
- }
564
- async request(method, endpoint, body) {
565
- const url = `${CLOUDFLARE_API_URL}${endpoint}`;
566
- const headers = {
567
- Authorization: `Bearer ${this.apiToken}`,
568
- "Content-Type": "application/json"
569
- };
570
- const options = {
571
- method,
572
- headers
573
- };
574
- if (body) {
575
- options.body = JSON.stringify(body);
576
- }
577
- const response = await fetch(url, options);
578
- const data = await response.json();
579
- if (!data.success) {
580
- const errorMessages = data.errors.map((e) => e.message).join(", ");
581
- throw new Error(`Cloudflare API error: ${errorMessages}`);
582
- }
583
- return data;
584
- }
585
- getRootDomain(domain) {
586
- const parts = domain.replace(/\.$/, "").split(".");
587
- if (parts.length >= 2) {
588
- return parts.slice(-2).join(".");
589
- }
590
- return domain;
591
- }
592
- async resolveZone(domain) {
593
- const hostname = domain.replace(/\.$/, "").toLowerCase();
594
- if (this.configuredZoneId) {
595
- const name = await this.resolveConfiguredZoneName();
596
- if (name && (hostname === name || hostname.endsWith(`.${name}`))) {
597
- return { id: this.configuredZoneId, name };
598
- }
599
- }
600
- const rootDomain = this.getRootDomain(hostname);
601
- const cached = this.zoneCache.get(rootDomain);
602
- if (cached) {
603
- return { id: cached, name: rootDomain };
604
- }
605
- const response = await this.request("GET", `/zones?name=${encodeURIComponent(rootDomain)}`);
606
- if (!response.result || response.result.length === 0) {
607
- throw new Error(`Zone not found for domain: ${rootDomain}. ` + `If the API token is scoped to a single zone, set its zone id (CLOUDFLARE_ZONE_ID) — ` + `zone lookup by name needs account-wide Zone:Read.`);
608
- }
609
- const zoneId = response.result[0].id;
610
- this.zoneCache.set(rootDomain, zoneId);
611
- return { id: zoneId, name: response.result[0].name || rootDomain };
612
- }
613
- async resolveConfiguredZoneName() {
614
- if (!this.configuredZoneId)
615
- return;
616
- if (this.configuredZoneName)
617
- return this.configuredZoneName;
618
- try {
619
- const response = await this.request("GET", `/zones/${this.configuredZoneId}`);
620
- const name = response.result?.name?.toLowerCase();
621
- if (!name)
622
- return;
623
- this.configuredZoneName = name;
624
- this.zoneCache.set(name, this.configuredZoneId);
625
- return name;
626
- } catch {
627
- return;
628
- }
629
- }
630
- async getZoneId(domain) {
631
- return (await this.resolveZone(domain)).id;
632
- }
633
- getFullRecordName(name, zoneName) {
634
- const cleanName = name.replace(/\.$/, "");
635
- if (!cleanName || cleanName === zoneName || cleanName === "@") {
636
- return zoneName;
637
- }
638
- if (cleanName.endsWith(`.${zoneName}`)) {
639
- return cleanName;
640
- }
641
- return `${cleanName}.${zoneName}`;
642
- }
643
- toCloudflareRecord(record, zoneName, proxied) {
644
- const cfRecord = {
645
- type: record.type,
646
- name: this.getFullRecordName(record.name, zoneName),
647
- content: record.content || record.value || "",
648
- ttl: record.ttl || 1
649
- };
650
- const desiredProxy = proxied ?? record.proxied;
651
- if (desiredProxy !== undefined && PROXIABLE_RECORD_TYPES.has(record.type)) {
652
- cfRecord.proxied = desiredProxy;
653
- if (desiredProxy)
654
- cfRecord.ttl = 1;
655
- }
656
- if (record.type === "MX" && record.priority !== undefined) {
657
- cfRecord.priority = record.priority;
658
- }
659
- if (record.type === "SRV") {
660
- if (record.priority !== undefined) {
661
- cfRecord.priority = record.priority;
662
- }
663
- }
664
- return cfRecord;
665
- }
666
- fromCloudflareRecord(record) {
667
- return {
668
- id: record.id,
669
- name: record.name,
670
- type: record.type,
671
- content: record.content,
672
- ttl: record.ttl,
673
- priority: record.priority,
674
- proxied: record.proxied
675
- };
676
- }
677
- async createRecord(domain, record) {
678
- try {
679
- const zone = await this.resolveZone(domain);
680
- const cfRecord = this.toCloudflareRecord(record, zone.name);
681
- const response = await this.request("POST", `/zones/${zone.id}/dns_records`, cfRecord);
682
- return {
683
- success: true,
684
- id: response.result.id,
685
- message: "Record created successfully"
686
- };
687
- } catch (error) {
688
- return {
689
- success: false,
690
- message: error instanceof Error ? error.message : "Unknown error"
691
- };
692
- }
693
- }
694
- async upsertRecord(domain, record) {
695
- try {
696
- const zone = await this.resolveZone(domain);
697
- const fullName = this.getFullRecordName(record.name, zone.name);
698
- const existingResponse = await this.request("GET", `/zones/${zone.id}/dns_records?type=${record.type}&name=${encodeURIComponent(fullName)}`);
699
- const existing = existingResponse.result?.[0];
700
- const proxied = record.proxied ?? existing?.proxied;
701
- const cfRecord = this.toCloudflareRecord(record, zone.name, proxied);
702
- if (existing) {
703
- const response2 = await this.request("PUT", `/zones/${zone.id}/dns_records/${existing.id}`, cfRecord);
704
- return {
705
- success: true,
706
- id: response2.result.id,
707
- message: "Record updated successfully"
708
- };
709
- }
710
- const response = await this.request("POST", `/zones/${zone.id}/dns_records`, cfRecord);
711
- return {
712
- success: true,
713
- id: response.result.id,
714
- message: "Record created successfully"
715
- };
716
- } catch (error) {
717
- return this.createRecord(domain, record);
718
- }
719
- }
720
- async deleteRecord(domain, record) {
721
- try {
722
- const zone = await this.resolveZone(domain);
723
- const zoneId = zone.id;
724
- const fullName = this.getFullRecordName(record.name, zone.name);
725
- const existingResponse = await this.request("GET", `/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(fullName)}`);
726
- if (!existingResponse.result || existingResponse.result.length === 0) {
727
- return {
728
- success: false,
729
- message: "Record not found"
730
- };
731
- }
732
- const matchingRecord = existingResponse.result.find((r) => r.content === record.content);
733
- if (!matchingRecord) {
734
- return {
735
- success: false,
736
- message: "Record with matching content not found"
737
- };
738
- }
739
- await this.request("DELETE", `/zones/${zoneId}/dns_records/${matchingRecord.id}`);
740
- return {
741
- success: true,
742
- message: "Record deleted successfully"
743
- };
744
- } catch (error) {
745
- return {
746
- success: false,
747
- message: error instanceof Error ? error.message : "Unknown error"
748
- };
749
- }
750
- }
751
- async listRecords(domain, type) {
752
- try {
753
- const zoneId = await this.getZoneId(domain);
754
- let endpoint = `/zones/${zoneId}/dns_records?per_page=100`;
755
- if (type) {
756
- endpoint += `&type=${type}`;
757
- }
758
- const allRecords = [];
759
- let page = 1;
760
- let hasMore = true;
761
- while (hasMore) {
762
- const response = await this.request("GET", `${endpoint}&page=${page}`);
763
- allRecords.push(...response.result || []);
764
- if (response.result_info) {
765
- hasMore = page < response.result_info.total_pages;
766
- page++;
767
- } else {
768
- hasMore = false;
769
- }
770
- }
771
- return {
772
- success: true,
773
- records: allRecords.map((r) => this.fromCloudflareRecord(r))
774
- };
775
- } catch (error) {
776
- return {
777
- success: false,
778
- records: [],
779
- message: error instanceof Error ? error.message : "Unknown error"
780
- };
781
- }
782
- }
783
- async canManageDomain(domain) {
784
- try {
785
- await this.getZoneId(domain);
786
- return true;
787
- } catch {
788
- return false;
789
- }
790
- }
791
- async listDomains() {
792
- try {
793
- const allZones = [];
794
- let page = 1;
795
- let hasMore = true;
796
- while (hasMore) {
797
- const response = await this.request("GET", `/zones?per_page=50&page=${page}`);
798
- allZones.push(...response.result || []);
799
- if (response.result_info) {
800
- hasMore = page < response.result_info.total_pages;
801
- page++;
802
- } else {
803
- hasMore = false;
804
- }
805
- }
806
- return allZones.map((z) => z.name);
807
- } catch {
808
- return [];
809
- }
810
- }
811
- async getZoneDetails(domain) {
812
- try {
813
- const zoneId = await this.getZoneId(domain);
814
- const response = await this.request("GET", `/zones/${zoneId}`);
815
- return {
816
- id: response.result.id,
817
- name: response.result.name,
818
- status: response.result.status,
819
- nameServers: response.result.name_servers,
820
- paused: response.result.paused
821
- };
822
- } catch {
823
- return null;
824
- }
825
- }
826
- async purgeCache(domain, options) {
827
- try {
828
- const zoneId = await this.getZoneId(domain);
829
- const body = {};
830
- if (options?.purgeEverything) {
831
- body.purge_everything = true;
832
- } else {
833
- if (options?.files)
834
- body.files = options.files;
835
- if (options?.tags)
836
- body.tags = options.tags;
837
- if (options?.hosts)
838
- body.hosts = options.hosts;
839
- }
840
- if (Object.keys(body).length === 0) {
841
- body.purge_everything = true;
842
- }
843
- await this.request("POST", `/zones/${zoneId}/purge_cache`, body);
844
- return true;
845
- } catch {
846
- return false;
847
- }
848
- }
849
- async getRecordProxyStatus(domain, record) {
850
- try {
851
- const zone = await this.resolveZone(domain);
852
- const zoneId = zone.id;
853
- const fullName = this.getFullRecordName(record.name, zone.name);
854
- const response = await this.request("GET", `/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(fullName)}`);
855
- if (response.result && response.result.length > 0) {
856
- const matchingRecord = response.result.find((r) => r.content === record.content);
857
- return matchingRecord?.proxied ?? null;
858
- }
859
- return null;
860
- } catch {
861
- return null;
862
- }
863
- }
864
- async setRecordProxyStatus(domain, record, proxied) {
865
- try {
866
- const zone = await this.resolveZone(domain);
867
- const zoneId = zone.id;
868
- const fullName = this.getFullRecordName(record.name, zone.name);
869
- const response = await this.request("GET", `/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(fullName)}`);
870
- if (!response.result || response.result.length === 0) {
871
- return false;
872
- }
873
- const matchingRecord = response.result.find((r) => r.content === record.content);
874
- if (!matchingRecord) {
875
- return false;
876
- }
877
- await this.request("PATCH", `/zones/${zoneId}/dns_records/${matchingRecord.id}`, { proxied });
878
- return true;
879
- } catch {
880
- return false;
881
- }
882
- }
883
- async zoneIdFor(domain) {
884
- return this.getZoneId(domain);
885
- }
886
- get account() {
887
- return this.accountId;
888
- }
889
- async getZoneSettings(domain) {
890
- const zoneId = await this.getZoneId(domain);
891
- const response = await this.request("GET", `/zones/${zoneId}/settings`);
892
- const settings = {};
893
- for (const setting of response.result || [])
894
- settings[setting.id] = setting.value;
895
- return settings;
896
- }
897
- async applyZoneSettings(domain, desired) {
898
- const zoneId = await this.getZoneId(domain);
899
- const current = await this.getZoneSettings(domain).catch(() => ({}));
900
- const changed = [];
901
- const failed = [];
902
- for (const [id, value] of Object.entries(desired)) {
903
- if (value === undefined)
904
- continue;
905
- const existing = current[id];
906
- if (existing !== undefined && deepEqual(existing, value))
907
- continue;
908
- try {
909
- await this.request("PATCH", `/zones/${zoneId}/settings/${id}`, { value });
910
- changed.push({ id, from: existing, to: value });
911
- } catch (error) {
912
- failed.push({ id, error: error instanceof Error ? error.message : String(error) });
913
- }
914
- }
915
- return { changed, failed };
916
- }
917
- async getPhaseRules(domain, phase) {
918
- const zoneId = await this.getZoneId(domain);
919
- try {
920
- const response = await this.request("GET", `/zones/${zoneId}/rulesets/phases/${phase}/entrypoint`);
921
- return response.result?.rules || [];
922
- } catch {
923
- return [];
924
- }
925
- }
926
- async putManagedPhaseRules(domain, phase, rules) {
927
- try {
928
- const zoneId = await this.getZoneId(domain);
929
- const existing = await this.getPhaseRules(domain, phase);
930
- const foreign = existing.filter((rule) => !(rule.description || "").startsWith(CLOUDFLARE_MANAGED_RULE_PREFIX));
931
- const managed = rules.map((rule) => ({
932
- ...rule,
933
- description: rule.description?.startsWith(CLOUDFLARE_MANAGED_RULE_PREFIX) ? rule.description : `${CLOUDFLARE_MANAGED_RULE_PREFIX} ${rule.description || phase}`
934
- }));
935
- const payload = [...foreign, ...managed].map(({ id: _id, ...rule }) => rule);
936
- await this.request("PUT", `/zones/${zoneId}/rulesets/phases/${phase}/entrypoint`, { rules: payload });
937
- return { success: true };
938
- } catch (error) {
939
- return { success: false, message: error instanceof Error ? error.message : String(error) };
940
- }
941
- }
942
- }
943
- function deepEqual(a, b) {
944
- if (a === b)
945
- return true;
946
- if (typeof a !== typeof b || a === null || b === null)
947
- return false;
948
- if (typeof a !== "object")
949
- return false;
950
- if (Array.isArray(a) || Array.isArray(b)) {
951
- if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
952
- return false;
953
- return a.every((item, index) => deepEqual(item, b[index]));
954
- }
955
- const aRecord = a;
956
- const bRecord = b;
957
- return Object.keys(bRecord).every((key) => deepEqual(aRecord[key], bRecord[key]));
958
- }
959
- // src/dns/declared-records.ts
960
- var SINGLETON_TYPES = new Set(["A", "AAAA", "CNAME", "ALIAS"]);
961
- function policyTag(content) {
962
- const normalized = content.trim().replace(/^"|"$/g, "").toLowerCase();
963
- if (normalized.startsWith("v=spf1"))
964
- return "v=spf1";
965
- if (normalized.startsWith("v=dmarc1"))
966
- return "v=dmarc1";
967
- return;
968
- }
969
- function unquote(value) {
970
- return value.trim().replace(/^"(.*)"$/s, "$1");
971
- }
972
- function normalizeName(name) {
973
- return name.replace(/\.$/, "").toLowerCase();
974
- }
975
- function qualifyName(name, zone) {
976
- const z = normalizeName(zone);
977
- const raw = (name ?? "@").trim();
978
- if (!raw || raw === "@")
979
- return z;
980
- const n = normalizeName(raw);
981
- if (n === z || n.endsWith(`.${z}`))
982
- return n;
983
- return `${n}.${z}`;
984
- }
985
- function resolveDeclaredRecords(records, zone) {
986
- return records.map((record) => ({
987
- ...record,
988
- name: qualifyName(record.name, zone),
989
- proxied: record.proxied ?? false
990
- }));
991
- }
992
- function matches(existing, record) {
993
- if (existing.type !== record.type)
994
- return false;
995
- if (normalizeName(existing.name) !== normalizeName(record.name))
996
- return false;
997
- if (SINGLETON_TYPES.has(record.type))
998
- return true;
999
- if (record.type === "TXT") {
1000
- const tag = policyTag(record.content);
1001
- if (tag)
1002
- return policyTag(unquote(existing.content)) === tag;
1003
- return unquote(existing.content) === unquote(record.content);
1004
- }
1005
- return unquote(existing.content) === unquote(record.content);
1006
- }
1007
- function isCurrent(existing, record) {
1008
- if (unquote(existing.content) !== unquote(record.content))
1009
- return false;
1010
- if (record.priority !== undefined && existing.priority !== record.priority)
1011
- return false;
1012
- if (record.proxied !== undefined && existing.proxied !== undefined && existing.proxied !== record.proxied)
1013
- return false;
1014
- return true;
1015
- }
1016
- async function reconcileDeclaredRecords(options) {
1017
- const { provider, zone, records } = options;
1018
- const report = { outcomes: [], warnings: [] };
1019
- if (records.length === 0)
1020
- return report;
1021
- const listed = await provider.listRecords(zone);
1022
- if (!listed.success) {
1023
- report.warnings.push(`could not list ${zone}: ${listed.message || "unknown provider error"} — records not reconciled`);
1024
- return report;
1025
- }
1026
- const existing = listed.records;
1027
- for (const record of records) {
1028
- const current = existing.filter((e) => matches(e, record));
1029
- if (current.length > 0 && current.every((e) => isCurrent(e, record))) {
1030
- report.outcomes.push({ record, action: "unchanged" });
1031
- continue;
1032
- }
1033
- const wire = {
1034
- name: record.name,
1035
- type: record.type,
1036
- content: record.content,
1037
- ttl: record.ttl ?? 3600,
1038
- ...record.priority !== undefined ? { priority: record.priority } : {},
1039
- ...record.proxied !== undefined ? { proxied: record.proxied } : {}
1040
- };
1041
- const supersedes = record.type === "TXT" && policyTag(record.content) !== undefined;
1042
- if (supersedes) {
1043
- for (const stale of current) {
1044
- const removed = await provider.deleteRecord(zone, {
1045
- name: stale.name,
1046
- type: stale.type,
1047
- content: stale.content
1048
- });
1049
- if (!removed.success)
1050
- report.warnings.push(`could not remove superseded ${stale.type} at ${stale.name}: ${removed.message || "unknown error"}`);
1051
- }
1052
- }
1053
- const result = supersedes || current.length === 0 ? await provider.createRecord(zone, wire) : await provider.upsertRecord(zone, wire);
1054
- if (result.success)
1055
- report.outcomes.push({ record, action: current.length > 0 ? "updated" : "created" });
1056
- else
1057
- report.outcomes.push({ record, action: "failed", message: result.message });
1058
- }
1059
- for (const type of ["MX"]) {
1060
- const declaredNames = new Set(records.filter((r) => r.type === type).map((r) => normalizeName(r.name)));
1061
- for (const name of declaredNames) {
1062
- const declaredValues = new Set(records.filter((r) => r.type === type && normalizeName(r.name) === name).map((r) => unquote(r.content).toLowerCase()));
1063
- const strays = existing.filter((e) => e.type === type && normalizeName(e.name) === name && !declaredValues.has(unquote(e.content).toLowerCase()));
1064
- for (const stray of strays)
1065
- report.warnings.push(`${name} has an undeclared ${type} record (${stray.content}) alongside the declared one — mail will be split between them. Remove it at the provider if it is stale.`);
1066
- }
1067
- }
1068
- return report;
1069
- }
1070
- // src/dns/route53-adapter.ts
1071
- class Route53Provider {
1072
- name = "route53";
1073
- client;
1074
- hostedZoneCache = new Map;
1075
- providedHostedZoneId;
1076
- constructor(region = "us-east-1", hostedZoneId) {
1077
- this.client = new Route53Client(region);
1078
- this.providedHostedZoneId = hostedZoneId;
1079
- }
1080
- getRootDomain(domain) {
1081
- const parts = domain.replace(/\.$/, "").split(".");
1082
- if (parts.length >= 2) {
1083
- return parts.slice(-2).join(".");
1084
- }
1085
- return domain;
1086
- }
1087
- async getHostedZoneId(domain) {
1088
- if (this.providedHostedZoneId) {
1089
- return this.providedHostedZoneId;
1090
- }
1091
- const rootDomain = this.getRootDomain(domain);
1092
- const cached = this.hostedZoneCache.get(rootDomain);
1093
- if (cached) {
1094
- return cached;
1095
- }
1096
- const zone = await this.client.findHostedZoneForDomain(domain);
1097
- if (zone) {
1098
- const zoneId = zone.Id.replace("/hostedzone/", "");
1099
- this.hostedZoneCache.set(rootDomain, zoneId);
1100
- return zoneId;
1101
- }
1102
- return null;
1103
- }
1104
- normalizeName(domain, name) {
1105
- const zone = domain.replace(/\.$/, "");
1106
- const record = name.replace(/\.$/, "");
1107
- if (!record || record === "@")
1108
- return `${zone}.`;
1109
- if (record === zone || record.endsWith(`.${zone}`))
1110
- return `${record}.`;
1111
- return `${record}.${zone}.`;
1112
- }
1113
- async createRecord(domain, record) {
1114
- try {
1115
- const hostedZoneId = await this.getHostedZoneId(domain);
1116
- if (!hostedZoneId) {
1117
- return {
1118
- success: false,
1119
- message: `No hosted zone found for domain: ${domain}`
1120
- };
1121
- }
1122
- const recordName = this.normalizeName(domain, record.name);
1123
- let recordValue = record.content;
1124
- if (record.type === "TXT" && !recordValue.startsWith('"')) {
1125
- recordValue = `"${recordValue}"`;
1126
- }
1127
- if (record.type === "MX" && record.priority !== undefined) {
1128
- recordValue = `${record.priority} ${recordValue}`;
1129
- }
1130
- const result = await this.client.changeResourceRecordSets({
1131
- HostedZoneId: hostedZoneId,
1132
- ChangeBatch: {
1133
- Comment: `Created by ts-cloud DNS provider`,
1134
- Changes: [
1135
- {
1136
- Action: "CREATE",
1137
- ResourceRecordSet: {
1138
- Name: recordName,
1139
- Type: record.type,
1140
- TTL: record.ttl || 300,
1141
- ResourceRecords: [{ Value: recordValue }]
1142
- }
1143
- }
1144
- ]
1145
- }
1146
- });
1147
- return {
1148
- success: true,
1149
- id: result.ChangeInfo?.Id,
1150
- message: "Record created successfully"
1151
- };
1152
- } catch (error) {
1153
- return {
1154
- success: false,
1155
- message: error instanceof Error ? error.message : "Unknown error"
1156
- };
1157
- }
1158
- }
1159
- async upsertRecord(domain, record) {
1160
- try {
1161
- const hostedZoneId = await this.getHostedZoneId(domain);
1162
- if (!hostedZoneId) {
1163
- return {
1164
- success: false,
1165
- message: `No hosted zone found for domain: ${domain}`
1166
- };
1167
- }
1168
- const recordName = this.normalizeName(domain, record.name);
1169
- let recordValue = record.content;
1170
- if (record.type === "TXT" && !recordValue.startsWith('"')) {
1171
- recordValue = `"${recordValue}"`;
1172
- }
1173
- if (record.type === "MX" && record.priority !== undefined) {
1174
- recordValue = `${record.priority} ${recordValue}`;
1175
- }
1176
- const result = await this.client.changeResourceRecordSets({
1177
- HostedZoneId: hostedZoneId,
1178
- ChangeBatch: {
1179
- Comment: `Upserted by ts-cloud DNS provider`,
1180
- Changes: [
1181
- {
1182
- Action: "UPSERT",
1183
- ResourceRecordSet: {
1184
- Name: recordName,
1185
- Type: record.type,
1186
- TTL: record.ttl || 300,
1187
- ResourceRecords: [{ Value: recordValue }]
1188
- }
1189
- }
1190
- ]
1191
- }
1192
- });
1193
- return {
1194
- success: true,
1195
- id: result.ChangeInfo?.Id,
1196
- message: "Record upserted successfully"
1197
- };
1198
- } catch (error) {
1199
- return {
1200
- success: false,
1201
- message: error instanceof Error ? error.message : "Unknown error"
1202
- };
1203
- }
1204
- }
1205
- async deleteRecord(domain, record) {
1206
- try {
1207
- const hostedZoneId = await this.getHostedZoneId(domain);
1208
- if (!hostedZoneId) {
1209
- return {
1210
- success: false,
1211
- message: `No hosted zone found for domain: ${domain}`
1212
- };
1213
- }
1214
- const recordName = this.normalizeName(domain, record.name);
1215
- let recordValue = record.content;
1216
- if (record.type === "TXT" && !recordValue.startsWith('"')) {
1217
- recordValue = `"${recordValue}"`;
1218
- }
1219
- if (record.type === "MX" && record.priority !== undefined) {
1220
- recordValue = `${record.priority} ${recordValue}`;
1221
- }
1222
- await this.client.changeResourceRecordSets({
1223
- HostedZoneId: hostedZoneId,
1224
- ChangeBatch: {
1225
- Comment: `Deleted by ts-cloud DNS provider`,
1226
- Changes: [
1227
- {
1228
- Action: "DELETE",
1229
- ResourceRecordSet: {
1230
- Name: recordName,
1231
- Type: record.type,
1232
- TTL: record.ttl || 300,
1233
- ResourceRecords: [{ Value: recordValue }]
1234
- }
1235
- }
1236
- ]
1237
- }
1238
- });
1239
- return {
1240
- success: true,
1241
- message: "Record deleted successfully"
1242
- };
1243
- } catch (error) {
1244
- return {
1245
- success: false,
1246
- message: error instanceof Error ? error.message : "Unknown error"
1247
- };
1248
- }
1249
- }
1250
- async listRecords(domain, type) {
1251
- try {
1252
- const hostedZoneId = await this.getHostedZoneId(domain);
1253
- if (!hostedZoneId) {
1254
- return {
1255
- success: false,
1256
- records: [],
1257
- message: `No hosted zone found for domain: ${domain}`
1258
- };
1259
- }
1260
- const result = await this.client.listResourceRecordSets({
1261
- HostedZoneId: hostedZoneId,
1262
- StartRecordType: type
1263
- });
1264
- const records = [];
1265
- for (const rs of result.ResourceRecordSets) {
1266
- if (type && rs.Type !== type) {
1267
- continue;
1268
- }
1269
- if (rs.AliasTarget) {
1270
- continue;
1271
- }
1272
- for (const rr of rs.ResourceRecords || []) {
1273
- let content = rr.Value;
1274
- let priority;
1275
- if (rs.Type === "MX") {
1276
- const parts = content.split(" ");
1277
- if (parts.length >= 2) {
1278
- priority = Number.parseInt(parts[0], 10);
1279
- content = parts.slice(1).join(" ");
1280
- }
1281
- }
1282
- if (rs.Type === "TXT" && content.startsWith('"') && content.endsWith('"')) {
1283
- content = content.slice(1, -1);
1284
- }
1285
- records.push({
1286
- name: rs.Name.replace(/\.$/, ""),
1287
- type: rs.Type,
1288
- content,
1289
- ttl: rs.TTL,
1290
- priority
1291
- });
1292
- }
1293
- }
1294
- return {
1295
- success: true,
1296
- records
1297
- };
1298
- } catch (error) {
1299
- return {
1300
- success: false,
1301
- records: [],
1302
- message: error instanceof Error ? error.message : "Unknown error"
1303
- };
1304
- }
1305
- }
1306
- async canManageDomain(domain) {
1307
- const hostedZoneId = await this.getHostedZoneId(domain);
1308
- return hostedZoneId !== null;
1309
- }
1310
- async listDomains() {
1311
- try {
1312
- const result = await this.client.listHostedZones();
1313
- return result.HostedZones.map((z) => z.Name.replace(/\.$/, ""));
1314
- } catch {
1315
- return [];
1316
- }
1317
- }
1318
- getRoute53Client() {
1319
- return this.client;
1320
- }
1321
- async createAliasRecord(params) {
1322
- try {
1323
- const hostedZoneId = await this.getHostedZoneId(params.domain);
1324
- if (!hostedZoneId) {
1325
- return {
1326
- success: false,
1327
- message: `No hosted zone found for domain: ${params.domain}`
1328
- };
1329
- }
1330
- const result = await this.client.createAliasRecord({
1331
- HostedZoneId: hostedZoneId,
1332
- Name: this.normalizeName(params.domain, params.name),
1333
- TargetHostedZoneId: params.targetHostedZoneId,
1334
- TargetDNSName: params.targetDnsName,
1335
- EvaluateTargetHealth: params.evaluateTargetHealth,
1336
- Type: params.type
1337
- });
1338
- return {
1339
- success: true,
1340
- id: result.ChangeInfo?.Id,
1341
- message: "Alias record created successfully"
1342
- };
1343
- } catch (error) {
1344
- return {
1345
- success: false,
1346
- message: error instanceof Error ? error.message : "Unknown error"
1347
- };
1348
- }
1349
- }
1350
- async createCloudFrontAlias(params) {
1351
- return this.createAliasRecord({
1352
- domain: params.domain,
1353
- name: params.name,
1354
- targetHostedZoneId: Route53Client.CloudFrontHostedZoneId,
1355
- targetDnsName: params.cloudFrontDomainName,
1356
- evaluateTargetHealth: false
1357
- });
1358
- }
1359
- async createAlbAlias(params) {
1360
- const hostedZoneId = Route53Client.ALBHostedZoneIds[params.region];
1361
- if (!hostedZoneId) {
1362
- return {
1363
- success: false,
1364
- message: `Unknown region for ALB: ${params.region}`
1365
- };
1366
- }
1367
- return this.createAliasRecord({
1368
- domain: params.domain,
1369
- name: params.name,
1370
- targetHostedZoneId: hostedZoneId,
1371
- targetDnsName: params.albDnsName,
1372
- evaluateTargetHealth: true
1373
- });
1374
- }
1375
- }
1376
- // src/aws/acm.ts
1377
- class ACMClient {
1378
- client;
1379
- region;
1380
- constructor(region = "us-east-1") {
1381
- this.client = new AWSClient;
1382
- this.region = region;
1383
- }
1384
- async requestCertificate(params) {
1385
- const requestBody = {
1386
- DomainName: params.DomainName,
1387
- ValidationMethod: params.ValidationMethod || "DNS"
1388
- };
1389
- if (params.SubjectAlternativeNames) {
1390
- requestBody.SubjectAlternativeNames = params.SubjectAlternativeNames;
1391
- }
1392
- const result = await this.client.request({
1393
- service: "acm",
1394
- region: this.region,
1395
- method: "POST",
1396
- path: "/",
1397
- headers: {
1398
- "content-type": "application/x-amz-json-1.1",
1399
- "x-amz-target": "CertificateManager.RequestCertificate"
1400
- },
1401
- body: JSON.stringify(requestBody)
1402
- });
1403
- return {
1404
- CertificateArn: result.CertificateArn || ""
1405
- };
1406
- }
1407
- async describeCertificate(params) {
1408
- const result = await this.client.request({
1409
- service: "acm",
1410
- region: this.region,
1411
- method: "POST",
1412
- path: "/",
1413
- headers: {
1414
- "content-type": "application/x-amz-json-1.1",
1415
- "x-amz-target": "CertificateManager.DescribeCertificate"
1416
- },
1417
- body: JSON.stringify({
1418
- CertificateArn: params.CertificateArn
1419
- })
1420
- });
1421
- const cert = result.Certificate || {};
1422
- return {
1423
- CertificateArn: cert.CertificateArn || "",
1424
- DomainName: cert.DomainName || "",
1425
- SubjectAlternativeNames: cert.SubjectAlternativeNames,
1426
- Status: cert.Status || "PENDING_VALIDATION",
1427
- Type: cert.Type,
1428
- DomainValidationOptions: cert.DomainValidationOptions?.map((opt) => ({
1429
- DomainName: opt.DomainName,
1430
- ValidationDomain: opt.ValidationDomain,
1431
- ValidationStatus: opt.ValidationStatus,
1432
- ResourceRecord: opt.ResourceRecord ? {
1433
- Name: opt.ResourceRecord.Name,
1434
- Type: opt.ResourceRecord.Type,
1435
- Value: opt.ResourceRecord.Value
1436
- } : undefined,
1437
- ValidationMethod: opt.ValidationMethod
1438
- })),
1439
- CreatedAt: cert.CreatedAt,
1440
- IssuedAt: cert.IssuedAt,
1441
- NotBefore: cert.NotBefore,
1442
- NotAfter: cert.NotAfter
1443
- };
1444
- }
1445
- async listCertificates(params) {
1446
- const requestBody = {};
1447
- if (params?.CertificateStatuses) {
1448
- requestBody.CertificateStatuses = params.CertificateStatuses;
1449
- }
1450
- if (params?.MaxItems) {
1451
- requestBody.MaxItems = params.MaxItems;
1452
- }
1453
- if (params?.NextToken) {
1454
- requestBody.NextToken = params.NextToken;
1455
- }
1456
- const result = await this.client.request({
1457
- service: "acm",
1458
- region: this.region,
1459
- method: "POST",
1460
- path: "/",
1461
- headers: {
1462
- "content-type": "application/x-amz-json-1.1",
1463
- "x-amz-target": "CertificateManager.ListCertificates"
1464
- },
1465
- body: JSON.stringify(requestBody)
1466
- });
1467
- return {
1468
- CertificateSummaryList: (result.CertificateSummaryList || []).map((cert) => ({
1469
- CertificateArn: cert.CertificateArn,
1470
- DomainName: cert.DomainName
1471
- })),
1472
- NextToken: result.NextToken
1473
- };
1474
- }
1475
- async deleteCertificate(params) {
1476
- await this.client.request({
1477
- service: "acm",
1478
- region: this.region,
1479
- method: "POST",
1480
- path: "/",
1481
- headers: {
1482
- "content-type": "application/x-amz-json-1.1",
1483
- "x-amz-target": "CertificateManager.DeleteCertificate"
1484
- },
1485
- body: JSON.stringify({
1486
- CertificateArn: params.CertificateArn
1487
- })
1488
- });
1489
- }
1490
- async listTagsForCertificate(params) {
1491
- const result = await this.client.request({
1492
- service: "acm",
1493
- region: this.region,
1494
- method: "POST",
1495
- path: "/",
1496
- headers: {
1497
- "content-type": "application/x-amz-json-1.1",
1498
- "x-amz-target": "CertificateManager.ListTagsForCertificate"
1499
- },
1500
- body: JSON.stringify({
1501
- CertificateArn: params.CertificateArn
1502
- })
1503
- });
1504
- return {
1505
- Tags: result.Tags || []
1506
- };
1507
- }
1508
- async addTagsToCertificate(params) {
1509
- await this.client.request({
1510
- service: "acm",
1511
- region: this.region,
1512
- method: "POST",
1513
- path: "/",
1514
- headers: {
1515
- "content-type": "application/x-amz-json-1.1",
1516
- "x-amz-target": "CertificateManager.AddTagsToCertificate"
1517
- },
1518
- body: JSON.stringify({
1519
- CertificateArn: params.CertificateArn,
1520
- Tags: params.Tags
1521
- })
1522
- });
1523
- }
1524
- async resendValidationEmail(params) {
1525
- await this.client.request({
1526
- service: "acm",
1527
- region: this.region,
1528
- method: "POST",
1529
- path: "/",
1530
- headers: {
1531
- "content-type": "application/x-amz-json-1.1",
1532
- "x-amz-target": "CertificateManager.ResendValidationEmail"
1533
- },
1534
- body: JSON.stringify({
1535
- CertificateArn: params.CertificateArn,
1536
- Domain: params.Domain,
1537
- ValidationDomain: params.ValidationDomain
1538
- })
1539
- });
1540
- }
1541
- async findCertificateByDomain(domainName) {
1542
- const result = await this.listCertificates({
1543
- CertificateStatuses: ["ISSUED"]
1544
- });
1545
- const summary = result.CertificateSummaryList.find((c) => c.DomainName === domainName || c.DomainName === `*.${domainName.split(".").slice(1).join(".")}`);
1546
- if (!summary) {
1547
- return null;
1548
- }
1549
- return this.describeCertificate({ CertificateArn: summary.CertificateArn });
1550
- }
1551
- async waitForCertificateValidation(certificateArn, maxAttempts = 60, delayMs = 30000) {
1552
- for (let i = 0;i < maxAttempts; i++) {
1553
- const cert = await this.describeCertificate({ CertificateArn: certificateArn });
1554
- if (cert.Status === "ISSUED") {
1555
- return cert;
1556
- }
1557
- if (cert.Status === "FAILED" || cert.Status === "VALIDATION_TIMED_OUT") {
1558
- return null;
1559
- }
1560
- await new Promise((resolve) => setTimeout(resolve, delayMs));
1561
- }
1562
- return null;
1563
- }
1564
- async getDnsValidationRecords(certificateArn) {
1565
- const cert = await this.describeCertificate({ CertificateArn: certificateArn });
1566
- if (!cert.DomainValidationOptions) {
1567
- return [];
1568
- }
1569
- return cert.DomainValidationOptions.filter((opt) => opt.ResourceRecord && opt.ValidationMethod === "DNS").map((opt) => ({
1570
- domainName: opt.DomainName,
1571
- recordName: opt.ResourceRecord.Name,
1572
- recordType: opt.ResourceRecord.Type,
1573
- recordValue: opt.ResourceRecord.Value
1574
- }));
1575
- }
1576
- async requestCertificateWithSans(params) {
1577
- const sans = new Set;
1578
- sans.add(params.DomainName);
1579
- if (params.IncludeWww !== false) {
1580
- sans.add(`www.${params.DomainName}`);
1581
- }
1582
- if (params.IncludeWildcard) {
1583
- sans.add(`*.${params.DomainName}`);
1584
- }
1585
- if (params.AdditionalSans) {
1586
- for (const san of params.AdditionalSans) {
1587
- sans.add(san);
1588
- }
1589
- }
1590
- return this.requestCertificate({
1591
- DomainName: params.DomainName,
1592
- SubjectAlternativeNames: Array.from(sans),
1593
- ValidationMethod: "DNS"
1594
- });
1595
- }
1596
- async isCertificateValidForDomain(certificateArn, domainName) {
1597
- const cert = await this.describeCertificate({ CertificateArn: certificateArn });
1598
- if (cert.Status !== "ISSUED") {
1599
- return false;
1600
- }
1601
- if (cert.DomainName === domainName) {
1602
- return true;
1603
- }
1604
- if (cert.DomainName?.startsWith("*.")) {
1605
- const baseDomain = cert.DomainName.slice(2);
1606
- const domainParts = domainName.split(".");
1607
- const baseParts = baseDomain.split(".");
1608
- if (domainParts.slice(-baseParts.length).join(".") === baseDomain) {
1609
- return true;
1610
- }
1611
- }
1612
- if (cert.SubjectAlternativeNames) {
1613
- for (const san of cert.SubjectAlternativeNames) {
1614
- if (san === domainName) {
1615
- return true;
1616
- }
1617
- if (san.startsWith("*.")) {
1618
- const baseDomain = san.slice(2);
1619
- const domainParts = domainName.split(".");
1620
- const baseParts = baseDomain.split(".");
1621
- if (domainParts.slice(-baseParts.length).join(".") === baseDomain) {
1622
- return true;
1623
- }
1624
- }
1625
- }
1626
- }
1627
- return false;
1628
- }
1629
- }
1630
-
1631
- class ACMDnsValidator {
1632
- acm;
1633
- route53;
1634
- dnsProvider;
1635
- constructor(region = "us-east-1", dnsProviderConfig) {
1636
- this.acm = new ACMClient(region);
1637
- this.route53 = new Route53Client;
1638
- if (dnsProviderConfig && dnsProviderConfig.provider !== "route53") {
1639
- this.dnsProvider = createDnsProvider(dnsProviderConfig);
1640
- }
1641
- }
1642
- async requestAndValidate(params) {
1643
- const {
1644
- domainName,
1645
- hostedZoneId,
1646
- subjectAlternativeNames = [],
1647
- waitForValidation = false,
1648
- maxWaitMinutes = 30
1649
- } = params;
1650
- if (!this.dnsProvider && !hostedZoneId) {
1651
- throw new Error("Either hostedZoneId or external DNS provider configuration is required");
1652
- }
1653
- const { CertificateArn } = await this.acm.requestCertificate({
1654
- DomainName: domainName,
1655
- SubjectAlternativeNames: subjectAlternativeNames.length > 0 ? [domainName, ...subjectAlternativeNames] : undefined,
1656
- ValidationMethod: "DNS"
1657
- });
1658
- await this.waitForValidationOptions(CertificateArn);
1659
- const validationRecords = await this.acm.getDnsValidationRecords(CertificateArn);
1660
- if (this.dnsProvider) {
1661
- for (const record of validationRecords) {
1662
- const result = await this.dnsProvider.upsertRecord(domainName, {
1663
- name: record.recordName,
1664
- type: record.recordType,
1665
- content: record.recordValue,
1666
- ttl: 300
1667
- });
1668
- if (!result.success) {
1669
- console.warn(`Failed to create validation record for ${record.domainName}: ${result.message}`);
1670
- }
1671
- }
1672
- } else if (hostedZoneId) {
1673
- for (const record of validationRecords) {
1674
- await this.route53.changeResourceRecordSets({
1675
- HostedZoneId: hostedZoneId,
1676
- ChangeBatch: {
1677
- Comment: `ACM DNS validation for ${record.domainName}`,
1678
- Changes: [
1679
- {
1680
- Action: "UPSERT",
1681
- ResourceRecordSet: {
1682
- Name: record.recordName,
1683
- Type: record.recordType,
1684
- TTL: 300,
1685
- ResourceRecords: [{ Value: record.recordValue }]
1686
- }
1687
- }
1688
- ]
1689
- }
1690
- });
1691
- }
1692
- }
1693
- if (waitForValidation) {
1694
- const cert = await this.acm.waitForCertificateValidation(CertificateArn, maxWaitMinutes * 2, 30000);
1695
- if (!cert) {
1696
- throw new Error(`Certificate validation timed out after ${maxWaitMinutes} minutes`);
1697
- }
1698
- }
1699
- return {
1700
- certificateArn: CertificateArn,
1701
- validationRecords
1702
- };
1703
- }
1704
- async waitForValidationOptions(certificateArn, maxAttempts = 30) {
1705
- for (let i = 0;i < maxAttempts; i++) {
1706
- const cert = await this.acm.describeCertificate({ CertificateArn: certificateArn });
1707
- if (cert.DomainValidationOptions && cert.DomainValidationOptions.length > 0 && cert.DomainValidationOptions[0].ResourceRecord) {
1708
- return;
1709
- }
1710
- await new Promise((resolve) => setTimeout(resolve, 2000));
1711
- }
1712
- throw new Error("Timeout waiting for DNS validation options");
1713
- }
1714
- async createValidationRecords(params) {
1715
- const { certificateArn, hostedZoneId, domain } = params;
1716
- if (!this.dnsProvider && !hostedZoneId) {
1717
- throw new Error("Either hostedZoneId or external DNS provider configuration is required");
1718
- }
1719
- const validationRecords = await this.acm.getDnsValidationRecords(certificateArn);
1720
- const results = [];
1721
- if (this.dnsProvider) {
1722
- const targetDomain = domain || validationRecords[0]?.domainName;
1723
- for (const record of validationRecords) {
1724
- const result = await this.dnsProvider.upsertRecord(targetDomain, {
1725
- name: record.recordName,
1726
- type: record.recordType,
1727
- content: record.recordValue,
1728
- ttl: 300
1729
- });
1730
- results.push({
1731
- ...record,
1732
- changeId: result.success ? result.id : undefined
1733
- });
1734
- }
1735
- } else if (hostedZoneId) {
1736
- for (const record of validationRecords) {
1737
- const result = await this.route53.changeResourceRecordSets({
1738
- HostedZoneId: hostedZoneId,
1739
- ChangeBatch: {
1740
- Comment: `ACM DNS validation for ${record.domainName}`,
1741
- Changes: [
1742
- {
1743
- Action: "UPSERT",
1744
- ResourceRecordSet: {
1745
- Name: record.recordName,
1746
- Type: record.recordType,
1747
- TTL: 300,
1748
- ResourceRecords: [{ Value: record.recordValue }]
1749
- }
1750
- }
1751
- ]
1752
- }
1753
- });
1754
- results.push({
1755
- ...record,
1756
- changeId: result.ChangeInfo?.Id
1757
- });
1758
- }
1759
- }
1760
- return results;
1761
- }
1762
- async deleteValidationRecords(params) {
1763
- const { certificateArn, hostedZoneId, domain } = params;
1764
- const validationRecords = await this.acm.getDnsValidationRecords(certificateArn);
1765
- if (this.dnsProvider) {
1766
- const targetDomain = domain || validationRecords[0]?.domainName;
1767
- for (const record of validationRecords) {
1768
- try {
1769
- await this.dnsProvider.deleteRecord(targetDomain, {
1770
- name: record.recordName,
1771
- type: record.recordType,
1772
- content: record.recordValue
1773
- });
1774
- } catch {}
1775
- }
1776
- } else if (hostedZoneId) {
1777
- for (const record of validationRecords) {
1778
- try {
1779
- await this.route53.changeResourceRecordSets({
1780
- HostedZoneId: hostedZoneId,
1781
- ChangeBatch: {
1782
- Comment: `Cleanup ACM DNS validation for ${record.domainName}`,
1783
- Changes: [
1784
- {
1785
- Action: "DELETE",
1786
- ResourceRecordSet: {
1787
- Name: record.recordName,
1788
- Type: record.recordType,
1789
- TTL: 300,
1790
- ResourceRecords: [{ Value: record.recordValue }]
1791
- }
1792
- }
1793
- ]
1794
- }
1795
- });
1796
- } catch {}
1797
- }
1798
- }
1799
- }
1800
- async findOrCreateCertificate(params) {
1801
- const { domainName, hostedZoneId, subjectAlternativeNames, waitForValidation = true } = params;
1802
- if (!this.dnsProvider && !hostedZoneId) {
1803
- throw new Error("Either hostedZoneId or external DNS provider configuration is required");
1804
- }
1805
- const existing = await this.acm.findCertificateByDomain(domainName);
1806
- if (existing && existing.Status === "ISSUED") {
1807
- return {
1808
- certificateArn: existing.CertificateArn,
1809
- isNew: false
1810
- };
1811
- }
1812
- const { certificateArn } = await this.requestAndValidate({
1813
- domainName,
1814
- hostedZoneId,
1815
- subjectAlternativeNames,
1816
- waitForValidation
1817
- });
1818
- return {
1819
- certificateArn,
1820
- isNew: true
1821
- };
1822
- }
1823
- hasExternalDnsProvider() {
1824
- return this.dnsProvider !== undefined;
1825
- }
1826
- getDnsProviderName() {
1827
- return this.dnsProvider?.name || "route53";
1828
- }
1829
- }
1830
-
1831
- // src/dns/validator.ts
1832
- class UnifiedDnsValidator {
1833
- acm;
1834
- dnsProvider;
1835
- constructor(dnsProvider, acmRegion = "us-east-1") {
1836
- this.acm = new ACMClient(acmRegion);
1837
- if ("provider" in dnsProvider) {
1838
- this.dnsProvider = createDnsProvider(dnsProvider);
1839
- } else {
1840
- this.dnsProvider = dnsProvider;
1841
- }
1842
- }
1843
- getProvider() {
1844
- return this.dnsProvider;
1845
- }
1846
- async requestAndValidate(params) {
1847
- const { domainName, subjectAlternativeNames = [], waitForValidation = false, maxWaitMinutes = 30 } = params;
1848
- const { CertificateArn } = await this.acm.requestCertificate({
1849
- DomainName: domainName,
1850
- SubjectAlternativeNames: subjectAlternativeNames.length > 0 ? [domainName, ...subjectAlternativeNames] : undefined,
1851
- ValidationMethod: "DNS"
1852
- });
1853
- await this.waitForValidationOptions(CertificateArn);
1854
- const validationRecords = await this.acm.getDnsValidationRecords(CertificateArn);
1855
- for (const record of validationRecords) {
1856
- const result = await this.dnsProvider.upsertRecord(domainName, {
1857
- name: record.recordName,
1858
- type: record.recordType,
1859
- content: record.recordValue,
1860
- ttl: 300
1861
- });
1862
- if (!result.success) {
1863
- console.warn(`Failed to create validation record for ${record.domainName}: ${result.message}`);
1864
- }
1865
- }
1866
- let status = "pending";
1867
- if (waitForValidation) {
1868
- const cert = await this.acm.waitForCertificateValidation(CertificateArn, maxWaitMinutes * 2, 30000);
1869
- status = cert?.Status === "ISSUED" ? "issued" : "failed";
1870
- }
1871
- return {
1872
- certificateArn: CertificateArn,
1873
- validationRecords: validationRecords.map((r) => ({
1874
- domainName: r.domainName,
1875
- recordName: r.recordName,
1876
- recordType: r.recordType,
1877
- recordValue: r.recordValue
1878
- })),
1879
- isNew: true,
1880
- status
1881
- };
1882
- }
1883
- async createValidationRecords(params) {
1884
- const { certificateArn, domain } = params;
1885
- const errors = [];
1886
- const validationRecords = await this.acm.getDnsValidationRecords(certificateArn);
1887
- for (const record of validationRecords) {
1888
- const result = await this.dnsProvider.upsertRecord(domain, {
1889
- name: record.recordName,
1890
- type: record.recordType,
1891
- content: record.recordValue,
1892
- ttl: 300
1893
- });
1894
- if (!result.success) {
1895
- errors.push(`Failed to create record for ${record.domainName}: ${result.message}`);
1896
- }
1897
- }
1898
- return {
1899
- success: errors.length === 0,
1900
- records: validationRecords.map((r) => ({
1901
- domainName: r.domainName,
1902
- recordName: r.recordName,
1903
- recordType: r.recordType,
1904
- recordValue: r.recordValue
1905
- })),
1906
- errors
1907
- };
1908
- }
1909
- async deleteValidationRecords(params) {
1910
- const { certificateArn, domain } = params;
1911
- const errors = [];
1912
- const validationRecords = await this.acm.getDnsValidationRecords(certificateArn);
1913
- for (const record of validationRecords) {
1914
- const result = await this.dnsProvider.deleteRecord(domain, {
1915
- name: record.recordName,
1916
- type: record.recordType,
1917
- content: record.recordValue
1918
- });
1919
- if (!result.success) {
1920
- errors.push(`Failed to delete record for ${record.domainName}: ${result.message}`);
1921
- }
1922
- }
1923
- return {
1924
- success: errors.length === 0,
1925
- errors
1926
- };
1927
- }
1928
- async findOrCreateCertificate(params) {
1929
- const { domainName, subjectAlternativeNames = [], waitForValidation = true, maxWaitMinutes } = params;
1930
- const existing = await this.acm.findCertificateByDomain(domainName);
1931
- if (existing && existing.Status === "ISSUED") {
1932
- const existingSans = existing.SubjectAlternativeNames || [existing.DomainName];
1933
- const hasWildcard = existingSans.some((san) => san === `*.${domainName}`);
1934
- const allSansCovered = subjectAlternativeNames.every((san) => existingSans.includes(san) || hasWildcard);
1935
- if (allSansCovered) {
1936
- return {
1937
- certificateArn: existing.CertificateArn,
1938
- validationRecords: [],
1939
- isNew: false,
1940
- status: "issued"
1941
- };
1942
- }
1943
- console.log(`Existing certificate doesn't cover all required SANs, requesting new certificate...`);
1944
- }
1945
- return this.requestAndValidate({
1946
- domainName,
1947
- subjectAlternativeNames,
1948
- waitForValidation,
1949
- maxWaitMinutes
1950
- });
1951
- }
1952
- async requestCertificateWithCommonSans(params) {
1953
- const {
1954
- domainName,
1955
- includeWww = true,
1956
- includeWildcard = false,
1957
- additionalSans = [],
1958
- waitForValidation = false
1959
- } = params;
1960
- const sans = [];
1961
- if (includeWww) {
1962
- sans.push(`www.${domainName}`);
1963
- }
1964
- if (includeWildcard) {
1965
- sans.push(`*.${domainName}`);
1966
- }
1967
- sans.push(...additionalSans);
1968
- return this.requestAndValidate({
1969
- domainName,
1970
- subjectAlternativeNames: sans,
1971
- waitForValidation
1972
- });
1973
- }
1974
- async waitForValidationOptions(certificateArn, maxAttempts = 30) {
1975
- for (let i = 0;i < maxAttempts; i++) {
1976
- const cert = await this.acm.describeCertificate({ CertificateArn: certificateArn });
1977
- if (cert.DomainValidationOptions && cert.DomainValidationOptions.length > 0 && cert.DomainValidationOptions[0].ResourceRecord) {
1978
- return;
1979
- }
1980
- await new Promise((resolve) => setTimeout(resolve, 2000));
1981
- }
1982
- throw new Error("Timeout waiting for DNS validation options");
1983
- }
1984
- async getCertificateStatus(certificateArn) {
1985
- const cert = await this.acm.describeCertificate({ CertificateArn: certificateArn });
1986
- return {
1987
- status: cert.Status,
1988
- domainValidations: (cert.DomainValidationOptions || []).map((opt) => ({
1989
- domain: opt.DomainName,
1990
- status: opt.ValidationStatus || "UNKNOWN"
1991
- }))
1992
- };
1993
- }
1994
- }
1995
- function createPorkbunValidator(apiKey, secretKey, acmRegion) {
1996
- return new UnifiedDnsValidator({ provider: "porkbun", apiKey, secretKey }, acmRegion);
1997
- }
1998
- function createGoDaddyValidator(apiKey, apiSecret, acmRegion, environment) {
1999
- return new UnifiedDnsValidator({ provider: "godaddy", apiKey, apiSecret, environment }, acmRegion);
2000
- }
2001
- function createRoute53Validator(region, hostedZoneId, acmRegion) {
2002
- return new UnifiedDnsValidator({ provider: "route53", region, hostedZoneId }, acmRegion || region);
2003
- }
2004
- // src/dns/index.ts
2005
- function createDnsProvider(config) {
2006
- switch (config.provider) {
2007
- case "route53":
2008
- return new Route53Provider(config.region, config.hostedZoneId);
2009
- case "porkbun":
2010
- return new PorkbunProvider(config.apiKey, config.secretKey);
2011
- case "godaddy":
2012
- return new GoDaddyProvider(config.apiKey, config.apiSecret, config.environment);
2013
- case "cloudflare":
2014
- return new CloudflareProvider(config.apiToken, { zoneId: config.zoneId, accountId: config.accountId });
2015
- default:
2016
- throw new Error(`Unknown DNS provider: ${config.provider}`);
2017
- }
2018
- }
2019
- async function detectDnsProvider(domain, configs) {
2020
- for (const config of configs) {
2021
- const provider = createDnsProvider(config);
2022
- if (await provider.canManageDomain(domain)) {
2023
- return provider;
2024
- }
2025
- }
2026
- return null;
2027
- }
2028
-
2029
- class DnsProviderFactory {
2030
- providers = new Map;
2031
- configs = [];
2032
- addConfig(config) {
2033
- this.configs.push(config);
2034
- return this;
2035
- }
2036
- addRoute53(region, hostedZoneId) {
2037
- this.configs.push({
2038
- provider: "route53",
2039
- region,
2040
- hostedZoneId
2041
- });
2042
- return this;
2043
- }
2044
- addPorkbun(apiKey, secretKey) {
2045
- this.configs.push({
2046
- provider: "porkbun",
2047
- apiKey,
2048
- secretKey
2049
- });
2050
- return this;
2051
- }
2052
- addGoDaddy(apiKey, apiSecret, environment) {
2053
- this.configs.push({
2054
- provider: "godaddy",
2055
- apiKey,
2056
- apiSecret,
2057
- environment
2058
- });
2059
- return this;
2060
- }
2061
- addCloudflare(apiToken, options = {}) {
2062
- this.configs.push({
2063
- provider: "cloudflare",
2064
- apiToken,
2065
- zoneId: options.zoneId,
2066
- accountId: options.accountId
2067
- });
2068
- return this;
2069
- }
2070
- loadFromEnv() {
2071
- if (process.env.AWS_ACCESS_KEY_ID || process.env.AWS_REGION) {
2072
- this.addRoute53(process.env.AWS_REGION);
2073
- }
2074
- const porkbunApiKey = process.env.PORKBUN_API_KEY;
2075
- const porkbunSecretKey = process.env.PORKBUN_SECRET_KEY;
2076
- if (porkbunApiKey && porkbunSecretKey) {
2077
- this.addPorkbun(porkbunApiKey, porkbunSecretKey);
2078
- }
2079
- const godaddyApiKey = process.env.GODADDY_API_KEY;
2080
- const godaddyApiSecret = process.env.GODADDY_API_SECRET;
2081
- if (godaddyApiKey && godaddyApiSecret) {
2082
- const env = process.env.GODADDY_ENVIRONMENT;
2083
- this.addGoDaddy(godaddyApiKey, godaddyApiSecret, env);
2084
- }
2085
- const cloudflareApiToken = process.env.CLOUDFLARE_API_TOKEN;
2086
- if (cloudflareApiToken) {
2087
- this.addCloudflare(cloudflareApiToken, {
2088
- zoneId: process.env.CLOUDFLARE_ZONE_ID,
2089
- accountId: process.env.CLOUDFLARE_ACCOUNT_ID
2090
- });
2091
- }
2092
- return this;
2093
- }
2094
- getProvider(name) {
2095
- const cached = this.providers.get(name);
2096
- if (cached) {
2097
- return cached;
2098
- }
2099
- const config = this.configs.find((c) => c.provider === name);
2100
- if (!config) {
2101
- return null;
2102
- }
2103
- const provider = createDnsProvider(config);
2104
- this.providers.set(name, provider);
2105
- return provider;
2106
- }
2107
- async getProviderForDomain(domain) {
2108
- for (const config of this.configs) {
2109
- const provider = createDnsProvider(config);
2110
- if (await provider.canManageDomain(domain)) {
2111
- return provider;
2112
- }
2113
- }
2114
- return null;
2115
- }
2116
- getAllProviders() {
2117
- return this.configs.map((config) => createDnsProvider(config));
2118
- }
2119
- }
2120
- var dnsProviders = new DnsProviderFactory;
2121
-
2122
- export { PROXIABLE_RECORD_TYPES, PorkbunProvider, GoDaddyProvider, CLOUDFLARE_MANAGED_RULE_PREFIX, CloudflareProvider, policyTag, qualifyName, resolveDeclaredRecords, reconcileDeclaredRecords, Route53Provider, UnifiedDnsValidator, createPorkbunValidator, createGoDaddyValidator, createRoute53Validator, createDnsProvider, detectDnsProvider, DnsProviderFactory, dnsProviders, ACMClient, ACMDnsValidator };