@geekmidas/cli 0.54.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +26 -5
  3. package/dist/CachedStateProvider-D73dCqfH.cjs +60 -0
  4. package/dist/CachedStateProvider-D73dCqfH.cjs.map +1 -0
  5. package/dist/CachedStateProvider-DVyKfaMm.mjs +54 -0
  6. package/dist/CachedStateProvider-DVyKfaMm.mjs.map +1 -0
  7. package/dist/CachedStateProvider-D_uISMmJ.cjs +3 -0
  8. package/dist/CachedStateProvider-OiFUGr7p.mjs +3 -0
  9. package/dist/HostingerProvider-DUV9-Tzg.cjs +210 -0
  10. package/dist/HostingerProvider-DUV9-Tzg.cjs.map +1 -0
  11. package/dist/HostingerProvider-DqUq6e9i.mjs +210 -0
  12. package/dist/HostingerProvider-DqUq6e9i.mjs.map +1 -0
  13. package/dist/LocalStateProvider-CdspeSVL.cjs +43 -0
  14. package/dist/LocalStateProvider-CdspeSVL.cjs.map +1 -0
  15. package/dist/LocalStateProvider-DxoSaWUV.mjs +42 -0
  16. package/dist/LocalStateProvider-DxoSaWUV.mjs.map +1 -0
  17. package/dist/Route53Provider-CpRIqu69.cjs +157 -0
  18. package/dist/Route53Provider-CpRIqu69.cjs.map +1 -0
  19. package/dist/Route53Provider-KUAX3vz9.mjs +156 -0
  20. package/dist/Route53Provider-KUAX3vz9.mjs.map +1 -0
  21. package/dist/SSMStateProvider-BxAPU99a.cjs +53 -0
  22. package/dist/SSMStateProvider-BxAPU99a.cjs.map +1 -0
  23. package/dist/SSMStateProvider-C4wp4AZe.mjs +52 -0
  24. package/dist/SSMStateProvider-C4wp4AZe.mjs.map +1 -0
  25. package/dist/{bundler-DGry2vaR.mjs → bundler-BqTN5Dj5.mjs} +3 -3
  26. package/dist/{bundler-DGry2vaR.mjs.map → bundler-BqTN5Dj5.mjs.map} +1 -1
  27. package/dist/{bundler-BB-kETMd.cjs → bundler-tHLLwYuU.cjs} +3 -3
  28. package/dist/{bundler-BB-kETMd.cjs.map → bundler-tHLLwYuU.cjs.map} +1 -1
  29. package/dist/{config-HYiM3iQJ.cjs → config-BGeJsW1r.cjs} +2 -2
  30. package/dist/{config-HYiM3iQJ.cjs.map → config-BGeJsW1r.cjs.map} +1 -1
  31. package/dist/{config-C3LSBNSl.mjs → config-C6awcFBx.mjs} +2 -2
  32. package/dist/{config-C3LSBNSl.mjs.map → config-C6awcFBx.mjs.map} +1 -1
  33. package/dist/config.cjs +2 -2
  34. package/dist/config.d.cts +1 -1
  35. package/dist/config.d.mts +2 -2
  36. package/dist/config.mjs +2 -2
  37. package/dist/credentials-C8DWtnMY.cjs +174 -0
  38. package/dist/credentials-C8DWtnMY.cjs.map +1 -0
  39. package/dist/credentials-DT1dSxIx.mjs +126 -0
  40. package/dist/credentials-DT1dSxIx.mjs.map +1 -0
  41. package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
  42. package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
  43. package/dist/deploy/sniffer-loader.cjs +1 -1
  44. package/dist/{dokploy-api-94KzmTVf.mjs → dokploy-api-7k3t7_zd.mjs} +1 -1
  45. package/dist/{dokploy-api-94KzmTVf.mjs.map → dokploy-api-7k3t7_zd.mjs.map} +1 -1
  46. package/dist/dokploy-api-CHa8G51l.mjs +3 -0
  47. package/dist/{dokploy-api-YD8WCQfW.cjs → dokploy-api-CQvhV6Hd.cjs} +1 -1
  48. package/dist/{dokploy-api-YD8WCQfW.cjs.map → dokploy-api-CQvhV6Hd.cjs.map} +1 -1
  49. package/dist/dokploy-api-CWc02yyg.cjs +3 -0
  50. package/dist/{encryption-DaCB_NmS.cjs → encryption-BE0UOb8j.cjs} +1 -1
  51. package/dist/{encryption-DaCB_NmS.cjs.map → encryption-BE0UOb8j.cjs.map} +1 -1
  52. package/dist/{encryption-Biq0EZ4m.cjs → encryption-Cv3zips0.cjs} +1 -1
  53. package/dist/{encryption-BC4MAODn.mjs → encryption-JtMsiGNp.mjs} +1 -1
  54. package/dist/{encryption-BC4MAODn.mjs.map → encryption-JtMsiGNp.mjs.map} +1 -1
  55. package/dist/encryption-UUmaWAmz.mjs +3 -0
  56. package/dist/{index-pOA56MWT.d.cts → index-B5rGIc4g.d.cts} +553 -196
  57. package/dist/index-B5rGIc4g.d.cts.map +1 -0
  58. package/dist/{index-A70abJ1m.d.mts → index-KFEbMIRa.d.mts} +554 -197
  59. package/dist/index-KFEbMIRa.d.mts.map +1 -0
  60. package/dist/index.cjs +2265 -658
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.mjs +2242 -635
  63. package/dist/index.mjs.map +1 -1
  64. package/dist/{openapi-C3C-BzIZ.mjs → openapi-BMFmLnX6.mjs} +51 -7
  65. package/dist/openapi-BMFmLnX6.mjs.map +1 -0
  66. package/dist/{openapi-D7WwlpPF.cjs → openapi-D1KXv2Ml.cjs} +51 -7
  67. package/dist/openapi-D1KXv2Ml.cjs.map +1 -0
  68. package/dist/{openapi-react-query-C_MxpBgF.cjs → openapi-react-query-BeXvk-wa.cjs} +1 -1
  69. package/dist/{openapi-react-query-C_MxpBgF.cjs.map → openapi-react-query-BeXvk-wa.cjs.map} +1 -1
  70. package/dist/{openapi-react-query-ZoP9DPbY.mjs → openapi-react-query-DGEkD39r.mjs} +1 -1
  71. package/dist/{openapi-react-query-ZoP9DPbY.mjs.map → openapi-react-query-DGEkD39r.mjs.map} +1 -1
  72. package/dist/openapi-react-query.cjs +1 -1
  73. package/dist/openapi-react-query.mjs +1 -1
  74. package/dist/openapi.cjs +3 -3
  75. package/dist/openapi.d.cts +1 -1
  76. package/dist/openapi.d.mts +2 -2
  77. package/dist/openapi.mjs +3 -3
  78. package/dist/{storage-Dhst7BhI.mjs → storage-BMW6yLu3.mjs} +1 -1
  79. package/dist/{storage-Dhst7BhI.mjs.map → storage-BMW6yLu3.mjs.map} +1 -1
  80. package/dist/{storage-fOR8dMu5.cjs → storage-C7pmBq1u.cjs} +1 -1
  81. package/dist/{storage-BPRgh3DU.cjs → storage-CoCNe0Pt.cjs} +1 -1
  82. package/dist/{storage-BPRgh3DU.cjs.map → storage-CoCNe0Pt.cjs.map} +1 -1
  83. package/dist/{storage-DNj_I11J.mjs → storage-D8XzjVaO.mjs} +1 -1
  84. package/dist/{types-BtGL-8QS.d.mts → types-BldpmqQX.d.mts} +1 -1
  85. package/dist/{types-BtGL-8QS.d.mts.map → types-BldpmqQX.d.mts.map} +1 -1
  86. package/dist/workspace/index.cjs +1 -1
  87. package/dist/workspace/index.d.cts +1 -1
  88. package/dist/workspace/index.d.mts +2 -2
  89. package/dist/workspace/index.mjs +1 -1
  90. package/dist/{workspace-CaVW6j2q.cjs → workspace-BFRUOOrh.cjs} +309 -25
  91. package/dist/workspace-BFRUOOrh.cjs.map +1 -0
  92. package/dist/{workspace-DLFRaDc-.mjs → workspace-DAxG3_H2.mjs} +309 -25
  93. package/dist/workspace-DAxG3_H2.mjs.map +1 -0
  94. package/package.json +14 -8
  95. package/scripts/sync-versions.ts +86 -0
  96. package/src/build/__tests__/handler-templates.spec.ts +115 -47
  97. package/src/deploy/CachedStateProvider.ts +86 -0
  98. package/src/deploy/LocalStateProvider.ts +57 -0
  99. package/src/deploy/SSMStateProvider.ts +93 -0
  100. package/src/deploy/StateProvider.ts +171 -0
  101. package/src/deploy/__tests__/CachedStateProvider.spec.ts +228 -0
  102. package/src/deploy/__tests__/HostingerProvider.spec.ts +347 -0
  103. package/src/deploy/__tests__/LocalStateProvider.spec.ts +126 -0
  104. package/src/deploy/__tests__/Route53Provider.spec.ts +402 -0
  105. package/src/deploy/__tests__/SSMStateProvider.spec.ts +177 -0
  106. package/src/deploy/__tests__/__fixtures__/env-parsers/throwing-env-parser.ts +1 -3
  107. package/src/deploy/__tests__/__fixtures__/route-apps/services.ts +28 -19
  108. package/src/deploy/__tests__/createDnsProvider.spec.ts +172 -0
  109. package/src/deploy/__tests__/createStateProvider.spec.ts +116 -0
  110. package/src/deploy/__tests__/dns-orchestration.spec.ts +192 -0
  111. package/src/deploy/__tests__/dns-verification.spec.ts +2 -2
  112. package/src/deploy/__tests__/env-resolver.spec.ts +37 -15
  113. package/src/deploy/__tests__/sniffer.spec.ts +4 -20
  114. package/src/deploy/__tests__/state.spec.ts +13 -5
  115. package/src/deploy/dns/DnsProvider.ts +163 -0
  116. package/src/deploy/dns/HostingerProvider.ts +100 -0
  117. package/src/deploy/dns/Route53Provider.ts +256 -0
  118. package/src/deploy/dns/index.ts +257 -165
  119. package/src/deploy/env-resolver.ts +12 -5
  120. package/src/deploy/index.ts +16 -13
  121. package/src/deploy/sniffer-envkit-patch.ts +3 -1
  122. package/src/deploy/sniffer-routes-worker.ts +104 -0
  123. package/src/deploy/sniffer.ts +77 -55
  124. package/src/deploy/state-commands.ts +274 -0
  125. package/src/dev/__tests__/entry.spec.ts +8 -2
  126. package/src/dev/__tests__/index.spec.ts +1 -3
  127. package/src/dev/index.ts +9 -3
  128. package/src/docker/__tests__/templates.spec.ts +3 -1
  129. package/src/index.ts +88 -0
  130. package/src/init/__tests__/generators.spec.ts +273 -0
  131. package/src/init/__tests__/init.spec.ts +3 -3
  132. package/src/init/generators/auth.ts +1 -0
  133. package/src/init/generators/config.ts +2 -0
  134. package/src/init/generators/models.ts +6 -1
  135. package/src/init/generators/monorepo.ts +3 -0
  136. package/src/init/generators/ui.ts +1472 -0
  137. package/src/init/generators/web.ts +134 -87
  138. package/src/init/index.ts +22 -3
  139. package/src/init/templates/api.ts +109 -3
  140. package/src/init/versions.ts +25 -53
  141. package/src/openapi.ts +99 -13
  142. package/src/workspace/__tests__/schema.spec.ts +107 -0
  143. package/src/workspace/schema.ts +314 -4
  144. package/src/workspace/types.ts +22 -36
  145. package/dist/dokploy-api-CItuaWTq.mjs +0 -3
  146. package/dist/dokploy-api-DBNE8MDt.cjs +0 -3
  147. package/dist/encryption-CQXBZGkt.mjs +0 -3
  148. package/dist/index-A70abJ1m.d.mts.map +0 -1
  149. package/dist/index-pOA56MWT.d.cts.map +0 -1
  150. package/dist/openapi-C3C-BzIZ.mjs.map +0 -1
  151. package/dist/openapi-D7WwlpPF.cjs.map +0 -1
  152. package/dist/workspace-CaVW6j2q.cjs.map +0 -1
  153. package/dist/workspace-DLFRaDc-.mjs.map +0 -1
  154. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HostingerProvider-DqUq6e9i.mjs","names":["message: string","status: number","statusText: string","errors?: Record<string, string[]>","token: string","method: 'GET' | 'POST' | 'PUT' | 'DELETE'","endpoint: string","body?: unknown","errors: Record<string, string[]> | undefined","domain: string","records: DnsRecord[]","filters: DnsRecordFilter[]","name: string","type: DnsRecordType","subdomain: string","ip: string","domain: string","records: UpsertDnsRecord[]","results: UpsertResult[]"],"sources":["../src/deploy/dns/hostinger-api.ts","../src/deploy/dns/HostingerProvider.ts"],"sourcesContent":["/**\n * Hostinger DNS API client\n *\n * API Documentation: https://developers.hostinger.com/\n * Authentication: Bearer token from hpanel.hostinger.com/profile/api\n */\n\nconst HOSTINGER_API_BASE = 'https://developers.hostinger.com';\n\n/**\n * DNS record types supported by Hostinger\n */\nexport type DnsRecordType =\n\t| 'A'\n\t| 'AAAA'\n\t| 'CNAME'\n\t| 'MX'\n\t| 'TXT'\n\t| 'NS'\n\t| 'SRV'\n\t| 'CAA';\n\n/**\n * A single DNS record\n */\nexport interface DnsRecord {\n\t/** Subdomain name (e.g., 'api.joemoer' for api.joemoer.traflabs.io) */\n\tname: string;\n\t/** Record type */\n\ttype: DnsRecordType;\n\t/** TTL in seconds */\n\tttl: number;\n\t/** Record values */\n\trecords: Array<{ content: string }>;\n}\n\n/**\n * Filter for deleting specific records\n */\nexport interface DnsRecordFilter {\n\tname: string;\n\ttype: DnsRecordType;\n}\n\n/**\n * API error response\n */\nexport interface HostingerErrorResponse {\n\tmessage?: string;\n\terrors?: Record<string, string[]>;\n}\n\n/**\n * Hostinger API error\n */\nexport class HostingerApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic errors?: Record<string, string[]>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'HostingerApiError';\n\t}\n}\n\n/**\n * Hostinger DNS API client\n *\n * @example\n * ```ts\n * const api = new HostingerApi(token);\n *\n * // Get all records for a domain\n * const records = await api.getRecords('traflabs.io');\n *\n * // Create/update records\n * await api.upsertRecords('traflabs.io', [\n * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }\n * ]);\n * ```\n */\nexport class HostingerApi {\n\tprivate token: string;\n\n\tconstructor(token: string) {\n\t\tthis.token = token;\n\t}\n\n\t/**\n\t * Make a request to the Hostinger API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${HOSTINGER_API_BASE}${endpoint}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\tAuthorization: `Bearer ${this.token}`,\n\t\t\t},\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;\n\t\t\tlet errors: Record<string, string[]> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as HostingerErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Hostinger API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\terrors = errorBody.errors;\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new HostingerApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\terrors,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Get all DNS records for a domain\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t */\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tinterface RecordResponse {\n\t\t\tdata: Array<{\n\t\t\t\tname: string;\n\t\t\t\ttype: DnsRecordType;\n\t\t\t\tttl: number;\n\t\t\t\trecords: Array<{ content: string }>;\n\t\t\t}>;\n\t\t}\n\n\t\tconst response = await this.request<RecordResponse>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\treturn response.data || [];\n\t}\n\n\t/**\n\t * Create or update DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to create/update\n\t * @param overwrite - If true, replaces all existing records. If false, merges with existing.\n\t */\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t\toverwrite = false,\n\t): Promise<void> {\n\t\tawait this.request('PUT', `/api/dns/v1/zones/${domain}`, {\n\t\t\toverwrite,\n\t\t\tzone: records,\n\t\t});\n\t}\n\n\t/**\n\t * Validate DNS records before applying\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to validate\n\t * @returns true if valid, throws if invalid\n\t */\n\tasync validateRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t): Promise<boolean> {\n\t\tawait this.request('POST', `/api/dns/v1/zones/${domain}/validate`, {\n\t\t\toverwrite: false,\n\t\t\tzone: records,\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * Delete specific DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param filters - Filters to match records for deletion\n\t */\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\tfilters: DnsRecordFilter[],\n\t): Promise<void> {\n\t\tawait this.request('DELETE', `/api/dns/v1/zones/${domain}`, {\n\t\t\tfilters,\n\t\t});\n\t}\n\n\t/**\n\t * Check if a specific record exists\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param name - Subdomain name (e.g., 'api.joemoer')\n\t * @param type - Record type (e.g., 'A')\n\t */\n\tasync recordExists(\n\t\tdomain: string,\n\t\tname: string,\n\t\ttype: DnsRecordType = 'A',\n\t): Promise<boolean> {\n\t\tconst records = await this.getRecords(domain);\n\t\treturn records.some((r) => r.name === name && r.type === type);\n\t}\n\n\t/**\n\t * Create a single A record if it doesn't exist\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param subdomain - Subdomain name (e.g., 'api.joemoer')\n\t * @param ip - IP address to point to\n\t * @param ttl - TTL in seconds (default: 300)\n\t * @returns true if created, false if already exists\n\t */\n\tasync createARecordIfNotExists(\n\t\tdomain: string,\n\t\tsubdomain: string,\n\t\tip: string,\n\t\tttl = 300,\n\t): Promise<boolean> {\n\t\tconst exists = await this.recordExists(domain, subdomain, 'A');\n\t\tif (exists) {\n\t\t\treturn false;\n\t\t}\n\n\t\tawait this.upsertRecords(domain, [\n\t\t\t{\n\t\t\t\tname: subdomain,\n\t\t\t\ttype: 'A',\n\t\t\t\tttl,\n\t\t\t\trecords: [{ content: ip }],\n\t\t\t},\n\t\t]);\n\n\t\treturn true;\n\t}\n}\n","/**\n * Hostinger DNS Provider\n *\n * Implements DnsProvider interface using the Hostinger DNS API.\n */\n\nimport { getHostingerToken } from '../../auth/credentials';\nimport type {\n\tDnsProvider,\n\tDnsRecord,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\nimport { HostingerApi } from './hostinger-api';\n\n/**\n * Hostinger DNS provider implementation.\n */\nexport class HostingerProvider implements DnsProvider {\n\treadonly name = 'hostinger';\n\tprivate api: HostingerApi | null = null;\n\n\t/**\n\t * Get or create the Hostinger API client.\n\t */\n\tprivate async getApi(): Promise<HostingerApi> {\n\t\tif (this.api) {\n\t\t\treturn this.api;\n\t\t}\n\n\t\tconst token = await getHostingerToken();\n\t\tif (!token) {\n\t\t\tthrow new Error(\n\t\t\t\t'Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api',\n\t\t\t);\n\t\t}\n\n\t\tthis.api = new HostingerApi(token);\n\t\treturn this.api;\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst api = await this.getApi();\n\t\tconst records = await api.getRecords(domain);\n\n\t\treturn records.map((r) => ({\n\t\t\tname: r.name,\n\t\t\ttype: r.type,\n\t\t\tttl: r.ttl,\n\t\t\tvalues: r.records.map((rec) => rec.content),\n\t\t}));\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to check what already exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\n\t\t\tconst existingValue = existing?.records?.[0]?.content;\n\n\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: false,\n\t\t\t\t\tunchanged: true,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Create or update the record\n\t\t\tawait api.upsertRecords(domain, [\n\t\t\t\t{\n\t\t\t\t\tname: record.name,\n\t\t\t\t\ttype: record.type,\n\t\t\t\t\tttl: record.ttl,\n\t\t\t\t\trecords: [{ content: record.value }],\n\t\t\t\t},\n\t\t\t]);\n\n\t\t\tresults.push({\n\t\t\t\trecord,\n\t\t\t\tcreated: !existing,\n\t\t\t\tunchanged: false,\n\t\t\t});\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":";;;;;;;;;AAOA,MAAM,qBAAqB;;;;AAgD3B,IAAa,oBAAb,cAAuC,MAAM;CAC5C,YACCA,SACOC,QACAC,YACAC,QACN;AACD,QAAM,QAAQ;EAJP;EACA;EACA;AAGP,OAAK,OAAO;CACZ;AACD;;;;;;;;;;;;;;;;;AAkBD,IAAa,eAAb,MAA0B;CACzB,AAAQ;CAER,YAAYC,OAAe;AAC1B,OAAK,QAAQ;CACb;;;;CAKD,MAAc,QACbC,QACAC,UACAC,MACa;EACb,MAAM,OAAO,EAAE,mBAAmB,EAAE,SAAS;EAE7C,MAAM,WAAW,MAAM,MAAM,KAAK;GACjC;GACA,SAAS;IACR,gBAAgB;IAChB,gBAAgB,SAAS,KAAK,MAAM;GACpC;GACD,MAAM,OAAO,KAAK,UAAU,KAAK;EACjC,EAAC;AAEF,OAAK,SAAS,IAAI;GACjB,IAAI,gBAAgB,uBAAuB,SAAS,OAAO,GAAG,SAAS,WAAW;GAClF,IAAIC;AAEJ,OAAI;IACH,MAAM,YAAa,MAAM,SAAS,MAAM;AACxC,QAAI,UAAU,QACb,iBAAgB,uBAAuB,UAAU,QAAQ;AAE1D,aAAS,UAAU;GACnB,QAAO,CAEP;AAED,SAAM,IAAI,kBACT,cACA,SAAS,QACT,SAAS,YACT;EAED;EAGD,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,OAAK,QAAQ,KAAK,MAAM,KAAK,GAC5B;AAED,SAAO,KAAK,MAAM,KAAK;CACvB;;;;;;CAOD,MAAM,WAAWC,QAAsC;EAUtD,MAAM,WAAW,MAAM,KAAK,QAC3B,QACC,oBAAoB,OAAO,EAC5B;AAED,SAAO,SAAS,QAAQ,CAAE;CAC1B;;;;;;;;CASD,MAAM,cACLA,QACAC,SACA,YAAY,OACI;AAChB,QAAM,KAAK,QAAQ,QAAQ,oBAAoB,OAAO,GAAG;GACxD;GACA,MAAM;EACN,EAAC;CACF;;;;;;;;CASD,MAAM,gBACLD,QACAC,SACmB;AACnB,QAAM,KAAK,QAAQ,SAAS,oBAAoB,OAAO,YAAY;GAClE,WAAW;GACX,MAAM;EACN,EAAC;AACF,SAAO;CACP;;;;;;;CAQD,MAAM,cACLD,QACAE,SACgB;AAChB,QAAM,KAAK,QAAQ,WAAW,oBAAoB,OAAO,GAAG,EAC3D,QACA,EAAC;CACF;;;;;;;;CASD,MAAM,aACLF,QACAG,MACAC,OAAsB,KACH;EACnB,MAAM,UAAU,MAAM,KAAK,WAAW,OAAO;AAC7C,SAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,SAAS,KAAK;CAC9D;;;;;;;;;;CAWD,MAAM,yBACLJ,QACAK,WACAC,IACA,MAAM,KACa;EACnB,MAAM,SAAS,MAAM,KAAK,aAAa,QAAQ,WAAW,IAAI;AAC9D,MAAI,OACH,QAAO;AAGR,QAAM,KAAK,cAAc,QAAQ,CAChC;GACC,MAAM;GACN,MAAM;GACN;GACA,SAAS,CAAC,EAAE,SAAS,GAAI,CAAC;EAC1B,CACD,EAAC;AAEF,SAAO;CACP;AACD;;;;;;;AClPD,IAAa,oBAAb,MAAsD;CACrD,AAAS,OAAO;CAChB,AAAQ,MAA2B;;;;CAKnC,MAAc,SAAgC;AAC7C,MAAI,KAAK,IACR,QAAO,KAAK;EAGb,MAAM,QAAQ,MAAM,mBAAmB;AACvC,OAAK,MACJ,OAAM,IAAI,MACT;AAIF,OAAK,MAAM,IAAI,aAAa;AAC5B,SAAO,KAAK;CACZ;CAED,MAAM,WAAWC,QAAsC;EACtD,MAAM,MAAM,MAAM,KAAK,QAAQ;EAC/B,MAAM,UAAU,MAAM,IAAI,WAAW,OAAO;AAE5C,SAAO,QAAQ,IAAI,CAAC,OAAO;GAC1B,MAAM,EAAE;GACR,MAAM,EAAE;GACR,KAAK,EAAE;GACP,QAAQ,EAAE,QAAQ,IAAI,CAAC,QAAQ,IAAI,QAAQ;EAC3C,GAAE;CACH;CAED,MAAM,cACLA,QACAC,SAC0B;EAC1B,MAAM,MAAM,MAAM,KAAK,QAAQ;EAC/B,MAAMC,UAA0B,CAAE;EAGlC,MAAM,kBAAkB,MAAM,IAAI,WAAW,OAAO;AAEpD,OAAK,MAAM,UAAU,SAAS;GAC7B,MAAM,WAAW,gBAAgB,KAChC,CAAC,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,SAAS,OAAO,KACnD;GAED,MAAM,gBAAgB,UAAU,UAAU,IAAI;AAE9C,OAAI,YAAY,kBAAkB,OAAO,OAAO;AAE/C,YAAQ,KAAK;KACZ;KACA,SAAS;KACT,WAAW;IACX,EAAC;AACF;GACA;AAGD,SAAM,IAAI,cAAc,QAAQ,CAC/B;IACC,MAAM,OAAO;IACb,MAAM,OAAO;IACb,KAAK,OAAO;IACZ,SAAS,CAAC,EAAE,SAAS,OAAO,MAAO,CAAC;GACpC,CACD,EAAC;AAEF,WAAQ,KAAK;IACZ;IACA,UAAU;IACV,WAAW;GACX,EAAC;EACF;AAED,SAAO;CACP;AACD"}
@@ -0,0 +1,43 @@
1
+ const require_chunk = require('./chunk-CUT6urMc.cjs');
2
+ const node_path = require_chunk.__toESM(require("node:path"));
3
+ const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
4
+
5
+ //#region src/deploy/LocalStateProvider.ts
6
+ /**
7
+ * Get the state file path for a stage.
8
+ */
9
+ function getStateFilePath(workspaceRoot, stage) {
10
+ return (0, node_path.join)(workspaceRoot, ".gkm", `deploy-${stage}.json`);
11
+ }
12
+ /**
13
+ * Local filesystem state provider.
14
+ *
15
+ * Stores state in .gkm/deploy-{stage}.json files in the workspace root.
16
+ */
17
+ var LocalStateProvider = class {
18
+ constructor(workspaceRoot) {
19
+ this.workspaceRoot = workspaceRoot;
20
+ }
21
+ async read(stage) {
22
+ const filePath = getStateFilePath(this.workspaceRoot, stage);
23
+ try {
24
+ const content = await (0, node_fs_promises.readFile)(filePath, "utf-8");
25
+ return JSON.parse(content);
26
+ } catch (error) {
27
+ if (error.code === "ENOENT") return null;
28
+ console.warn(`Warning: Could not read deploy state: ${error}`);
29
+ return null;
30
+ }
31
+ }
32
+ async write(stage, state) {
33
+ const filePath = getStateFilePath(this.workspaceRoot, stage);
34
+ const dir = (0, node_path.join)(this.workspaceRoot, ".gkm");
35
+ await (0, node_fs_promises.mkdir)(dir, { recursive: true });
36
+ state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
37
+ await (0, node_fs_promises.writeFile)(filePath, JSON.stringify(state, null, 2));
38
+ }
39
+ };
40
+
41
+ //#endregion
42
+ exports.LocalStateProvider = LocalStateProvider;
43
+ //# sourceMappingURL=LocalStateProvider-CdspeSVL.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalStateProvider-CdspeSVL.cjs","names":["workspaceRoot: string","stage: string","state: DokployStageState"],"sources":["../src/deploy/LocalStateProvider.ts"],"sourcesContent":["/**\n * Local Filesystem State Provider\n *\n * Stores deployment state in .gkm/deploy-{stage}.json files.\n * This is the default provider for local development.\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/**\n * Get the state file path for a stage.\n */\nfunction getStateFilePath(workspaceRoot: string, stage: string): string {\n\treturn join(workspaceRoot, '.gkm', `deploy-${stage}.json`);\n}\n\n/**\n * Local filesystem state provider.\n *\n * Stores state in .gkm/deploy-{stage}.json files in the workspace root.\n */\nexport class LocalStateProvider implements StateProvider {\n\tconstructor(private readonly workspaceRoot: string) {}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\n\t\ttry {\n\t\t\tconst content = await readFile(filePath, 'utf-8');\n\t\t\treturn JSON.parse(content) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// File doesn't exist - return null\n\t\t\tif ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\t// Log other errors but don't fail\n\t\t\tconsole.warn(`Warning: Could not read deploy state: ${error}`);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\t\tconst dir = join(this.workspaceRoot, '.gkm');\n\n\t\t// Ensure .gkm directory exists\n\t\tawait mkdir(dir, { recursive: true });\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait writeFile(filePath, JSON.stringify(state, null, 2));\n\t}\n}\n"],"mappings":";;;;;;;;AAeA,SAAS,iBAAiBA,eAAuBC,OAAuB;AACvE,QAAO,oBAAK,eAAe,SAAS,SAAS,MAAM,OAAO;AAC1D;;;;;;AAOD,IAAa,qBAAb,MAAyD;CACxD,YAA6BD,eAAuB;EAAvB;CAAyB;CAEtD,MAAM,KAAKC,OAAkD;EAC5D,MAAM,WAAW,iBAAiB,KAAK,eAAe,MAAM;AAE5D,MAAI;GACH,MAAM,UAAU,MAAM,+BAAS,UAAU,QAAQ;AACjD,UAAO,KAAK,MAAM,QAAQ;EAC1B,SAAQ,OAAO;AAEf,OAAK,MAAgC,SAAS,SAC7C,QAAO;AAGR,WAAQ,MAAM,wCAAwC,MAAM,EAAE;AAC9D,UAAO;EACP;CACD;CAED,MAAM,MAAMA,OAAeC,OAAyC;EACnE,MAAM,WAAW,iBAAiB,KAAK,eAAe,MAAM;EAC5D,MAAM,MAAM,oBAAK,KAAK,eAAe,OAAO;AAG5C,QAAM,4BAAM,KAAK,EAAE,WAAW,KAAM,EAAC;AAGrC,QAAM,iBAAiB,qBAAI,QAAO,aAAa;AAE/C,QAAM,gCAAU,UAAU,KAAK,UAAU,OAAO,MAAM,EAAE,CAAC;CACzD;AACD"}
@@ -0,0 +1,42 @@
1
+ import { join } from "node:path";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+
4
+ //#region src/deploy/LocalStateProvider.ts
5
+ /**
6
+ * Get the state file path for a stage.
7
+ */
8
+ function getStateFilePath(workspaceRoot, stage) {
9
+ return join(workspaceRoot, ".gkm", `deploy-${stage}.json`);
10
+ }
11
+ /**
12
+ * Local filesystem state provider.
13
+ *
14
+ * Stores state in .gkm/deploy-{stage}.json files in the workspace root.
15
+ */
16
+ var LocalStateProvider = class {
17
+ constructor(workspaceRoot) {
18
+ this.workspaceRoot = workspaceRoot;
19
+ }
20
+ async read(stage) {
21
+ const filePath = getStateFilePath(this.workspaceRoot, stage);
22
+ try {
23
+ const content = await readFile(filePath, "utf-8");
24
+ return JSON.parse(content);
25
+ } catch (error) {
26
+ if (error.code === "ENOENT") return null;
27
+ console.warn(`Warning: Could not read deploy state: ${error}`);
28
+ return null;
29
+ }
30
+ }
31
+ async write(stage, state) {
32
+ const filePath = getStateFilePath(this.workspaceRoot, stage);
33
+ const dir = join(this.workspaceRoot, ".gkm");
34
+ await mkdir(dir, { recursive: true });
35
+ state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
36
+ await writeFile(filePath, JSON.stringify(state, null, 2));
37
+ }
38
+ };
39
+
40
+ //#endregion
41
+ export { LocalStateProvider };
42
+ //# sourceMappingURL=LocalStateProvider-DxoSaWUV.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalStateProvider-DxoSaWUV.mjs","names":["workspaceRoot: string","stage: string","state: DokployStageState"],"sources":["../src/deploy/LocalStateProvider.ts"],"sourcesContent":["/**\n * Local Filesystem State Provider\n *\n * Stores deployment state in .gkm/deploy-{stage}.json files.\n * This is the default provider for local development.\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/**\n * Get the state file path for a stage.\n */\nfunction getStateFilePath(workspaceRoot: string, stage: string): string {\n\treturn join(workspaceRoot, '.gkm', `deploy-${stage}.json`);\n}\n\n/**\n * Local filesystem state provider.\n *\n * Stores state in .gkm/deploy-{stage}.json files in the workspace root.\n */\nexport class LocalStateProvider implements StateProvider {\n\tconstructor(private readonly workspaceRoot: string) {}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\n\t\ttry {\n\t\t\tconst content = await readFile(filePath, 'utf-8');\n\t\t\treturn JSON.parse(content) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// File doesn't exist - return null\n\t\t\tif ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\t// Log other errors but don't fail\n\t\t\tconsole.warn(`Warning: Could not read deploy state: ${error}`);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\t\tconst dir = join(this.workspaceRoot, '.gkm');\n\n\t\t// Ensure .gkm directory exists\n\t\tawait mkdir(dir, { recursive: true });\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait writeFile(filePath, JSON.stringify(state, null, 2));\n\t}\n}\n"],"mappings":";;;;;;;AAeA,SAAS,iBAAiBA,eAAuBC,OAAuB;AACvE,QAAO,KAAK,eAAe,SAAS,SAAS,MAAM,OAAO;AAC1D;;;;;;AAOD,IAAa,qBAAb,MAAyD;CACxD,YAA6BD,eAAuB;EAAvB;CAAyB;CAEtD,MAAM,KAAKC,OAAkD;EAC5D,MAAM,WAAW,iBAAiB,KAAK,eAAe,MAAM;AAE5D,MAAI;GACH,MAAM,UAAU,MAAM,SAAS,UAAU,QAAQ;AACjD,UAAO,KAAK,MAAM,QAAQ;EAC1B,SAAQ,OAAO;AAEf,OAAK,MAAgC,SAAS,SAC7C,QAAO;AAGR,WAAQ,MAAM,wCAAwC,MAAM,EAAE;AAC9D,UAAO;EACP;CACD;CAED,MAAM,MAAMA,OAAeC,OAAyC;EACnE,MAAM,WAAW,iBAAiB,KAAK,eAAe,MAAM;EAC5D,MAAM,MAAM,KAAK,KAAK,eAAe,OAAO;AAG5C,QAAM,MAAM,KAAK,EAAE,WAAW,KAAM,EAAC;AAGrC,QAAM,iBAAiB,qBAAI,QAAO,aAAa;AAE/C,QAAM,UAAU,UAAU,KAAK,UAAU,OAAO,MAAM,EAAE,CAAC;CACzD;AACD"}
@@ -0,0 +1,157 @@
1
+ const require_chunk = require('./chunk-CUT6urMc.cjs');
2
+ const __aws_sdk_client_route_53 = require_chunk.__toESM(require("@aws-sdk/client-route-53"));
3
+ const __aws_sdk_credential_providers = require_chunk.__toESM(require("@aws-sdk/credential-providers"));
4
+
5
+ //#region src/deploy/dns/Route53Provider.ts
6
+ /**
7
+ * Route53 DNS provider implementation.
8
+ *
9
+ * Uses AWS default credential chain for authentication.
10
+ * Region can be specified or will use AWS_REGION/AWS_DEFAULT_REGION env vars.
11
+ * Profile can be specified to use a named profile from ~/.aws/credentials.
12
+ */
13
+ var Route53Provider = class {
14
+ name = "route53";
15
+ client;
16
+ hostedZoneId;
17
+ hostedZoneCache = /* @__PURE__ */ new Map();
18
+ constructor(options = {}) {
19
+ this.client = new __aws_sdk_client_route_53.Route53Client({
20
+ ...options.region && { region: options.region },
21
+ ...options.endpoint && { endpoint: options.endpoint },
22
+ ...options.profile && { credentials: (0, __aws_sdk_credential_providers.fromIni)({ profile: options.profile }) }
23
+ });
24
+ this.hostedZoneId = options.hostedZoneId;
25
+ }
26
+ /**
27
+ * Get the hosted zone ID for a domain.
28
+ * Uses cache to avoid repeated API calls.
29
+ */
30
+ async getHostedZoneId(domain) {
31
+ if (this.hostedZoneId) return this.hostedZoneId;
32
+ if (this.hostedZoneCache.has(domain)) return this.hostedZoneCache.get(domain);
33
+ const command = new __aws_sdk_client_route_53.ListHostedZonesByNameCommand({
34
+ DNSName: domain,
35
+ MaxItems: 1
36
+ });
37
+ const response = await this.client.send(command);
38
+ const zones = response.HostedZones ?? [];
39
+ const normalizedDomain = domain.endsWith(".") ? domain : `${domain}.`;
40
+ const zone = zones.find((z) => z.Name === normalizedDomain);
41
+ if (!zone?.Id) throw new Error(`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`);
42
+ const zoneId = zone.Id.replace("/hostedzone/", "");
43
+ this.hostedZoneCache.set(domain, zoneId);
44
+ return zoneId;
45
+ }
46
+ /**
47
+ * Convert Route53 record type to our DnsRecordType.
48
+ * Excludes NS and SOA which are auto-managed by Route53 for the zone.
49
+ */
50
+ toRecordType(type) {
51
+ const managedTypes = ["NS", "SOA"];
52
+ if (managedTypes.includes(type)) return null;
53
+ const validTypes = [
54
+ "A",
55
+ "AAAA",
56
+ "CNAME",
57
+ "MX",
58
+ "TXT",
59
+ "SRV",
60
+ "CAA"
61
+ ];
62
+ return validTypes.includes(type) ? type : null;
63
+ }
64
+ /**
65
+ * Extract subdomain from full record name relative to domain.
66
+ */
67
+ extractSubdomain(recordName, domain) {
68
+ const normalizedDomain = domain.endsWith(".") ? domain : `${domain}.`;
69
+ const normalizedName = recordName.endsWith(".") ? recordName : `${recordName}.`;
70
+ if (normalizedName === normalizedDomain) return "@";
71
+ const subdomain = normalizedName.replace(`.${normalizedDomain}`, "");
72
+ return subdomain.replace(/\.$/, "");
73
+ }
74
+ async getRecords(domain) {
75
+ const zoneId = await this.getHostedZoneId(domain);
76
+ const records = [];
77
+ let nextRecordName;
78
+ let nextRecordType;
79
+ do {
80
+ const command = new __aws_sdk_client_route_53.ListResourceRecordSetsCommand({
81
+ HostedZoneId: zoneId,
82
+ StartRecordName: nextRecordName,
83
+ StartRecordType: nextRecordType,
84
+ MaxItems: 100
85
+ });
86
+ const response = await this.client.send(command);
87
+ for (const recordSet of response.ResourceRecordSets ?? []) {
88
+ const type = this.toRecordType(recordSet.Type ?? "");
89
+ if (!type || !recordSet.Name) continue;
90
+ const values = (recordSet.ResourceRecords ?? []).map((r) => r.Value).filter((v) => !!v);
91
+ records.push({
92
+ name: this.extractSubdomain(recordSet.Name, domain),
93
+ type,
94
+ ttl: recordSet.TTL ?? 300,
95
+ values
96
+ });
97
+ }
98
+ if (response.IsTruncated) {
99
+ nextRecordName = response.NextRecordName;
100
+ nextRecordType = response.NextRecordType;
101
+ } else nextRecordName = void 0;
102
+ } while (nextRecordName);
103
+ return records;
104
+ }
105
+ async upsertRecords(domain, records) {
106
+ const zoneId = await this.getHostedZoneId(domain);
107
+ const results = [];
108
+ const existingRecords = await this.getRecords(domain);
109
+ const batchSize = 100;
110
+ for (let i = 0; i < records.length; i += batchSize) {
111
+ const batch = records.slice(i, i + batchSize);
112
+ const changes = [];
113
+ for (const record of batch) {
114
+ const existing = existingRecords.find((r) => r.name === record.name && r.type === record.type);
115
+ const existingValue = existing?.values?.[0];
116
+ if (existing && existingValue === record.value) {
117
+ results.push({
118
+ record,
119
+ created: false,
120
+ unchanged: true
121
+ });
122
+ continue;
123
+ }
124
+ const recordName = record.name === "@" ? domain : `${record.name}.${domain}`;
125
+ changes.push({
126
+ Action: "UPSERT",
127
+ ResourceRecordSet: {
128
+ Name: recordName,
129
+ Type: record.type,
130
+ TTL: record.ttl,
131
+ ResourceRecords: [{ Value: record.value }]
132
+ }
133
+ });
134
+ results.push({
135
+ record,
136
+ created: !existing,
137
+ unchanged: false
138
+ });
139
+ }
140
+ if (changes.length > 0) {
141
+ const command = new __aws_sdk_client_route_53.ChangeResourceRecordSetsCommand({
142
+ HostedZoneId: zoneId,
143
+ ChangeBatch: {
144
+ Comment: "Upsert by gkm deploy",
145
+ Changes: changes
146
+ }
147
+ });
148
+ await this.client.send(command);
149
+ }
150
+ }
151
+ return results;
152
+ }
153
+ };
154
+
155
+ //#endregion
156
+ exports.Route53Provider = Route53Provider;
157
+ //# sourceMappingURL=Route53Provider-CpRIqu69.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Route53Provider-CpRIqu69.cjs","names":["options: Route53ProviderOptions","Route53Client","domain: string","ListHostedZonesByNameCommand","type: string","validTypes: DnsRecordType[]","recordName: string","records: DnsRecord[]","nextRecordName: string | undefined","nextRecordType: RRType | undefined","ListResourceRecordSetsCommand","records: UpsertDnsRecord[]","results: UpsertResult[]","ChangeResourceRecordSetsCommand"],"sources":["../src/deploy/dns/Route53Provider.ts"],"sourcesContent":["/**\n * Route53 DNS Provider\n *\n * Implements DnsProvider interface using AWS Route53.\n */\n\nimport {\n\tChangeResourceRecordSetsCommand,\n\tListHostedZonesByNameCommand,\n\tListResourceRecordSetsCommand,\n\tRoute53Client,\n\ttype RRType,\n} from '@aws-sdk/client-route-53';\nimport { fromIni } from '@aws-sdk/credential-providers';\nimport type {\n\tDnsProvider,\n\tDnsRecord,\n\tDnsRecordType,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\n\nexport interface Route53ProviderOptions {\n\t/** AWS region (optional - uses AWS_REGION env var if not provided) */\n\tregion?: string;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n\t/** Hosted zone ID (optional - auto-detected from domain if not provided) */\n\thostedZoneId?: string;\n\t/** Custom endpoint for testing with localstack */\n\tendpoint?: string;\n}\n\n/**\n * Route53 DNS provider implementation.\n *\n * Uses AWS default credential chain for authentication.\n * Region can be specified or will use AWS_REGION/AWS_DEFAULT_REGION env vars.\n * Profile can be specified to use a named profile from ~/.aws/credentials.\n */\nexport class Route53Provider implements DnsProvider {\n\treadonly name = 'route53';\n\tprivate client: Route53Client;\n\tprivate hostedZoneId?: string;\n\tprivate hostedZoneCache: Map<string, string> = new Map();\n\n\tconstructor(options: Route53ProviderOptions = {}) {\n\t\tthis.client = new Route53Client({\n\t\t\t...(options.region && { region: options.region }),\n\t\t\t...(options.endpoint && { endpoint: options.endpoint }),\n\t\t\t...(options.profile && {\n\t\t\t\tcredentials: fromIni({ profile: options.profile }),\n\t\t\t}),\n\t\t});\n\t\tthis.hostedZoneId = options.hostedZoneId;\n\t}\n\n\t/**\n\t * Get the hosted zone ID for a domain.\n\t * Uses cache to avoid repeated API calls.\n\t */\n\tprivate async getHostedZoneId(domain: string): Promise<string> {\n\t\t// Use configured zone ID if provided\n\t\tif (this.hostedZoneId) {\n\t\t\treturn this.hostedZoneId;\n\t\t}\n\n\t\t// Check cache\n\t\tif (this.hostedZoneCache.has(domain)) {\n\t\t\treturn this.hostedZoneCache.get(domain)!;\n\t\t}\n\n\t\t// Auto-detect from domain\n\t\tconst command = new ListHostedZonesByNameCommand({\n\t\t\tDNSName: domain,\n\t\t\tMaxItems: 1,\n\t\t});\n\n\t\tconst response = await this.client.send(command);\n\t\tconst zones = response.HostedZones ?? [];\n\n\t\t// Find exact match (domain with trailing dot)\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst zone = zones.find((z) => z.Name === normalizedDomain);\n\n\t\tif (!zone?.Id) {\n\t\t\tthrow new Error(\n\t\t\t\t`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`,\n\t\t\t);\n\t\t}\n\n\t\t// Zone ID comes as \"/hostedzone/Z1234567890\" - extract just the ID\n\t\tconst zoneId = zone.Id.replace('/hostedzone/', '');\n\t\tthis.hostedZoneCache.set(domain, zoneId);\n\t\treturn zoneId;\n\t}\n\n\t/**\n\t * Convert Route53 record type to our DnsRecordType.\n\t * Excludes NS and SOA which are auto-managed by Route53 for the zone.\n\t */\n\tprivate toRecordType(type: string): DnsRecordType | null {\n\t\t// Exclude NS and SOA which are auto-managed zone records\n\t\tconst managedTypes = ['NS', 'SOA'];\n\t\tif (managedTypes.includes(type)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst validTypes: DnsRecordType[] = [\n\t\t\t'A',\n\t\t\t'AAAA',\n\t\t\t'CNAME',\n\t\t\t'MX',\n\t\t\t'TXT',\n\t\t\t'SRV',\n\t\t\t'CAA',\n\t\t];\n\t\treturn validTypes.includes(type as DnsRecordType)\n\t\t\t? (type as DnsRecordType)\n\t\t\t: null;\n\t}\n\n\t/**\n\t * Extract subdomain from full record name relative to domain.\n\t */\n\tprivate extractSubdomain(recordName: string, domain: string): string {\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst normalizedName = recordName.endsWith('.')\n\t\t\t? recordName\n\t\t\t: `${recordName}.`;\n\n\t\tif (normalizedName === normalizedDomain) {\n\t\t\treturn '@';\n\t\t}\n\n\t\t// Remove the domain suffix\n\t\tconst subdomain = normalizedName.replace(`.${normalizedDomain}`, '');\n\t\treturn subdomain.replace(/\\.$/, ''); // Remove trailing dot if any\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst records: DnsRecord[] = [];\n\n\t\tlet nextRecordName: string | undefined;\n\t\tlet nextRecordType: RRType | undefined;\n\n\t\t// Paginate through all records\n\t\tdo {\n\t\t\tconst command = new ListResourceRecordSetsCommand({\n\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\tStartRecordName: nextRecordName,\n\t\t\t\tStartRecordType: nextRecordType,\n\t\t\t\tMaxItems: 100,\n\t\t\t});\n\n\t\t\tconst response = await this.client.send(command);\n\n\t\t\tfor (const recordSet of response.ResourceRecordSets ?? []) {\n\t\t\t\tconst type = this.toRecordType(recordSet.Type ?? '');\n\t\t\t\tif (!type || !recordSet.Name) continue;\n\n\t\t\t\tconst values = (recordSet.ResourceRecords ?? [])\n\t\t\t\t\t.map((r) => r.Value)\n\t\t\t\t\t.filter((v): v is string => !!v);\n\n\t\t\t\trecords.push({\n\t\t\t\t\tname: this.extractSubdomain(recordSet.Name, domain),\n\t\t\t\t\ttype,\n\t\t\t\t\tttl: recordSet.TTL ?? 300,\n\t\t\t\t\tvalues,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (response.IsTruncated) {\n\t\t\t\tnextRecordName = response.NextRecordName;\n\t\t\t\tnextRecordType = response.NextRecordType;\n\t\t\t} else {\n\t\t\t\tnextRecordName = undefined;\n\t\t\t}\n\t\t} while (nextRecordName);\n\n\t\treturn records;\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to determine if creating or updating\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tconst existingValue = existing?.values?.[0];\n\n\t\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tcreated: false,\n\t\t\t\t\t\tunchanged: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'UPSERT' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: record.ttl,\n\t\t\t\t\t\tResourceRecords: [{ Value: record.value }],\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: !existing,\n\t\t\t\t\tunchanged: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\tComment: 'Upsert by gkm deploy',\n\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tawait this.client.send(command);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,156 @@
1
+ import { ChangeResourceRecordSetsCommand, ListHostedZonesByNameCommand, ListResourceRecordSetsCommand, Route53Client } from "@aws-sdk/client-route-53";
2
+ import { fromIni } from "@aws-sdk/credential-providers";
3
+
4
+ //#region src/deploy/dns/Route53Provider.ts
5
+ /**
6
+ * Route53 DNS provider implementation.
7
+ *
8
+ * Uses AWS default credential chain for authentication.
9
+ * Region can be specified or will use AWS_REGION/AWS_DEFAULT_REGION env vars.
10
+ * Profile can be specified to use a named profile from ~/.aws/credentials.
11
+ */
12
+ var Route53Provider = class {
13
+ name = "route53";
14
+ client;
15
+ hostedZoneId;
16
+ hostedZoneCache = /* @__PURE__ */ new Map();
17
+ constructor(options = {}) {
18
+ this.client = new Route53Client({
19
+ ...options.region && { region: options.region },
20
+ ...options.endpoint && { endpoint: options.endpoint },
21
+ ...options.profile && { credentials: fromIni({ profile: options.profile }) }
22
+ });
23
+ this.hostedZoneId = options.hostedZoneId;
24
+ }
25
+ /**
26
+ * Get the hosted zone ID for a domain.
27
+ * Uses cache to avoid repeated API calls.
28
+ */
29
+ async getHostedZoneId(domain) {
30
+ if (this.hostedZoneId) return this.hostedZoneId;
31
+ if (this.hostedZoneCache.has(domain)) return this.hostedZoneCache.get(domain);
32
+ const command = new ListHostedZonesByNameCommand({
33
+ DNSName: domain,
34
+ MaxItems: 1
35
+ });
36
+ const response = await this.client.send(command);
37
+ const zones = response.HostedZones ?? [];
38
+ const normalizedDomain = domain.endsWith(".") ? domain : `${domain}.`;
39
+ const zone = zones.find((z) => z.Name === normalizedDomain);
40
+ if (!zone?.Id) throw new Error(`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`);
41
+ const zoneId = zone.Id.replace("/hostedzone/", "");
42
+ this.hostedZoneCache.set(domain, zoneId);
43
+ return zoneId;
44
+ }
45
+ /**
46
+ * Convert Route53 record type to our DnsRecordType.
47
+ * Excludes NS and SOA which are auto-managed by Route53 for the zone.
48
+ */
49
+ toRecordType(type) {
50
+ const managedTypes = ["NS", "SOA"];
51
+ if (managedTypes.includes(type)) return null;
52
+ const validTypes = [
53
+ "A",
54
+ "AAAA",
55
+ "CNAME",
56
+ "MX",
57
+ "TXT",
58
+ "SRV",
59
+ "CAA"
60
+ ];
61
+ return validTypes.includes(type) ? type : null;
62
+ }
63
+ /**
64
+ * Extract subdomain from full record name relative to domain.
65
+ */
66
+ extractSubdomain(recordName, domain) {
67
+ const normalizedDomain = domain.endsWith(".") ? domain : `${domain}.`;
68
+ const normalizedName = recordName.endsWith(".") ? recordName : `${recordName}.`;
69
+ if (normalizedName === normalizedDomain) return "@";
70
+ const subdomain = normalizedName.replace(`.${normalizedDomain}`, "");
71
+ return subdomain.replace(/\.$/, "");
72
+ }
73
+ async getRecords(domain) {
74
+ const zoneId = await this.getHostedZoneId(domain);
75
+ const records = [];
76
+ let nextRecordName;
77
+ let nextRecordType;
78
+ do {
79
+ const command = new ListResourceRecordSetsCommand({
80
+ HostedZoneId: zoneId,
81
+ StartRecordName: nextRecordName,
82
+ StartRecordType: nextRecordType,
83
+ MaxItems: 100
84
+ });
85
+ const response = await this.client.send(command);
86
+ for (const recordSet of response.ResourceRecordSets ?? []) {
87
+ const type = this.toRecordType(recordSet.Type ?? "");
88
+ if (!type || !recordSet.Name) continue;
89
+ const values = (recordSet.ResourceRecords ?? []).map((r) => r.Value).filter((v) => !!v);
90
+ records.push({
91
+ name: this.extractSubdomain(recordSet.Name, domain),
92
+ type,
93
+ ttl: recordSet.TTL ?? 300,
94
+ values
95
+ });
96
+ }
97
+ if (response.IsTruncated) {
98
+ nextRecordName = response.NextRecordName;
99
+ nextRecordType = response.NextRecordType;
100
+ } else nextRecordName = void 0;
101
+ } while (nextRecordName);
102
+ return records;
103
+ }
104
+ async upsertRecords(domain, records) {
105
+ const zoneId = await this.getHostedZoneId(domain);
106
+ const results = [];
107
+ const existingRecords = await this.getRecords(domain);
108
+ const batchSize = 100;
109
+ for (let i = 0; i < records.length; i += batchSize) {
110
+ const batch = records.slice(i, i + batchSize);
111
+ const changes = [];
112
+ for (const record of batch) {
113
+ const existing = existingRecords.find((r) => r.name === record.name && r.type === record.type);
114
+ const existingValue = existing?.values?.[0];
115
+ if (existing && existingValue === record.value) {
116
+ results.push({
117
+ record,
118
+ created: false,
119
+ unchanged: true
120
+ });
121
+ continue;
122
+ }
123
+ const recordName = record.name === "@" ? domain : `${record.name}.${domain}`;
124
+ changes.push({
125
+ Action: "UPSERT",
126
+ ResourceRecordSet: {
127
+ Name: recordName,
128
+ Type: record.type,
129
+ TTL: record.ttl,
130
+ ResourceRecords: [{ Value: record.value }]
131
+ }
132
+ });
133
+ results.push({
134
+ record,
135
+ created: !existing,
136
+ unchanged: false
137
+ });
138
+ }
139
+ if (changes.length > 0) {
140
+ const command = new ChangeResourceRecordSetsCommand({
141
+ HostedZoneId: zoneId,
142
+ ChangeBatch: {
143
+ Comment: "Upsert by gkm deploy",
144
+ Changes: changes
145
+ }
146
+ });
147
+ await this.client.send(command);
148
+ }
149
+ }
150
+ return results;
151
+ }
152
+ };
153
+
154
+ //#endregion
155
+ export { Route53Provider };
156
+ //# sourceMappingURL=Route53Provider-KUAX3vz9.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Route53Provider-KUAX3vz9.mjs","names":["options: Route53ProviderOptions","domain: string","type: string","validTypes: DnsRecordType[]","recordName: string","records: DnsRecord[]","nextRecordName: string | undefined","nextRecordType: RRType | undefined","records: UpsertDnsRecord[]","results: UpsertResult[]"],"sources":["../src/deploy/dns/Route53Provider.ts"],"sourcesContent":["/**\n * Route53 DNS Provider\n *\n * Implements DnsProvider interface using AWS Route53.\n */\n\nimport {\n\tChangeResourceRecordSetsCommand,\n\tListHostedZonesByNameCommand,\n\tListResourceRecordSetsCommand,\n\tRoute53Client,\n\ttype RRType,\n} from '@aws-sdk/client-route-53';\nimport { fromIni } from '@aws-sdk/credential-providers';\nimport type {\n\tDnsProvider,\n\tDnsRecord,\n\tDnsRecordType,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\n\nexport interface Route53ProviderOptions {\n\t/** AWS region (optional - uses AWS_REGION env var if not provided) */\n\tregion?: string;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n\t/** Hosted zone ID (optional - auto-detected from domain if not provided) */\n\thostedZoneId?: string;\n\t/** Custom endpoint for testing with localstack */\n\tendpoint?: string;\n}\n\n/**\n * Route53 DNS provider implementation.\n *\n * Uses AWS default credential chain for authentication.\n * Region can be specified or will use AWS_REGION/AWS_DEFAULT_REGION env vars.\n * Profile can be specified to use a named profile from ~/.aws/credentials.\n */\nexport class Route53Provider implements DnsProvider {\n\treadonly name = 'route53';\n\tprivate client: Route53Client;\n\tprivate hostedZoneId?: string;\n\tprivate hostedZoneCache: Map<string, string> = new Map();\n\n\tconstructor(options: Route53ProviderOptions = {}) {\n\t\tthis.client = new Route53Client({\n\t\t\t...(options.region && { region: options.region }),\n\t\t\t...(options.endpoint && { endpoint: options.endpoint }),\n\t\t\t...(options.profile && {\n\t\t\t\tcredentials: fromIni({ profile: options.profile }),\n\t\t\t}),\n\t\t});\n\t\tthis.hostedZoneId = options.hostedZoneId;\n\t}\n\n\t/**\n\t * Get the hosted zone ID for a domain.\n\t * Uses cache to avoid repeated API calls.\n\t */\n\tprivate async getHostedZoneId(domain: string): Promise<string> {\n\t\t// Use configured zone ID if provided\n\t\tif (this.hostedZoneId) {\n\t\t\treturn this.hostedZoneId;\n\t\t}\n\n\t\t// Check cache\n\t\tif (this.hostedZoneCache.has(domain)) {\n\t\t\treturn this.hostedZoneCache.get(domain)!;\n\t\t}\n\n\t\t// Auto-detect from domain\n\t\tconst command = new ListHostedZonesByNameCommand({\n\t\t\tDNSName: domain,\n\t\t\tMaxItems: 1,\n\t\t});\n\n\t\tconst response = await this.client.send(command);\n\t\tconst zones = response.HostedZones ?? [];\n\n\t\t// Find exact match (domain with trailing dot)\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst zone = zones.find((z) => z.Name === normalizedDomain);\n\n\t\tif (!zone?.Id) {\n\t\t\tthrow new Error(\n\t\t\t\t`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`,\n\t\t\t);\n\t\t}\n\n\t\t// Zone ID comes as \"/hostedzone/Z1234567890\" - extract just the ID\n\t\tconst zoneId = zone.Id.replace('/hostedzone/', '');\n\t\tthis.hostedZoneCache.set(domain, zoneId);\n\t\treturn zoneId;\n\t}\n\n\t/**\n\t * Convert Route53 record type to our DnsRecordType.\n\t * Excludes NS and SOA which are auto-managed by Route53 for the zone.\n\t */\n\tprivate toRecordType(type: string): DnsRecordType | null {\n\t\t// Exclude NS and SOA which are auto-managed zone records\n\t\tconst managedTypes = ['NS', 'SOA'];\n\t\tif (managedTypes.includes(type)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst validTypes: DnsRecordType[] = [\n\t\t\t'A',\n\t\t\t'AAAA',\n\t\t\t'CNAME',\n\t\t\t'MX',\n\t\t\t'TXT',\n\t\t\t'SRV',\n\t\t\t'CAA',\n\t\t];\n\t\treturn validTypes.includes(type as DnsRecordType)\n\t\t\t? (type as DnsRecordType)\n\t\t\t: null;\n\t}\n\n\t/**\n\t * Extract subdomain from full record name relative to domain.\n\t */\n\tprivate extractSubdomain(recordName: string, domain: string): string {\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst normalizedName = recordName.endsWith('.')\n\t\t\t? recordName\n\t\t\t: `${recordName}.`;\n\n\t\tif (normalizedName === normalizedDomain) {\n\t\t\treturn '@';\n\t\t}\n\n\t\t// Remove the domain suffix\n\t\tconst subdomain = normalizedName.replace(`.${normalizedDomain}`, '');\n\t\treturn subdomain.replace(/\\.$/, ''); // Remove trailing dot if any\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst records: DnsRecord[] = [];\n\n\t\tlet nextRecordName: string | undefined;\n\t\tlet nextRecordType: RRType | undefined;\n\n\t\t// Paginate through all records\n\t\tdo {\n\t\t\tconst command = new ListResourceRecordSetsCommand({\n\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\tStartRecordName: nextRecordName,\n\t\t\t\tStartRecordType: nextRecordType,\n\t\t\t\tMaxItems: 100,\n\t\t\t});\n\n\t\t\tconst response = await this.client.send(command);\n\n\t\t\tfor (const recordSet of response.ResourceRecordSets ?? []) {\n\t\t\t\tconst type = this.toRecordType(recordSet.Type ?? '');\n\t\t\t\tif (!type || !recordSet.Name) continue;\n\n\t\t\t\tconst values = (recordSet.ResourceRecords ?? [])\n\t\t\t\t\t.map((r) => r.Value)\n\t\t\t\t\t.filter((v): v is string => !!v);\n\n\t\t\t\trecords.push({\n\t\t\t\t\tname: this.extractSubdomain(recordSet.Name, domain),\n\t\t\t\t\ttype,\n\t\t\t\t\tttl: recordSet.TTL ?? 300,\n\t\t\t\t\tvalues,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (response.IsTruncated) {\n\t\t\t\tnextRecordName = response.NextRecordName;\n\t\t\t\tnextRecordType = response.NextRecordType;\n\t\t\t} else {\n\t\t\t\tnextRecordName = undefined;\n\t\t\t}\n\t\t} while (nextRecordName);\n\n\t\treturn records;\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to determine if creating or updating\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tconst existingValue = existing?.values?.[0];\n\n\t\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tcreated: false,\n\t\t\t\t\t\tunchanged: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'UPSERT' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: record.ttl,\n\t\t\t\t\t\tResourceRecords: [{ Value: record.value }],\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: !existing,\n\t\t\t\t\tunchanged: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\tComment: 'Upsert by gkm deploy',\n\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tawait this.client.send(command);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ const require_chunk = require('./chunk-CUT6urMc.cjs');
2
+ const __aws_sdk_client_ssm = require_chunk.__toESM(require("@aws-sdk/client-ssm"));
3
+
4
+ //#region src/deploy/SSMStateProvider.ts
5
+ /**
6
+ * AWS SSM Parameter Store state provider.
7
+ *
8
+ * Stores state as encrypted SecureString parameters.
9
+ * Parameter path: /gkm/{workspaceName}/{stage}/state
10
+ */
11
+ var SSMStateProvider = class {
12
+ client;
13
+ workspaceName;
14
+ constructor(options) {
15
+ this.workspaceName = options.workspaceName;
16
+ this.client = new __aws_sdk_client_ssm.SSMClient({ region: options.region });
17
+ }
18
+ /**
19
+ * Get the SSM parameter name for a stage.
20
+ */
21
+ getParameterName(stage) {
22
+ return `/gkm/${this.workspaceName}/${stage}/state`;
23
+ }
24
+ async read(stage) {
25
+ const parameterName = this.getParameterName(stage);
26
+ try {
27
+ const response = await this.client.send(new __aws_sdk_client_ssm.GetParameterCommand({
28
+ Name: parameterName,
29
+ WithDecryption: true
30
+ }));
31
+ if (!response.Parameter?.Value) return null;
32
+ return JSON.parse(response.Parameter.Value);
33
+ } catch (error) {
34
+ if (error instanceof __aws_sdk_client_ssm.ParameterNotFound) return null;
35
+ throw error;
36
+ }
37
+ }
38
+ async write(stage, state) {
39
+ const parameterName = this.getParameterName(stage);
40
+ state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
41
+ await this.client.send(new __aws_sdk_client_ssm.PutParameterCommand({
42
+ Name: parameterName,
43
+ Value: JSON.stringify(state),
44
+ Type: "SecureString",
45
+ Overwrite: true,
46
+ Description: `GKM deployment state for ${this.workspaceName}/${stage}`
47
+ }));
48
+ }
49
+ };
50
+
51
+ //#endregion
52
+ exports.SSMStateProvider = SSMStateProvider;
53
+ //# sourceMappingURL=SSMStateProvider-BxAPU99a.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SSMStateProvider-BxAPU99a.cjs","names":["options: SSMStateProviderOptions","SSMClient","stage: string","GetParameterCommand","ParameterNotFound","state: DokployStageState","PutParameterCommand"],"sources":["../src/deploy/SSMStateProvider.ts"],"sourcesContent":["/**\n * AWS SSM Parameter Store State Provider\n *\n * Stores deployment state as SecureString parameters in AWS SSM.\n * Uses AWS-managed KMS key for encryption (free tier).\n *\n * Parameter naming: /gkm/{workspaceName}/{stage}/state\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n} from '@aws-sdk/client-ssm';\nimport type { AwsRegion, StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\nexport interface SSMStateProviderOptions {\n\t/** Workspace name (used in parameter path) */\n\tworkspaceName: string;\n\t/** AWS region (required) */\n\tregion: AwsRegion;\n}\n\n/**\n * AWS SSM Parameter Store state provider.\n *\n * Stores state as encrypted SecureString parameters.\n * Parameter path: /gkm/{workspaceName}/{stage}/state\n */\nexport class SSMStateProvider implements StateProvider {\n\tprivate readonly client: SSMClient;\n\tprivate readonly workspaceName: string;\n\n\tconstructor(options: SSMStateProviderOptions) {\n\t\tthis.workspaceName = options.workspaceName;\n\t\tthis.client = new SSMClient({\n\t\t\tregion: options.region,\n\t\t});\n\t}\n\n\t/**\n\t * Get the SSM parameter name for a stage.\n\t */\n\tprivate getParameterName(stage: string): string {\n\t\treturn `/gkm/${this.workspaceName}/${stage}/state`;\n\t}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst parameterName = this.getParameterName(stage);\n\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetParameterCommand({\n\t\t\t\t\tName: parameterName,\n\t\t\t\t\tWithDecryption: true,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tif (!response.Parameter?.Value) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn JSON.parse(response.Parameter.Value) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// Parameter doesn't exist - return null (new deployment)\n\t\t\tif (error instanceof ParameterNotFound) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Re-throw other errors (permission denied, network, etc.)\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst parameterName = this.getParameterName(stage);\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait this.client.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName: parameterName,\n\t\t\t\tValue: JSON.stringify(state),\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription: `GKM deployment state for ${this.workspaceName}/${stage}`,\n\t\t\t}),\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;AA+BA,IAAa,mBAAb,MAAuD;CACtD,AAAiB;CACjB,AAAiB;CAEjB,YAAYA,SAAkC;AAC7C,OAAK,gBAAgB,QAAQ;AAC7B,OAAK,SAAS,IAAIC,+BAAU,EAC3B,QAAQ,QAAQ,OAChB;CACD;;;;CAKD,AAAQ,iBAAiBC,OAAuB;AAC/C,UAAQ,OAAO,KAAK,cAAc,GAAG,MAAM;CAC3C;CAED,MAAM,KAAKA,OAAkD;EAC5D,MAAM,gBAAgB,KAAK,iBAAiB,MAAM;AAElD,MAAI;GACH,MAAM,WAAW,MAAM,KAAK,OAAO,KAClC,IAAIC,yCAAoB;IACvB,MAAM;IACN,gBAAgB;GAChB,GACD;AAED,QAAK,SAAS,WAAW,MACxB,QAAO;AAGR,UAAO,KAAK,MAAM,SAAS,UAAU,MAAM;EAC3C,SAAQ,OAAO;AAEf,OAAI,iBAAiBC,uCACpB,QAAO;AAIR,SAAM;EACN;CACD;CAED,MAAM,MAAMF,OAAeG,OAAyC;EACnE,MAAM,gBAAgB,KAAK,iBAAiB,MAAM;AAGlD,QAAM,iBAAiB,qBAAI,QAAO,aAAa;AAE/C,QAAM,KAAK,OAAO,KACjB,IAAIC,yCAAoB;GACvB,MAAM;GACN,OAAO,KAAK,UAAU,MAAM;GAC5B,MAAM;GACN,WAAW;GACX,cAAc,2BAA2B,KAAK,cAAc,GAAG,MAAM;EACrE,GACD;CACD;AACD"}
@@ -0,0 +1,52 @@
1
+ import { GetParameterCommand, ParameterNotFound, PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
2
+
3
+ //#region src/deploy/SSMStateProvider.ts
4
+ /**
5
+ * AWS SSM Parameter Store state provider.
6
+ *
7
+ * Stores state as encrypted SecureString parameters.
8
+ * Parameter path: /gkm/{workspaceName}/{stage}/state
9
+ */
10
+ var SSMStateProvider = class {
11
+ client;
12
+ workspaceName;
13
+ constructor(options) {
14
+ this.workspaceName = options.workspaceName;
15
+ this.client = new SSMClient({ region: options.region });
16
+ }
17
+ /**
18
+ * Get the SSM parameter name for a stage.
19
+ */
20
+ getParameterName(stage) {
21
+ return `/gkm/${this.workspaceName}/${stage}/state`;
22
+ }
23
+ async read(stage) {
24
+ const parameterName = this.getParameterName(stage);
25
+ try {
26
+ const response = await this.client.send(new GetParameterCommand({
27
+ Name: parameterName,
28
+ WithDecryption: true
29
+ }));
30
+ if (!response.Parameter?.Value) return null;
31
+ return JSON.parse(response.Parameter.Value);
32
+ } catch (error) {
33
+ if (error instanceof ParameterNotFound) return null;
34
+ throw error;
35
+ }
36
+ }
37
+ async write(stage, state) {
38
+ const parameterName = this.getParameterName(stage);
39
+ state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
40
+ await this.client.send(new PutParameterCommand({
41
+ Name: parameterName,
42
+ Value: JSON.stringify(state),
43
+ Type: "SecureString",
44
+ Overwrite: true,
45
+ Description: `GKM deployment state for ${this.workspaceName}/${stage}`
46
+ }));
47
+ }
48
+ };
49
+
50
+ //#endregion
51
+ export { SSMStateProvider };
52
+ //# sourceMappingURL=SSMStateProvider-C4wp4AZe.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SSMStateProvider-C4wp4AZe.mjs","names":["options: SSMStateProviderOptions","stage: string","state: DokployStageState"],"sources":["../src/deploy/SSMStateProvider.ts"],"sourcesContent":["/**\n * AWS SSM Parameter Store State Provider\n *\n * Stores deployment state as SecureString parameters in AWS SSM.\n * Uses AWS-managed KMS key for encryption (free tier).\n *\n * Parameter naming: /gkm/{workspaceName}/{stage}/state\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n} from '@aws-sdk/client-ssm';\nimport type { AwsRegion, StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\nexport interface SSMStateProviderOptions {\n\t/** Workspace name (used in parameter path) */\n\tworkspaceName: string;\n\t/** AWS region (required) */\n\tregion: AwsRegion;\n}\n\n/**\n * AWS SSM Parameter Store state provider.\n *\n * Stores state as encrypted SecureString parameters.\n * Parameter path: /gkm/{workspaceName}/{stage}/state\n */\nexport class SSMStateProvider implements StateProvider {\n\tprivate readonly client: SSMClient;\n\tprivate readonly workspaceName: string;\n\n\tconstructor(options: SSMStateProviderOptions) {\n\t\tthis.workspaceName = options.workspaceName;\n\t\tthis.client = new SSMClient({\n\t\t\tregion: options.region,\n\t\t});\n\t}\n\n\t/**\n\t * Get the SSM parameter name for a stage.\n\t */\n\tprivate getParameterName(stage: string): string {\n\t\treturn `/gkm/${this.workspaceName}/${stage}/state`;\n\t}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst parameterName = this.getParameterName(stage);\n\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetParameterCommand({\n\t\t\t\t\tName: parameterName,\n\t\t\t\t\tWithDecryption: true,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tif (!response.Parameter?.Value) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn JSON.parse(response.Parameter.Value) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// Parameter doesn't exist - return null (new deployment)\n\t\t\tif (error instanceof ParameterNotFound) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Re-throw other errors (permission denied, network, etc.)\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst parameterName = this.getParameterName(stage);\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait this.client.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName: parameterName,\n\t\t\t\tValue: JSON.stringify(state),\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription: `GKM deployment state for ${this.workspaceName}/${stage}`,\n\t\t\t}),\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;AA+BA,IAAa,mBAAb,MAAuD;CACtD,AAAiB;CACjB,AAAiB;CAEjB,YAAYA,SAAkC;AAC7C,OAAK,gBAAgB,QAAQ;AAC7B,OAAK,SAAS,IAAI,UAAU,EAC3B,QAAQ,QAAQ,OAChB;CACD;;;;CAKD,AAAQ,iBAAiBC,OAAuB;AAC/C,UAAQ,OAAO,KAAK,cAAc,GAAG,MAAM;CAC3C;CAED,MAAM,KAAKA,OAAkD;EAC5D,MAAM,gBAAgB,KAAK,iBAAiB,MAAM;AAElD,MAAI;GACH,MAAM,WAAW,MAAM,KAAK,OAAO,KAClC,IAAI,oBAAoB;IACvB,MAAM;IACN,gBAAgB;GAChB,GACD;AAED,QAAK,SAAS,WAAW,MACxB,QAAO;AAGR,UAAO,KAAK,MAAM,SAAS,UAAU,MAAM;EAC3C,SAAQ,OAAO;AAEf,OAAI,iBAAiB,kBACpB,QAAO;AAIR,SAAM;EACN;CACD;CAED,MAAM,MAAMA,OAAeC,OAAyC;EACnE,MAAM,gBAAgB,KAAK,iBAAiB,MAAM;AAGlD,QAAM,iBAAiB,qBAAI,QAAO,aAAa;AAE/C,QAAM,KAAK,OAAO,KACjB,IAAI,oBAAoB;GACvB,MAAM;GACN,OAAO,KAAK,UAAU,MAAM;GAC5B,MAAM;GACN,WAAW;GACX,cAAc,2BAA2B,KAAK,cAAc,GAAG,MAAM;EACrE,GACD;CACD;AACD"}
@@ -58,8 +58,8 @@ async function bundleServer(options) {
58
58
  for (const ext of external) args.push(`--external:${ext}`);
59
59
  let masterKey;
60
60
  if (stage) {
61
- const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets, writeStageSecrets } = await import("./storage-DNj_I11J.mjs");
62
- const { encryptSecrets, generateDefineOptions } = await import("./encryption-CQXBZGkt.mjs");
61
+ const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets, writeStageSecrets } = await import("./storage-D8XzjVaO.mjs");
62
+ const { encryptSecrets, generateDefineOptions } = await import("./encryption-UUmaWAmz.mjs");
63
63
  let secrets = await readStageSecrets(stage);
64
64
  if (!secrets) {
65
65
  console.log(` Initializing secrets for stage "${stage}"...`);
@@ -132,4 +132,4 @@ async function bundleServer(options) {
132
132
 
133
133
  //#endregion
134
134
  export { bundleServer };
135
- //# sourceMappingURL=bundler-DGry2vaR.mjs.map
135
+ //# sourceMappingURL=bundler-BqTN5Dj5.mjs.map