@geekmidas/cli 10.0.0-alpha.59 → 10.0.0-alpha.60

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 (150) hide show
  1. package/README.md +18 -1
  2. package/dist/{HostingerProvider-BZFGYYVA.cjs → HostingerProvider-BTZd2u0f.cjs} +3 -3
  3. package/dist/{HostingerProvider-BZFGYYVA.cjs.map → HostingerProvider-BTZd2u0f.cjs.map} +1 -1
  4. package/dist/{HostingerProvider-YQY3rMpV.mjs → HostingerProvider-DgzKa_Tj.mjs} +2 -2
  5. package/dist/{HostingerProvider-YQY3rMpV.mjs.map → HostingerProvider-DgzKa_Tj.mjs.map} +1 -1
  6. package/dist/{LegacyStateStore-CB4ch3Dl.mjs → LegacyStateStore-CToa2hga.mjs} +2 -2
  7. package/dist/{LegacyStateStore-CB4ch3Dl.mjs.map → LegacyStateStore-CToa2hga.mjs.map} +1 -1
  8. package/dist/{LegacyStateStore-CQJqvmXw.cjs → LegacyStateStore-CidlPT0N.cjs} +7 -7
  9. package/dist/{LegacyStateStore-CQJqvmXw.cjs.map → LegacyStateStore-CidlPT0N.cjs.map} +1 -1
  10. package/dist/LocalStateStore-BFLiOocU.mjs +165 -0
  11. package/dist/LocalStateStore-BFLiOocU.mjs.map +1 -0
  12. package/dist/LocalStateStore-Dpuqka1N.cjs +176 -0
  13. package/dist/LocalStateStore-Dpuqka1N.cjs.map +1 -0
  14. package/dist/{OpenApiTsGenerator-BCWPRZwD.cjs → OpenApiTsGenerator-BTuUumsV.cjs} +3 -3
  15. package/dist/{OpenApiTsGenerator-BCWPRZwD.cjs.map → OpenApiTsGenerator-BTuUumsV.cjs.map} +1 -1
  16. package/dist/{OpenApiTsGenerator-BRWD1Wit.mjs → OpenApiTsGenerator-CvVr1Ilz.mjs} +3 -3
  17. package/dist/{OpenApiTsGenerator-BRWD1Wit.mjs.map → OpenApiTsGenerator-CvVr1Ilz.mjs.map} +1 -1
  18. package/dist/{S3StateStore-CXTenDTW.mjs → S3StateStore-aaGkjcxO.mjs} +2 -2
  19. package/dist/{S3StateStore-CXTenDTW.mjs.map → S3StateStore-aaGkjcxO.mjs.map} +1 -1
  20. package/dist/{S3StateStore-DLS4rEc0.cjs → S3StateStore-cnm813Xn.cjs} +5 -5
  21. package/dist/{S3StateStore-DLS4rEc0.cjs.map → S3StateStore-cnm813Xn.cjs.map} +1 -1
  22. package/dist/{SSMStateStore-hnP5deFJ.mjs → SSMStateStore-BllaJKGo.mjs} +2 -2
  23. package/dist/{SSMStateStore-hnP5deFJ.mjs.map → SSMStateStore-BllaJKGo.mjs.map} +1 -1
  24. package/dist/{SSMStateStore-EqsTBIOs.cjs → SSMStateStore-DwaGprVX.cjs} +9 -9
  25. package/dist/{SSMStateStore-EqsTBIOs.cjs.map → SSMStateStore-DwaGprVX.cjs.map} +1 -1
  26. package/dist/appPorts-C9z9Pzn9.mjs +223 -0
  27. package/dist/appPorts-C9z9Pzn9.mjs.map +1 -0
  28. package/dist/appPorts-Cu79hJF6.cjs +264 -0
  29. package/dist/appPorts-Cu79hJF6.cjs.map +1 -0
  30. package/dist/{bundler-By6_sXiL.cjs → bundler-CzXZPSBJ.cjs} +4 -4
  31. package/dist/{bundler-By6_sXiL.cjs.map → bundler-CzXZPSBJ.cjs.map} +1 -1
  32. package/dist/{bundler-ouDdNvk9.mjs → bundler-DJVR4QlW.mjs} +4 -4
  33. package/dist/{bundler-ouDdNvk9.mjs.map → bundler-DJVR4QlW.mjs.map} +1 -1
  34. package/dist/cli-D_qTl20Z.cjs +69 -0
  35. package/dist/cli-D_qTl20Z.cjs.map +1 -0
  36. package/dist/cli-DzR1hpum.mjs +69 -0
  37. package/dist/cli-DzR1hpum.mjs.map +1 -0
  38. package/dist/{config-EuzFEkcH.mjs → config-C5-EEdmr.mjs} +4 -4
  39. package/dist/config-C5-EEdmr.mjs.map +1 -0
  40. package/dist/{config-WMOB9bLL.cjs → config-DXnkCEq0.cjs} +4 -4
  41. package/dist/config-DXnkCEq0.cjs.map +1 -0
  42. package/dist/config.cjs +1 -1
  43. package/dist/config.d.cts +2 -2
  44. package/dist/config.d.cts.map +1 -1
  45. package/dist/config.d.mts +2 -2
  46. package/dist/config.d.mts.map +1 -1
  47. package/dist/config.mjs +1 -1
  48. package/dist/deploy/public.cjs +22 -0
  49. package/dist/deploy/public.d.cts +459 -0
  50. package/dist/deploy/public.d.cts.map +1 -0
  51. package/dist/deploy/public.d.mts +459 -0
  52. package/dist/deploy/public.d.mts.map +1 -0
  53. package/dist/deploy/public.mjs +5 -0
  54. package/dist/deploy/sniffer-hooks.cjs +1 -1
  55. package/dist/deploy/sniffer-hooks.mjs +1 -1
  56. package/dist/deploy/sniffer-loader.cjs +1 -1
  57. package/dist/deploy/sniffer-loader.mjs +1 -1
  58. package/dist/deploy-0fBVeWO-.cjs +3518 -0
  59. package/dist/deploy-0fBVeWO-.cjs.map +1 -0
  60. package/dist/deploy-D6Q09OkA.mjs +3429 -0
  61. package/dist/deploy-D6Q09OkA.mjs.map +1 -0
  62. package/dist/{discover-DXNviAh-.cjs → discover-Bc-4eA_p.cjs} +55 -3
  63. package/dist/discover-Bc-4eA_p.cjs.map +1 -0
  64. package/dist/{discover-Cf2jAcgF.mjs → discover-FrYvzkeC.mjs} +44 -4
  65. package/dist/discover-FrYvzkeC.mjs.map +1 -0
  66. package/dist/{encryption-BP8zfxEJ.cjs → encryption-CML4UaPi.cjs} +5 -5
  67. package/dist/{encryption-BP8zfxEJ.cjs.map → encryption-CML4UaPi.cjs.map} +1 -1
  68. package/dist/{encryption-BW7Gh51G.mjs → encryption-aDA2AzVx.mjs} +4 -4
  69. package/dist/{encryption-BW7Gh51G.mjs.map → encryption-aDA2AzVx.mjs.map} +1 -1
  70. package/dist/fakes-C9RiT6XG.mjs +125 -0
  71. package/dist/fakes-C9RiT6XG.mjs.map +1 -0
  72. package/dist/fakes-zW-jNAbQ.cjs +148 -0
  73. package/dist/fakes-zW-jNAbQ.cjs.map +1 -0
  74. package/dist/{github-DHpDMBT7.cjs → github-Cq57poPj.cjs} +7 -7
  75. package/dist/github-Cq57poPj.cjs.map +1 -0
  76. package/dist/{github-T_6sK3Rz.mjs → github-DmCj2u95.mjs} +7 -7
  77. package/dist/github-DmCj2u95.mjs.map +1 -0
  78. package/dist/home-DOzBaWaL.cjs +33 -0
  79. package/dist/home-DOzBaWaL.cjs.map +1 -0
  80. package/dist/home-GI7pJl1M.mjs +22 -0
  81. package/dist/home-GI7pJl1M.mjs.map +1 -0
  82. package/dist/{index-DV6j4Ydq.d.cts → index-Da1-oa9W.d.mts} +2 -2
  83. package/dist/{index-B5TJ-mB6.d.mts.map → index-Da1-oa9W.d.mts.map} +1 -1
  84. package/dist/{index-B5TJ-mB6.d.mts → index-UiWCaGzU.d.cts} +2 -2
  85. package/dist/{index-DV6j4Ydq.d.cts.map → index-UiWCaGzU.d.cts.map} +1 -1
  86. package/dist/index.cjs +341 -6412
  87. package/dist/index.cjs.map +1 -1
  88. package/dist/index.mjs +289 -6335
  89. package/dist/index.mjs.map +1 -1
  90. package/dist/{migrate-CThJfVHR.cjs → migrate-BTqIflwc.cjs} +11 -11
  91. package/dist/{migrate-CThJfVHR.cjs.map → migrate-BTqIflwc.cjs.map} +1 -1
  92. package/dist/{migrate-BrFREmgQ.mjs → migrate-B_6KofUo.mjs} +6 -6
  93. package/dist/{migrate-BrFREmgQ.mjs.map → migrate-B_6KofUo.mjs.map} +1 -1
  94. package/dist/openapi.cjs +3 -3
  95. package/dist/openapi.d.cts +1 -1
  96. package/dist/openapi.d.mts +1 -1
  97. package/dist/openapi.mjs +3 -3
  98. package/dist/{ready-mR96orkJ.cjs → ready-CCLIAkwR.cjs} +7 -148
  99. package/dist/ready-CCLIAkwR.cjs.map +1 -0
  100. package/dist/{ready-DbDHneLR.mjs → ready-CIHpnUc5.mjs} +7 -124
  101. package/dist/ready-CIHpnUc5.mjs.map +1 -0
  102. package/dist/reconcile/public.cjs +1 -1
  103. package/dist/reconcile/public.d.cts +1 -1
  104. package/dist/reconcile/public.d.cts.map +1 -1
  105. package/dist/reconcile/public.d.mts +1 -1
  106. package/dist/reconcile/public.d.mts.map +1 -1
  107. package/dist/reconcile/public.mjs +1 -1
  108. package/dist/{workspace-BRZ14o-o.mjs → sniffer-BM8fpCas.mjs} +3317 -27
  109. package/dist/sniffer-BM8fpCas.mjs.map +1 -0
  110. package/dist/{workspace-Dbn07ILl.cjs → sniffer-DIv12ncU.cjs} +3616 -91
  111. package/dist/sniffer-DIv12ncU.cjs.map +1 -0
  112. package/dist/store-BS8A7NcA.mjs +415 -0
  113. package/dist/store-BS8A7NcA.mjs.map +1 -0
  114. package/dist/store-KvNQwYaB.cjs +510 -0
  115. package/dist/store-KvNQwYaB.cjs.map +1 -0
  116. package/dist/{types-CipIIm8f.d.mts → types-Bp1mVxG0.d.mts} +17 -4
  117. package/dist/types-Bp1mVxG0.d.mts.map +1 -0
  118. package/dist/{types-D9b-IYdW.d.cts → types-efNLu9xu.d.cts} +17 -4
  119. package/dist/types-efNLu9xu.d.cts.map +1 -0
  120. package/dist/vitest.cjs +2 -2
  121. package/dist/vitest.mjs +2 -2
  122. package/dist/workspace/index.d.cts +2 -2
  123. package/dist/workspace/index.d.mts +2 -2
  124. package/dist/workspace-B64Xk1Mb.cjs.map +1 -1
  125. package/dist/workspace-Bp1xVaOz.mjs.map +1 -1
  126. package/package.json +23 -13
  127. package/dist/StateStore-B4u0wx2t.cjs +0 -412
  128. package/dist/StateStore-B4u0wx2t.cjs.map +0 -1
  129. package/dist/StateStore-CYUfGYcx.mjs +0 -359
  130. package/dist/StateStore-CYUfGYcx.mjs.map +0 -1
  131. package/dist/backends-CjuCiE8h.mjs +0 -59
  132. package/dist/backends-CjuCiE8h.mjs.map +0 -1
  133. package/dist/backends-v3xDuhO0.cjs +0 -82
  134. package/dist/backends-v3xDuhO0.cjs.map +0 -1
  135. package/dist/config-EuzFEkcH.mjs.map +0 -1
  136. package/dist/config-WMOB9bLL.cjs.map +0 -1
  137. package/dist/discover-Cf2jAcgF.mjs.map +0 -1
  138. package/dist/discover-DXNviAh-.cjs.map +0 -1
  139. package/dist/github-DHpDMBT7.cjs.map +0 -1
  140. package/dist/github-T_6sK3Rz.mjs.map +0 -1
  141. package/dist/ready-DbDHneLR.mjs.map +0 -1
  142. package/dist/ready-mR96orkJ.cjs.map +0 -1
  143. package/dist/store-DQgxT_WR.cjs +0 -211
  144. package/dist/store-DQgxT_WR.cjs.map +0 -1
  145. package/dist/store-DeGATCS-.mjs +0 -182
  146. package/dist/store-DeGATCS-.mjs.map +0 -1
  147. package/dist/types-CipIIm8f.d.mts.map +0 -1
  148. package/dist/types-D9b-IYdW.d.cts.map +0 -1
  149. package/dist/workspace-BRZ14o-o.mjs.map +0 -1
  150. package/dist/workspace-Dbn07ILl.cjs.map +0 -1
package/README.md CHANGED
@@ -786,7 +786,7 @@ Store credentials locally to avoid setting environment variables for every comma
786
786
 
787
787
  ### `gkm login`
788
788
 
789
- Authenticate with a deployment service. Credentials are stored in `~/.gkm/credentials.json`.
789
+ Authenticate with a deployment service. Credentials are stored in `~/.gkm/credentials.json` (`$GKM_HOME/credentials.json` when `GKM_HOME` is set).
790
790
 
791
791
  ```bash
792
792
  gkm login [options]
@@ -872,6 +872,20 @@ gkm deploy --provider <provider> --stage <stage> [options]
872
872
  - `--tag <tag>`: Image tag (default: `stage-timestamp`)
873
873
  - `--skip-push`: Skip pushing image to registry
874
874
  - `--skip-build`: Skip build step (use existing build)
875
+ - `--json`: Write the deploy's events as JSON lines on stdout instead of progress; never prompts
876
+ - `--dry-run`: Show what would be created or reused, and change, build and push nothing
877
+
878
+ At a terminal a missing Dokploy or registry login is asked for (and the Dokploy one stored). With `--json` or without a terminal nothing is asked: the deploy stops with `MissingCredential` and exits 1.
879
+
880
+ **From a program:** the same deploy is `deploy()` from `@geekmidas/cli/deploy` — explicit `cwd`, an injected `CredentialProvider`, a logger and an `AbortSignal`; it returns a run you iterate for JSON events, plus a `result` promise. It never prompts, prints or exits.
881
+
882
+ ```typescript
883
+ import { deploy } from '@geekmidas/cli/deploy';
884
+
885
+ const run = deploy({ cwd: '/srv/shop', stage: 'production', signal });
886
+ for await (const event of run) console.log(event.type);
887
+ const result = await run.result;
888
+ ```
875
889
 
876
890
  **Examples:**
877
891
  ```bash
@@ -932,6 +946,9 @@ export default defineConfig({
932
946
 
933
947
  **Environment Variables:**
934
948
  - `DOKPLOY_API_TOKEN`: API token for Dokploy (not needed if logged in via `gkm login`)
949
+ - `DOKPLOY_ENDPOINT`: Dokploy URL, if neither the stored login nor `deploy.dokploy.endpoint` gives one
950
+ - `DOCKER_REGISTRY_USERNAME` / `DOCKER_REGISTRY_PASSWORD`: a registry login, used only when Dokploy has no registry for `deploy.dokploy.registry` and one has to be created
951
+ - `GKM_HOME`: where stage keys and stored logins live (default `~/.gkm`)
935
952
  - `GKM_MASTER_KEY`: Automatically set by Dokploy, or manually for Docker deployments — from `.gkm/server/master.key`, which `gkm build --stage` writes (the key is never printed; output shows its fingerprint)
936
953
 
937
954
  ### `gkm deploy:init`
@@ -1,4 +1,4 @@
1
- const require_index = require("./index.cjs");
1
+ const require_sniffer = require("./sniffer-DIv12ncU.cjs");
2
2
  //#region src/deploy/dns/hostinger-api.ts
3
3
  /**
4
4
  * Hostinger DNS API client
@@ -164,7 +164,7 @@ var HostingerProvider = class {
164
164
  */
165
165
  async getApi() {
166
166
  if (this.api) return this.api;
167
- const token = await require_index.getHostingerToken();
167
+ const token = await require_sniffer.getHostingerToken();
168
168
  if (!token) throw new Error("Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api");
169
169
  this.api = new HostingerApi(token);
170
170
  return this.api;
@@ -240,4 +240,4 @@ var HostingerProvider = class {
240
240
  //#endregion
241
241
  exports.HostingerProvider = HostingerProvider;
242
242
 
243
- //# sourceMappingURL=HostingerProvider-BZFGYYVA.cjs.map
243
+ //# sourceMappingURL=HostingerProvider-BTZd2u0f.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"HostingerProvider-BZFGYYVA.cjs","names":["getHostingerToken"],"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\t// The zone comes back as a **bare array**, not `{ data: [...] }`.\n\t\t//\n\t\t// This read `response.data || []`, so every call returned zero records\n\t\t// against the real API while every mocked test passed. Silently: no error,\n\t\t// no warning, just an empty zone. `upsertRecords` uses this to decide what\n\t\t// already exists, so everything looked new and every write was reported as\n\t\t// a creation — harmless only because the PUT merges rather than replaces.\n\t\t//\n\t\t// Both shapes are accepted because one of them is a guess: the wrapper was\n\t\t// written against documentation and the array is what the API actually\n\t\t// sends, so tolerating an envelope costs a line and survives it appearing.\n\t\tconst response = await this.request<DnsRecord[] | { data?: DnsRecord[] }>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\tif (Array.isArray(response)) return response;\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\tDeleteDnsRecord,\n\tDeleteResult,\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\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to check what exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\t// Filter to only records that exist\n\t\tconst recordsToDelete = records.filter((record) =>\n\t\t\texistingRecords.some(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t),\n\t\t);\n\n\t\t// Delete existing records\n\t\tif (recordsToDelete.length > 0) {\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecords(\n\t\t\t\t\tdomain,\n\t\t\t\t\trecordsToDelete.map((r) => ({\n\t\t\t\t\t\tname: r.name,\n\t\t\t\t\t\ttype: r.type as\n\t\t\t\t\t\t\t| 'A'\n\t\t\t\t\t\t\t| 'AAAA'\n\t\t\t\t\t\t\t| 'CNAME'\n\t\t\t\t\t\t\t| 'MX'\n\t\t\t\t\t\t\t| 'TXT'\n\t\t\t\t\t\t\t| 'SRV'\n\t\t\t\t\t\t\t| 'CAA',\n\t\t\t\t\t})),\n\t\t\t\t);\n\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If batch delete fails, report error for all records\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t\terror: String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Mark non-existent records as not found\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\t\t\tif (!existing) {\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\tnotFound: true,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"HostingerProvider-BTZd2u0f.cjs","names":["getHostingerToken"],"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\t// The zone comes back as a **bare array**, not `{ data: [...] }`.\n\t\t//\n\t\t// This read `response.data || []`, so every call returned zero records\n\t\t// against the real API while every mocked test passed. Silently: no error,\n\t\t// no warning, just an empty zone. `upsertRecords` uses this to decide what\n\t\t// already exists, so everything looked new and every write was reported as\n\t\t// a creation — harmless only because the PUT merges rather than replaces.\n\t\t//\n\t\t// Both shapes are accepted because one of them is a guess: the wrapper was\n\t\t// written against documentation and the array is what the API actually\n\t\t// sends, so tolerating an envelope costs a line and survives it appearing.\n\t\tconst response = await this.request<DnsRecord[] | { data?: DnsRecord[] }>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\tif (Array.isArray(response)) return response;\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\tDeleteDnsRecord,\n\tDeleteResult,\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\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to check what exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\t// Filter to only records that exist\n\t\tconst recordsToDelete = records.filter((record) =>\n\t\t\texistingRecords.some(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t),\n\t\t);\n\n\t\t// Delete existing records\n\t\tif (recordsToDelete.length > 0) {\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecords(\n\t\t\t\t\tdomain,\n\t\t\t\t\trecordsToDelete.map((r) => ({\n\t\t\t\t\t\tname: r.name,\n\t\t\t\t\t\ttype: r.type as\n\t\t\t\t\t\t\t| 'A'\n\t\t\t\t\t\t\t| 'AAAA'\n\t\t\t\t\t\t\t| 'CNAME'\n\t\t\t\t\t\t\t| 'MX'\n\t\t\t\t\t\t\t| 'TXT'\n\t\t\t\t\t\t\t| 'SRV'\n\t\t\t\t\t\t\t| 'CAA',\n\t\t\t\t\t})),\n\t\t\t\t);\n\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If batch delete fails, report error for all records\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t\terror: String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Mark non-existent records as not found\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\t\t\tif (!existing) {\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\tnotFound: true,\n\t\t\t\t});\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;CAGpC;CACA;CACA;CAJR,YACC,SACA,QACA,YACA,QACC;EACD,MAAM,OAAO;EAJN,KAAA,SAAA;EACA,KAAA,aAAA;EACA,KAAA,SAAA;EAGP,KAAK,OAAO;CACb;AACD;;;;;;;;;;;;;;;;;AAkBA,IAAa,eAAb,MAA0B;CACzB;CAEA,YAAY,OAAe;EAC1B,KAAK,QAAQ;CACd;;;;CAKA,MAAc,QACb,QACA,UACA,MACa;EACb,MAAM,MAAM,GAAG,qBAAqB;EAEpC,MAAM,WAAW,MAAM,MAAM,KAAK;GACjC;GACA,SAAS;IACR,gBAAgB;IAChB,eAAe,UAAU,KAAK;GAC/B;GACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI,KAAA;EACrC,CAAC;EAED,IAAI,CAAC,SAAS,IAAI;GACjB,IAAI,eAAe,wBAAwB,SAAS,OAAO,GAAG,SAAS;GACvE,IAAI;GAEJ,IAAI;IACH,MAAM,YAAa,MAAM,SAAS,KAAK;IACvC,IAAI,UAAU,SACb,eAAe,wBAAwB,UAAU;IAElD,SAAS,UAAU;GACpB,QAAQ,CAER;GAEA,MAAM,IAAI,kBACT,cACA,SAAS,QACT,SAAS,YACT,MACD;EACD;EAGA,MAAM,OAAO,MAAM,SAAS,KAAK;EACjC,IAAI,CAAC,QAAQ,KAAK,KAAK,MAAM,IAC5B;EAED,OAAO,KAAK,MAAM,IAAI;CACvB;;;;;;CAOA,MAAM,WAAW,QAAsC;EAYtD,MAAM,WAAW,MAAM,KAAK,QAC3B,OACA,qBAAqB,QACtB;EAEA,IAAI,MAAM,QAAQ,QAAQ,GAAG,OAAO;EAEpC,OAAO,UAAU,QAAQ,CAAC;CAC3B;;;;;;;;CASA,MAAM,cACL,QACA,SACA,YAAY,OACI;EAChB,MAAM,KAAK,QAAQ,OAAO,qBAAqB,UAAU;GACxD;GACA,MAAM;EACP,CAAC;CACF;;;;;;;;CASA,MAAM,gBACL,QACA,SACmB;EACnB,MAAM,KAAK,QAAQ,QAAQ,qBAAqB,OAAO,YAAY;GAClE,WAAW;GACX,MAAM;EACP,CAAC;EACD,OAAO;CACR;;;;;;;CAQA,MAAM,cACL,QACA,SACgB;EAChB,MAAM,KAAK,QAAQ,UAAU,qBAAqB,UAAU,EAC3D,QACD,CAAC;CACF;;;;;;;;CASA,MAAM,aACL,QACA,MACA,OAAsB,KACH;EAEnB,QAAO,MADe,KAAK,WAAW,MAAM,EAAA,CAC7B,MAAM,MAAM,EAAE,SAAS,QAAQ,EAAE,SAAS,IAAI;CAC9D;;;;;;;;;;CAWA,MAAM,yBACL,QACA,WACA,IACA,MAAM,KACa;EAEnB,IAAI,MADiB,KAAK,aAAa,QAAQ,WAAW,GAAG,GAE5D,OAAO;EAGR,MAAM,KAAK,cAAc,QAAQ,CAChC;GACC,MAAM;GACN,MAAM;GACN;GACA,SAAS,CAAC,EAAE,SAAS,GAAG,CAAC;EAC1B,CACD,CAAC;EAED,OAAO;CACR;AACD;;;;;;;;;;;ACpPA,IAAa,oBAAb,MAAsD;CACrD,OAAgB;CAChB,MAAmC;;;;CAKnC,MAAc,SAAgC;EAC7C,IAAI,KAAK,KACR,OAAO,KAAK;EAGb,MAAM,QAAQ,MAAMA,gBAAAA,kBAAkB;EACtC,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,yIACD;EAGD,KAAK,MAAM,IAAI,aAAa,KAAK;EACjC,OAAO,KAAK;CACb;CAEA,MAAM,WAAW,QAAsC;EAItD,QAAO,OAFe,MADJ,KAAK,OAAO,EAAA,CACJ,WAAW,MAAM,EAAA,CAE5B,KAAK,OAAO;GAC1B,MAAM,EAAE;GACR,MAAM,EAAE;GACR,KAAK,EAAE;GACP,QAAQ,EAAE,QAAQ,KAAK,QAAQ,IAAI,OAAO;EAC3C,EAAE;CACH;CAEA,MAAM,cACL,QACA,SAC0B;EAC1B,MAAM,MAAM,MAAM,KAAK,OAAO;EAC9B,MAAM,UAA0B,CAAC;EAGjC,MAAM,kBAAkB,MAAM,IAAI,WAAW,MAAM;EAEnD,KAAK,MAAM,UAAU,SAAS;GAC7B,MAAM,WAAW,gBAAgB,MAC/B,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,SAAS,OAAO,IACpD;GAEA,MAAM,gBAAgB,UAAU,UAAU,EAAE,EAAE;GAE9C,IAAI,YAAY,kBAAkB,OAAO,OAAO;IAE/C,QAAQ,KAAK;KACZ;KACA,SAAS;KACT,WAAW;IACZ,CAAC;IACD;GACD;GAGA,MAAM,IAAI,cAAc,QAAQ,CAC/B;IACC,MAAM,OAAO;IACb,MAAM,OAAO;IACb,KAAK,OAAO;IACZ,SAAS,CAAC,EAAE,SAAS,OAAO,MAAM,CAAC;GACpC,CACD,CAAC;GAED,QAAQ,KAAK;IACZ;IACA,SAAS,CAAC;IACV,WAAW;GACZ,CAAC;EACF;EAEA,OAAO;CACR;CAEA,MAAM,cACL,QACA,SAC0B;EAC1B,MAAM,MAAM,MAAM,KAAK,OAAO;EAC9B,MAAM,UAA0B,CAAC;EAGjC,MAAM,kBAAkB,MAAM,IAAI,WAAW,MAAM;EAGnD,MAAM,kBAAkB,QAAQ,QAAQ,WACvC,gBAAgB,MACd,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,SAAS,OAAO,IACpD,CACD;EAGA,IAAI,gBAAgB,SAAS,GAC5B,IAAI;GACH,MAAM,IAAI,cACT,QACA,gBAAgB,KAAK,OAAO;IAC3B,MAAM,EAAE;IACR,MAAM,EAAE;GAQT,EAAE,CACH;GAEA,KAAK,MAAM,UAAU,iBACpB,QAAQ,KAAK;IACZ;IACA,SAAS;IACT,UAAU;GACX,CAAC;EAEH,SAAS,OAAO;GAEf,KAAK,MAAM,UAAU,iBACpB,QAAQ,KAAK;IACZ;IACA,SAAS;IACT,UAAU;IACV,OAAO,OAAO,KAAK;GACpB,CAAC;EAEH;EAID,KAAK,MAAM,UAAU,SAIpB,IAAI,CAHa,gBAAgB,MAC/B,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,SAAS,OAAO,IAExC,GACX,QAAQ,KAAK;GACZ;GACA,SAAS;GACT,UAAU;EACX,CAAC;EAIH,OAAO;CACR;AACD"}
@@ -1,4 +1,4 @@
1
- import { s as getHostingerToken } from "./index.mjs";
1
+ import { ot as getHostingerToken } from "./sniffer-BM8fpCas.mjs";
2
2
  //#region src/deploy/dns/hostinger-api.ts
3
3
  /**
4
4
  * Hostinger DNS API client
@@ -240,4 +240,4 @@ var HostingerProvider = class {
240
240
  //#endregion
241
241
  export { HostingerProvider };
242
242
 
243
- //# sourceMappingURL=HostingerProvider-YQY3rMpV.mjs.map
243
+ //# sourceMappingURL=HostingerProvider-DgzKa_Tj.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"HostingerProvider-YQY3rMpV.mjs","names":[],"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\t// The zone comes back as a **bare array**, not `{ data: [...] }`.\n\t\t//\n\t\t// This read `response.data || []`, so every call returned zero records\n\t\t// against the real API while every mocked test passed. Silently: no error,\n\t\t// no warning, just an empty zone. `upsertRecords` uses this to decide what\n\t\t// already exists, so everything looked new and every write was reported as\n\t\t// a creation — harmless only because the PUT merges rather than replaces.\n\t\t//\n\t\t// Both shapes are accepted because one of them is a guess: the wrapper was\n\t\t// written against documentation and the array is what the API actually\n\t\t// sends, so tolerating an envelope costs a line and survives it appearing.\n\t\tconst response = await this.request<DnsRecord[] | { data?: DnsRecord[] }>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\tif (Array.isArray(response)) return response;\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\tDeleteDnsRecord,\n\tDeleteResult,\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\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to check what exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\t// Filter to only records that exist\n\t\tconst recordsToDelete = records.filter((record) =>\n\t\t\texistingRecords.some(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t),\n\t\t);\n\n\t\t// Delete existing records\n\t\tif (recordsToDelete.length > 0) {\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecords(\n\t\t\t\t\tdomain,\n\t\t\t\t\trecordsToDelete.map((r) => ({\n\t\t\t\t\t\tname: r.name,\n\t\t\t\t\t\ttype: r.type as\n\t\t\t\t\t\t\t| 'A'\n\t\t\t\t\t\t\t| 'AAAA'\n\t\t\t\t\t\t\t| 'CNAME'\n\t\t\t\t\t\t\t| 'MX'\n\t\t\t\t\t\t\t| 'TXT'\n\t\t\t\t\t\t\t| 'SRV'\n\t\t\t\t\t\t\t| 'CAA',\n\t\t\t\t\t})),\n\t\t\t\t);\n\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If batch delete fails, report error for all records\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t\terror: String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Mark non-existent records as not found\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\t\t\tif (!existing) {\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\tnotFound: true,\n\t\t\t\t});\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;CAGpC;CACA;CACA;CAJR,YACC,SACA,QACA,YACA,QACC;EACD,MAAM,OAAO;EAJN,KAAA,SAAA;EACA,KAAA,aAAA;EACA,KAAA,SAAA;EAGP,KAAK,OAAO;CACb;AACD;;;;;;;;;;;;;;;;;AAkBA,IAAa,eAAb,MAA0B;CACzB;CAEA,YAAY,OAAe;EAC1B,KAAK,QAAQ;CACd;;;;CAKA,MAAc,QACb,QACA,UACA,MACa;EACb,MAAM,MAAM,GAAG,qBAAqB;EAEpC,MAAM,WAAW,MAAM,MAAM,KAAK;GACjC;GACA,SAAS;IACR,gBAAgB;IAChB,eAAe,UAAU,KAAK;GAC/B;GACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI,KAAA;EACrC,CAAC;EAED,IAAI,CAAC,SAAS,IAAI;GACjB,IAAI,eAAe,wBAAwB,SAAS,OAAO,GAAG,SAAS;GACvE,IAAI;GAEJ,IAAI;IACH,MAAM,YAAa,MAAM,SAAS,KAAK;IACvC,IAAI,UAAU,SACb,eAAe,wBAAwB,UAAU;IAElD,SAAS,UAAU;GACpB,QAAQ,CAER;GAEA,MAAM,IAAI,kBACT,cACA,SAAS,QACT,SAAS,YACT,MACD;EACD;EAGA,MAAM,OAAO,MAAM,SAAS,KAAK;EACjC,IAAI,CAAC,QAAQ,KAAK,KAAK,MAAM,IAC5B;EAED,OAAO,KAAK,MAAM,IAAI;CACvB;;;;;;CAOA,MAAM,WAAW,QAAsC;EAYtD,MAAM,WAAW,MAAM,KAAK,QAC3B,OACA,qBAAqB,QACtB;EAEA,IAAI,MAAM,QAAQ,QAAQ,GAAG,OAAO;EAEpC,OAAO,UAAU,QAAQ,CAAC;CAC3B;;;;;;;;CASA,MAAM,cACL,QACA,SACA,YAAY,OACI;EAChB,MAAM,KAAK,QAAQ,OAAO,qBAAqB,UAAU;GACxD;GACA,MAAM;EACP,CAAC;CACF;;;;;;;;CASA,MAAM,gBACL,QACA,SACmB;EACnB,MAAM,KAAK,QAAQ,QAAQ,qBAAqB,OAAO,YAAY;GAClE,WAAW;GACX,MAAM;EACP,CAAC;EACD,OAAO;CACR;;;;;;;CAQA,MAAM,cACL,QACA,SACgB;EAChB,MAAM,KAAK,QAAQ,UAAU,qBAAqB,UAAU,EAC3D,QACD,CAAC;CACF;;;;;;;;CASA,MAAM,aACL,QACA,MACA,OAAsB,KACH;EAEnB,QAAO,MADe,KAAK,WAAW,MAAM,EAAA,CAC7B,MAAM,MAAM,EAAE,SAAS,QAAQ,EAAE,SAAS,IAAI;CAC9D;;;;;;;;;;CAWA,MAAM,yBACL,QACA,WACA,IACA,MAAM,KACa;EAEnB,IAAI,MADiB,KAAK,aAAa,QAAQ,WAAW,GAAG,GAE5D,OAAO;EAGR,MAAM,KAAK,cAAc,QAAQ,CAChC;GACC,MAAM;GACN,MAAM;GACN;GACA,SAAS,CAAC,EAAE,SAAS,GAAG,CAAC;EAC1B,CACD,CAAC;EAED,OAAO;CACR;AACD;;;;;;;;;;;ACpPA,IAAa,oBAAb,MAAsD;CACrD,OAAgB;CAChB,MAAmC;;;;CAKnC,MAAc,SAAgC;EAC7C,IAAI,KAAK,KACR,OAAO,KAAK;EAGb,MAAM,QAAQ,MAAM,kBAAkB;EACtC,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,yIACD;EAGD,KAAK,MAAM,IAAI,aAAa,KAAK;EACjC,OAAO,KAAK;CACb;CAEA,MAAM,WAAW,QAAsC;EAItD,QAAO,OAFe,MADJ,KAAK,OAAO,EAAA,CACJ,WAAW,MAAM,EAAA,CAE5B,KAAK,OAAO;GAC1B,MAAM,EAAE;GACR,MAAM,EAAE;GACR,KAAK,EAAE;GACP,QAAQ,EAAE,QAAQ,KAAK,QAAQ,IAAI,OAAO;EAC3C,EAAE;CACH;CAEA,MAAM,cACL,QACA,SAC0B;EAC1B,MAAM,MAAM,MAAM,KAAK,OAAO;EAC9B,MAAM,UAA0B,CAAC;EAGjC,MAAM,kBAAkB,MAAM,IAAI,WAAW,MAAM;EAEnD,KAAK,MAAM,UAAU,SAAS;GAC7B,MAAM,WAAW,gBAAgB,MAC/B,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,SAAS,OAAO,IACpD;GAEA,MAAM,gBAAgB,UAAU,UAAU,EAAE,EAAE;GAE9C,IAAI,YAAY,kBAAkB,OAAO,OAAO;IAE/C,QAAQ,KAAK;KACZ;KACA,SAAS;KACT,WAAW;IACZ,CAAC;IACD;GACD;GAGA,MAAM,IAAI,cAAc,QAAQ,CAC/B;IACC,MAAM,OAAO;IACb,MAAM,OAAO;IACb,KAAK,OAAO;IACZ,SAAS,CAAC,EAAE,SAAS,OAAO,MAAM,CAAC;GACpC,CACD,CAAC;GAED,QAAQ,KAAK;IACZ;IACA,SAAS,CAAC;IACV,WAAW;GACZ,CAAC;EACF;EAEA,OAAO;CACR;CAEA,MAAM,cACL,QACA,SAC0B;EAC1B,MAAM,MAAM,MAAM,KAAK,OAAO;EAC9B,MAAM,UAA0B,CAAC;EAGjC,MAAM,kBAAkB,MAAM,IAAI,WAAW,MAAM;EAGnD,MAAM,kBAAkB,QAAQ,QAAQ,WACvC,gBAAgB,MACd,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,SAAS,OAAO,IACpD,CACD;EAGA,IAAI,gBAAgB,SAAS,GAC5B,IAAI;GACH,MAAM,IAAI,cACT,QACA,gBAAgB,KAAK,OAAO;IAC3B,MAAM,EAAE;IACR,MAAM,EAAE;GAQT,EAAE,CACH;GAEA,KAAK,MAAM,UAAU,iBACpB,QAAQ,KAAK;IACZ;IACA,SAAS;IACT,UAAU;GACX,CAAC;EAEH,SAAS,OAAO;GAEf,KAAK,MAAM,UAAU,iBACpB,QAAQ,KAAK;IACZ;IACA,SAAS;IACT,UAAU;IACV,OAAO,OAAO,KAAK;GACpB,CAAC;EAEH;EAID,KAAK,MAAM,UAAU,SAIpB,IAAI,CAHa,gBAAgB,MAC/B,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,SAAS,OAAO,IAExC,GACX,QAAQ,KAAK;GACZ;GACA,SAAS;GACT,UAAU;EACX,CAAC;EAIH,OAAO;CACR;AACD"}
1
+ {"version":3,"file":"HostingerProvider-DgzKa_Tj.mjs","names":[],"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\t// The zone comes back as a **bare array**, not `{ data: [...] }`.\n\t\t//\n\t\t// This read `response.data || []`, so every call returned zero records\n\t\t// against the real API while every mocked test passed. Silently: no error,\n\t\t// no warning, just an empty zone. `upsertRecords` uses this to decide what\n\t\t// already exists, so everything looked new and every write was reported as\n\t\t// a creation — harmless only because the PUT merges rather than replaces.\n\t\t//\n\t\t// Both shapes are accepted because one of them is a guess: the wrapper was\n\t\t// written against documentation and the array is what the API actually\n\t\t// sends, so tolerating an envelope costs a line and survives it appearing.\n\t\tconst response = await this.request<DnsRecord[] | { data?: DnsRecord[] }>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\tif (Array.isArray(response)) return response;\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\tDeleteDnsRecord,\n\tDeleteResult,\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\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to check what exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\t// Filter to only records that exist\n\t\tconst recordsToDelete = records.filter((record) =>\n\t\t\texistingRecords.some(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t),\n\t\t);\n\n\t\t// Delete existing records\n\t\tif (recordsToDelete.length > 0) {\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecords(\n\t\t\t\t\tdomain,\n\t\t\t\t\trecordsToDelete.map((r) => ({\n\t\t\t\t\t\tname: r.name,\n\t\t\t\t\t\ttype: r.type as\n\t\t\t\t\t\t\t| 'A'\n\t\t\t\t\t\t\t| 'AAAA'\n\t\t\t\t\t\t\t| 'CNAME'\n\t\t\t\t\t\t\t| 'MX'\n\t\t\t\t\t\t\t| 'TXT'\n\t\t\t\t\t\t\t| 'SRV'\n\t\t\t\t\t\t\t| 'CAA',\n\t\t\t\t\t})),\n\t\t\t\t);\n\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If batch delete fails, report error for all records\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t\terror: String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Mark non-existent records as not found\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\t\t\tif (!existing) {\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\tnotFound: true,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { a as StateStoreWithoutLocking, l as newLockHolder, n as StageStateMissing, o as StateVersionConflict, s as contentVersion } from "./StateStore-CYUfGYcx.mjs";
1
+ import { _ as newLockHolder, h as contentVersion, m as StateVersionConflict, p as StateStoreWithoutLocking, u as StageStateMissing } from "./sniffer-BM8fpCas.mjs";
2
2
  //#region src/deploy/LegacyStateStore.ts
3
3
  var LegacyStateStore = class {
4
4
  provider;
@@ -86,4 +86,4 @@ function versionOf(state) {
86
86
  //#endregion
87
87
  export { LegacyStateStore };
88
88
 
89
- //# sourceMappingURL=LegacyStateStore-CB4ch3Dl.mjs.map
89
+ //# sourceMappingURL=LegacyStateStore-CToa2hga.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"LegacyStateStore-CB4ch3Dl.mjs","names":[],"sources":["../src/deploy/LegacyStateStore.ts"],"sourcesContent":["/**\n * A custom `StateProvider` as a `StateStore`.\n *\n * A provider only reads and writes whole states, so this adapter can check a\n * version before it writes but cannot make the check and the write one step,\n * and cannot lock. It warns once (`StateStoreWithoutLocking`) the first time\n * a caller relies on either, and otherwise behaves as a store. Resource\n * records travel inside the state the provider stores, under `resources`.\n */\n\nimport type { StateProvider } from './StateProvider';\nimport {\n\tcontentVersion,\n\ttype LockHolder,\n\ttype LockOptions,\n\tnewLockHolder,\n\ttype ResourceInput,\n\ttype ResourceRecord,\n\ttype ResourceWriteOptions,\n\tStageStateMissing,\n\ttype StateLock,\n\ttype StateStore,\n\tStateStoreWithoutLocking,\n\ttype StateVersion,\n\tStateVersionConflict,\n\ttype StoredStageState,\n\ttype WriteOptions,\n} from './StateStore';\nimport type { DokployStageState } from './state';\n\ntype ProviderState = DokployStageState & {\n\tresources?: Record<string, ResourceRecord>;\n};\n\nexport interface LegacyStateStoreOptions {\n\t/** Where the warning goes; `process.emitWarning` by default. */\n\twarn?: (warning: StateStoreWithoutLocking) => void;\n}\n\nexport class LegacyStateStore implements StateStore {\n\tprivate warned = false;\n\tprivate readonly warn: (warning: StateStoreWithoutLocking) => void;\n\n\tconstructor(\n\t\treadonly provider: StateProvider,\n\t\toptions: LegacyStateStoreOptions = {},\n\t) {\n\t\tthis.warn = options.warn ?? ((warning) => process.emitWarning(warning));\n\t}\n\n\tasync lock(stage: string, options?: LockOptions): Promise<StateLock> {\n\t\tthis.warnOnce();\n\t\treturn { stage, holder: newLockHolder(options), release: async () => {} };\n\t}\n\n\tasync forceUnlock(_stage: string): Promise<LockHolder | null> {\n\t\treturn null;\n\t}\n\n\tasync read(stage: string): Promise<StoredStageState | null> {\n\t\tconst stored = (await this.provider.read(stage)) as ProviderState | null;\n\t\tif (!stored) return null;\n\t\tconst { resources = {}, ...state } = stored;\n\t\treturn { state, resources, version: versionOf(stored) };\n\t}\n\n\tasync write(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.read(stage);\n\t\tthis.check(stage, options.expectedVersion, current);\n\t\treturn this.put(stage, { ...state, resources: current?.resources ?? {} });\n\t}\n\n\tasync putResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.current(stage, options);\n\t\treturn this.put(stage, {\n\t\t\t...current.state,\n\t\t\tresources: {\n\t\t\t\t...current.resources,\n\t\t\t\t[record.key]: { ...record, updatedAt: new Date().toISOString() },\n\t\t\t},\n\t\t});\n\t}\n\n\tasync deleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.current(stage, options);\n\t\tconst { [key]: _removed, ...resources } = current.resources;\n\t\treturn this.put(stage, { ...current.state, resources });\n\t}\n\n\tprivate async current(\n\t\tstage: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StoredStageState> {\n\t\tconst current = await this.read(stage);\n\t\tif (options?.expectedVersion !== undefined) {\n\t\t\tthis.check(stage, options.expectedVersion, current);\n\t\t}\n\t\tif (!current) throw new StageStateMissing(stage);\n\t\treturn current;\n\t}\n\n\tprivate check(\n\t\tstage: string,\n\t\texpected: StateVersion | null,\n\t\tcurrent: StoredStageState | null,\n\t): void {\n\t\tconst actual = current?.version ?? null;\n\t\tif (actual !== expected) {\n\t\t\tthrow new StateVersionConflict(stage, expected, actual);\n\t\t}\n\t}\n\n\tprivate async put(\n\t\tstage: string,\n\t\tstate: ProviderState,\n\t): Promise<StateVersion> {\n\t\tthis.warnOnce();\n\t\t// Providers stamp `lastDeployedAt` on the object they are given, so the\n\t\t// version is taken after the write, from what was actually stored.\n\t\tawait this.provider.write(stage, state);\n\t\treturn versionOf(state);\n\t}\n\n\tprivate warnOnce(): void {\n\t\tif (this.warned) return;\n\t\tthis.warned = true;\n\t\tthis.warn(\n\t\t\tnew StateStoreWithoutLocking(\n\t\t\t\tthis.provider.constructor?.name || 'anonymous',\n\t\t\t),\n\t\t);\n\t}\n}\n\n/** Key order is the provider's, so this is stable for a given stored state. */\nfunction versionOf(state: ProviderState): StateVersion {\n\treturn contentVersion(JSON.stringify(state));\n}\n"],"mappings":";;AAuCA,IAAa,mBAAb,MAAoD;CAKzC;CAJV,SAAiB;CACjB;CAEA,YACC,UACA,UAAmC,CAAC,GACnC;EAFQ,KAAA,WAAA;EAGT,KAAK,OAAO,QAAQ,UAAU,YAAY,QAAQ,YAAY,OAAO;CACtE;CAEA,MAAM,KAAK,OAAe,SAA2C;EACpE,KAAK,SAAS;EACd,OAAO;GAAE;GAAO,QAAQ,cAAc,OAAO;GAAG,SAAS,YAAY,CAAC;EAAE;CACzE;CAEA,MAAM,YAAY,QAA4C;EAC7D,OAAO;CACR;CAEA,MAAM,KAAK,OAAiD;EAC3D,MAAM,SAAU,MAAM,KAAK,SAAS,KAAK,KAAK;EAC9C,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,EAAE,YAAY,CAAC,GAAG,GAAG,UAAU;EACrC,OAAO;GAAE;GAAO;GAAW,SAAS,UAAU,MAAM;EAAE;CACvD;CAEA,MAAM,MACL,OACA,OACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,KAAK,KAAK;EACrC,KAAK,MAAM,OAAO,QAAQ,iBAAiB,OAAO;EAClD,OAAO,KAAK,IAAI,OAAO;GAAE,GAAG;GAAO,WAAW,SAAS,aAAa,CAAC;EAAE,CAAC;CACzE;CAEA,MAAM,YACL,OACA,QACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,OAAO;EACjD,OAAO,KAAK,IAAI,OAAO;GACtB,GAAG,QAAQ;GACX,WAAW;IACV,GAAG,QAAQ;KACV,OAAO,MAAM;KAAE,GAAG;KAAQ,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;IAAE;GAChE;EACD,CAAC;CACF;CAEA,MAAM,eACL,OACA,KACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,OAAO;EACjD,MAAM,GAAG,MAAM,UAAU,GAAG,cAAc,QAAQ;EAClD,OAAO,KAAK,IAAI,OAAO;GAAE,GAAG,QAAQ;GAAO;EAAU,CAAC;CACvD;CAEA,MAAc,QACb,OACA,SAC4B;EAC5B,MAAM,UAAU,MAAM,KAAK,KAAK,KAAK;EACrC,IAAI,SAAS,oBAAoB,KAAA,GAChC,KAAK,MAAM,OAAO,QAAQ,iBAAiB,OAAO;EAEnD,IAAI,CAAC,SAAS,MAAM,IAAI,kBAAkB,KAAK;EAC/C,OAAO;CACR;CAEA,MACC,OACA,UACA,SACO;EACP,MAAM,SAAS,SAAS,WAAW;EACnC,IAAI,WAAW,UACd,MAAM,IAAI,qBAAqB,OAAO,UAAU,MAAM;CAExD;CAEA,MAAc,IACb,OACA,OACwB;EACxB,KAAK,SAAS;EAGd,MAAM,KAAK,SAAS,MAAM,OAAO,KAAK;EACtC,OAAO,UAAU,KAAK;CACvB;CAEA,WAAyB;EACxB,IAAI,KAAK,QAAQ;EACjB,KAAK,SAAS;EACd,KAAK,KACJ,IAAI,yBACH,KAAK,SAAS,aAAa,QAAQ,WACpC,CACD;CACD;AACD;;AAGA,SAAS,UAAU,OAAoC;CACtD,OAAO,eAAe,KAAK,UAAU,KAAK,CAAC;AAC5C"}
1
+ {"version":3,"file":"LegacyStateStore-CToa2hga.mjs","names":[],"sources":["../src/deploy/LegacyStateStore.ts"],"sourcesContent":["/**\n * A custom `StateProvider` as a `StateStore`.\n *\n * A provider only reads and writes whole states, so this adapter can check a\n * version before it writes but cannot make the check and the write one step,\n * and cannot lock. It warns once (`StateStoreWithoutLocking`) the first time\n * a caller relies on either, and otherwise behaves as a store. Resource\n * records travel inside the state the provider stores, under `resources`.\n */\n\nimport type { StateProvider } from './StateProvider';\nimport {\n\tcontentVersion,\n\ttype LockHolder,\n\ttype LockOptions,\n\tnewLockHolder,\n\ttype ResourceInput,\n\ttype ResourceRecord,\n\ttype ResourceWriteOptions,\n\tStageStateMissing,\n\ttype StateLock,\n\ttype StateStore,\n\tStateStoreWithoutLocking,\n\ttype StateVersion,\n\tStateVersionConflict,\n\ttype StoredStageState,\n\ttype WriteOptions,\n} from './StateStore';\nimport type { DokployStageState } from './state';\n\ntype ProviderState = DokployStageState & {\n\tresources?: Record<string, ResourceRecord>;\n};\n\nexport interface LegacyStateStoreOptions {\n\t/** Where the warning goes; `process.emitWarning` by default. */\n\twarn?: (warning: StateStoreWithoutLocking) => void;\n}\n\nexport class LegacyStateStore implements StateStore {\n\tprivate warned = false;\n\tprivate readonly warn: (warning: StateStoreWithoutLocking) => void;\n\n\tconstructor(\n\t\treadonly provider: StateProvider,\n\t\toptions: LegacyStateStoreOptions = {},\n\t) {\n\t\tthis.warn = options.warn ?? ((warning) => process.emitWarning(warning));\n\t}\n\n\tasync lock(stage: string, options?: LockOptions): Promise<StateLock> {\n\t\tthis.warnOnce();\n\t\treturn { stage, holder: newLockHolder(options), release: async () => {} };\n\t}\n\n\tasync forceUnlock(_stage: string): Promise<LockHolder | null> {\n\t\treturn null;\n\t}\n\n\tasync read(stage: string): Promise<StoredStageState | null> {\n\t\tconst stored = (await this.provider.read(stage)) as ProviderState | null;\n\t\tif (!stored) return null;\n\t\tconst { resources = {}, ...state } = stored;\n\t\treturn { state, resources, version: versionOf(stored) };\n\t}\n\n\tasync write(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.read(stage);\n\t\tthis.check(stage, options.expectedVersion, current);\n\t\treturn this.put(stage, { ...state, resources: current?.resources ?? {} });\n\t}\n\n\tasync putResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.current(stage, options);\n\t\treturn this.put(stage, {\n\t\t\t...current.state,\n\t\t\tresources: {\n\t\t\t\t...current.resources,\n\t\t\t\t[record.key]: { ...record, updatedAt: new Date().toISOString() },\n\t\t\t},\n\t\t});\n\t}\n\n\tasync deleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.current(stage, options);\n\t\tconst { [key]: _removed, ...resources } = current.resources;\n\t\treturn this.put(stage, { ...current.state, resources });\n\t}\n\n\tprivate async current(\n\t\tstage: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StoredStageState> {\n\t\tconst current = await this.read(stage);\n\t\tif (options?.expectedVersion !== undefined) {\n\t\t\tthis.check(stage, options.expectedVersion, current);\n\t\t}\n\t\tif (!current) throw new StageStateMissing(stage);\n\t\treturn current;\n\t}\n\n\tprivate check(\n\t\tstage: string,\n\t\texpected: StateVersion | null,\n\t\tcurrent: StoredStageState | null,\n\t): void {\n\t\tconst actual = current?.version ?? null;\n\t\tif (actual !== expected) {\n\t\t\tthrow new StateVersionConflict(stage, expected, actual);\n\t\t}\n\t}\n\n\tprivate async put(\n\t\tstage: string,\n\t\tstate: ProviderState,\n\t): Promise<StateVersion> {\n\t\tthis.warnOnce();\n\t\t// Providers stamp `lastDeployedAt` on the object they are given, so the\n\t\t// version is taken after the write, from what was actually stored.\n\t\tawait this.provider.write(stage, state);\n\t\treturn versionOf(state);\n\t}\n\n\tprivate warnOnce(): void {\n\t\tif (this.warned) return;\n\t\tthis.warned = true;\n\t\tthis.warn(\n\t\t\tnew StateStoreWithoutLocking(\n\t\t\t\tthis.provider.constructor?.name || 'anonymous',\n\t\t\t),\n\t\t);\n\t}\n}\n\n/** Key order is the provider's, so this is stable for a given stored state. */\nfunction versionOf(state: ProviderState): StateVersion {\n\treturn contentVersion(JSON.stringify(state));\n}\n"],"mappings":";;AAuCA,IAAa,mBAAb,MAAoD;CAKzC;CAJV,SAAiB;CACjB;CAEA,YACC,UACA,UAAmC,CAAC,GACnC;EAFQ,KAAA,WAAA;EAGT,KAAK,OAAO,QAAQ,UAAU,YAAY,QAAQ,YAAY,OAAO;CACtE;CAEA,MAAM,KAAK,OAAe,SAA2C;EACpE,KAAK,SAAS;EACd,OAAO;GAAE;GAAO,QAAQ,cAAc,OAAO;GAAG,SAAS,YAAY,CAAC;EAAE;CACzE;CAEA,MAAM,YAAY,QAA4C;EAC7D,OAAO;CACR;CAEA,MAAM,KAAK,OAAiD;EAC3D,MAAM,SAAU,MAAM,KAAK,SAAS,KAAK,KAAK;EAC9C,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,EAAE,YAAY,CAAC,GAAG,GAAG,UAAU;EACrC,OAAO;GAAE;GAAO;GAAW,SAAS,UAAU,MAAM;EAAE;CACvD;CAEA,MAAM,MACL,OACA,OACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,KAAK,KAAK;EACrC,KAAK,MAAM,OAAO,QAAQ,iBAAiB,OAAO;EAClD,OAAO,KAAK,IAAI,OAAO;GAAE,GAAG;GAAO,WAAW,SAAS,aAAa,CAAC;EAAE,CAAC;CACzE;CAEA,MAAM,YACL,OACA,QACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,OAAO;EACjD,OAAO,KAAK,IAAI,OAAO;GACtB,GAAG,QAAQ;GACX,WAAW;IACV,GAAG,QAAQ;KACV,OAAO,MAAM;KAAE,GAAG;KAAQ,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;IAAE;GAChE;EACD,CAAC;CACF;CAEA,MAAM,eACL,OACA,KACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,OAAO;EACjD,MAAM,GAAG,MAAM,UAAU,GAAG,cAAc,QAAQ;EAClD,OAAO,KAAK,IAAI,OAAO;GAAE,GAAG,QAAQ;GAAO;EAAU,CAAC;CACvD;CAEA,MAAc,QACb,OACA,SAC4B;EAC5B,MAAM,UAAU,MAAM,KAAK,KAAK,KAAK;EACrC,IAAI,SAAS,oBAAoB,KAAA,GAChC,KAAK,MAAM,OAAO,QAAQ,iBAAiB,OAAO;EAEnD,IAAI,CAAC,SAAS,MAAM,IAAI,kBAAkB,KAAK;EAC/C,OAAO;CACR;CAEA,MACC,OACA,UACA,SACO;EACP,MAAM,SAAS,SAAS,WAAW;EACnC,IAAI,WAAW,UACd,MAAM,IAAI,qBAAqB,OAAO,UAAU,MAAM;CAExD;CAEA,MAAc,IACb,OACA,OACwB;EACxB,KAAK,SAAS;EAGd,MAAM,KAAK,SAAS,MAAM,OAAO,KAAK;EACtC,OAAO,UAAU,KAAK;CACvB;CAEA,WAAyB;EACxB,IAAI,KAAK,QAAQ;EACjB,KAAK,SAAS;EACd,KAAK,KACJ,IAAI,yBACH,KAAK,SAAS,aAAa,QAAQ,WACpC,CACD;CACD;AACD;;AAGA,SAAS,UAAU,OAAoC;CACtD,OAAO,eAAe,KAAK,UAAU,KAAK,CAAC;AAC5C"}
@@ -1,4 +1,4 @@
1
- const require_StateStore = require("./StateStore-B4u0wx2t.cjs");
1
+ const require_sniffer = require("./sniffer-DIv12ncU.cjs");
2
2
  //#region src/deploy/LegacyStateStore.ts
3
3
  var LegacyStateStore = class {
4
4
  provider;
@@ -12,7 +12,7 @@ var LegacyStateStore = class {
12
12
  this.warnOnce();
13
13
  return {
14
14
  stage,
15
- holder: require_StateStore.newLockHolder(options),
15
+ holder: require_sniffer.newLockHolder(options),
16
16
  release: async () => {}
17
17
  };
18
18
  }
@@ -61,12 +61,12 @@ var LegacyStateStore = class {
61
61
  async current(stage, options) {
62
62
  const current = await this.read(stage);
63
63
  if (options?.expectedVersion !== void 0) this.check(stage, options.expectedVersion, current);
64
- if (!current) throw new require_StateStore.StageStateMissing(stage);
64
+ if (!current) throw new require_sniffer.StageStateMissing(stage);
65
65
  return current;
66
66
  }
67
67
  check(stage, expected, current) {
68
68
  const actual = current?.version ?? null;
69
- if (actual !== expected) throw new require_StateStore.StateVersionConflict(stage, expected, actual);
69
+ if (actual !== expected) throw new require_sniffer.StateVersionConflict(stage, expected, actual);
70
70
  }
71
71
  async put(stage, state) {
72
72
  this.warnOnce();
@@ -76,14 +76,14 @@ var LegacyStateStore = class {
76
76
  warnOnce() {
77
77
  if (this.warned) return;
78
78
  this.warned = true;
79
- this.warn(new require_StateStore.StateStoreWithoutLocking(this.provider.constructor?.name || "anonymous"));
79
+ this.warn(new require_sniffer.StateStoreWithoutLocking(this.provider.constructor?.name || "anonymous"));
80
80
  }
81
81
  };
82
82
  /** Key order is the provider's, so this is stable for a given stored state. */
83
83
  function versionOf(state) {
84
- return require_StateStore.contentVersion(JSON.stringify(state));
84
+ return require_sniffer.contentVersion(JSON.stringify(state));
85
85
  }
86
86
  //#endregion
87
87
  exports.LegacyStateStore = LegacyStateStore;
88
88
 
89
- //# sourceMappingURL=LegacyStateStore-CQJqvmXw.cjs.map
89
+ //# sourceMappingURL=LegacyStateStore-CidlPT0N.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"LegacyStateStore-CQJqvmXw.cjs","names":["newLockHolder","StageStateMissing","StateVersionConflict","StateStoreWithoutLocking","contentVersion"],"sources":["../src/deploy/LegacyStateStore.ts"],"sourcesContent":["/**\n * A custom `StateProvider` as a `StateStore`.\n *\n * A provider only reads and writes whole states, so this adapter can check a\n * version before it writes but cannot make the check and the write one step,\n * and cannot lock. It warns once (`StateStoreWithoutLocking`) the first time\n * a caller relies on either, and otherwise behaves as a store. Resource\n * records travel inside the state the provider stores, under `resources`.\n */\n\nimport type { StateProvider } from './StateProvider';\nimport {\n\tcontentVersion,\n\ttype LockHolder,\n\ttype LockOptions,\n\tnewLockHolder,\n\ttype ResourceInput,\n\ttype ResourceRecord,\n\ttype ResourceWriteOptions,\n\tStageStateMissing,\n\ttype StateLock,\n\ttype StateStore,\n\tStateStoreWithoutLocking,\n\ttype StateVersion,\n\tStateVersionConflict,\n\ttype StoredStageState,\n\ttype WriteOptions,\n} from './StateStore';\nimport type { DokployStageState } from './state';\n\ntype ProviderState = DokployStageState & {\n\tresources?: Record<string, ResourceRecord>;\n};\n\nexport interface LegacyStateStoreOptions {\n\t/** Where the warning goes; `process.emitWarning` by default. */\n\twarn?: (warning: StateStoreWithoutLocking) => void;\n}\n\nexport class LegacyStateStore implements StateStore {\n\tprivate warned = false;\n\tprivate readonly warn: (warning: StateStoreWithoutLocking) => void;\n\n\tconstructor(\n\t\treadonly provider: StateProvider,\n\t\toptions: LegacyStateStoreOptions = {},\n\t) {\n\t\tthis.warn = options.warn ?? ((warning) => process.emitWarning(warning));\n\t}\n\n\tasync lock(stage: string, options?: LockOptions): Promise<StateLock> {\n\t\tthis.warnOnce();\n\t\treturn { stage, holder: newLockHolder(options), release: async () => {} };\n\t}\n\n\tasync forceUnlock(_stage: string): Promise<LockHolder | null> {\n\t\treturn null;\n\t}\n\n\tasync read(stage: string): Promise<StoredStageState | null> {\n\t\tconst stored = (await this.provider.read(stage)) as ProviderState | null;\n\t\tif (!stored) return null;\n\t\tconst { resources = {}, ...state } = stored;\n\t\treturn { state, resources, version: versionOf(stored) };\n\t}\n\n\tasync write(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.read(stage);\n\t\tthis.check(stage, options.expectedVersion, current);\n\t\treturn this.put(stage, { ...state, resources: current?.resources ?? {} });\n\t}\n\n\tasync putResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.current(stage, options);\n\t\treturn this.put(stage, {\n\t\t\t...current.state,\n\t\t\tresources: {\n\t\t\t\t...current.resources,\n\t\t\t\t[record.key]: { ...record, updatedAt: new Date().toISOString() },\n\t\t\t},\n\t\t});\n\t}\n\n\tasync deleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.current(stage, options);\n\t\tconst { [key]: _removed, ...resources } = current.resources;\n\t\treturn this.put(stage, { ...current.state, resources });\n\t}\n\n\tprivate async current(\n\t\tstage: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StoredStageState> {\n\t\tconst current = await this.read(stage);\n\t\tif (options?.expectedVersion !== undefined) {\n\t\t\tthis.check(stage, options.expectedVersion, current);\n\t\t}\n\t\tif (!current) throw new StageStateMissing(stage);\n\t\treturn current;\n\t}\n\n\tprivate check(\n\t\tstage: string,\n\t\texpected: StateVersion | null,\n\t\tcurrent: StoredStageState | null,\n\t): void {\n\t\tconst actual = current?.version ?? null;\n\t\tif (actual !== expected) {\n\t\t\tthrow new StateVersionConflict(stage, expected, actual);\n\t\t}\n\t}\n\n\tprivate async put(\n\t\tstage: string,\n\t\tstate: ProviderState,\n\t): Promise<StateVersion> {\n\t\tthis.warnOnce();\n\t\t// Providers stamp `lastDeployedAt` on the object they are given, so the\n\t\t// version is taken after the write, from what was actually stored.\n\t\tawait this.provider.write(stage, state);\n\t\treturn versionOf(state);\n\t}\n\n\tprivate warnOnce(): void {\n\t\tif (this.warned) return;\n\t\tthis.warned = true;\n\t\tthis.warn(\n\t\t\tnew StateStoreWithoutLocking(\n\t\t\t\tthis.provider.constructor?.name || 'anonymous',\n\t\t\t),\n\t\t);\n\t}\n}\n\n/** Key order is the provider's, so this is stable for a given stored state. */\nfunction versionOf(state: ProviderState): StateVersion {\n\treturn contentVersion(JSON.stringify(state));\n}\n"],"mappings":";;AAuCA,IAAa,mBAAb,MAAoD;CAKzC;CAJV,SAAiB;CACjB;CAEA,YACC,UACA,UAAmC,CAAC,GACnC;EAFQ,KAAA,WAAA;EAGT,KAAK,OAAO,QAAQ,UAAU,YAAY,QAAQ,YAAY,OAAO;CACtE;CAEA,MAAM,KAAK,OAAe,SAA2C;EACpE,KAAK,SAAS;EACd,OAAO;GAAE;GAAO,QAAQA,mBAAAA,cAAc,OAAO;GAAG,SAAS,YAAY,CAAC;EAAE;CACzE;CAEA,MAAM,YAAY,QAA4C;EAC7D,OAAO;CACR;CAEA,MAAM,KAAK,OAAiD;EAC3D,MAAM,SAAU,MAAM,KAAK,SAAS,KAAK,KAAK;EAC9C,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,EAAE,YAAY,CAAC,GAAG,GAAG,UAAU;EACrC,OAAO;GAAE;GAAO;GAAW,SAAS,UAAU,MAAM;EAAE;CACvD;CAEA,MAAM,MACL,OACA,OACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,KAAK,KAAK;EACrC,KAAK,MAAM,OAAO,QAAQ,iBAAiB,OAAO;EAClD,OAAO,KAAK,IAAI,OAAO;GAAE,GAAG;GAAO,WAAW,SAAS,aAAa,CAAC;EAAE,CAAC;CACzE;CAEA,MAAM,YACL,OACA,QACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,OAAO;EACjD,OAAO,KAAK,IAAI,OAAO;GACtB,GAAG,QAAQ;GACX,WAAW;IACV,GAAG,QAAQ;KACV,OAAO,MAAM;KAAE,GAAG;KAAQ,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;IAAE;GAChE;EACD,CAAC;CACF;CAEA,MAAM,eACL,OACA,KACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,OAAO;EACjD,MAAM,GAAG,MAAM,UAAU,GAAG,cAAc,QAAQ;EAClD,OAAO,KAAK,IAAI,OAAO;GAAE,GAAG,QAAQ;GAAO;EAAU,CAAC;CACvD;CAEA,MAAc,QACb,OACA,SAC4B;EAC5B,MAAM,UAAU,MAAM,KAAK,KAAK,KAAK;EACrC,IAAI,SAAS,oBAAoB,KAAA,GAChC,KAAK,MAAM,OAAO,QAAQ,iBAAiB,OAAO;EAEnD,IAAI,CAAC,SAAS,MAAM,IAAIC,mBAAAA,kBAAkB,KAAK;EAC/C,OAAO;CACR;CAEA,MACC,OACA,UACA,SACO;EACP,MAAM,SAAS,SAAS,WAAW;EACnC,IAAI,WAAW,UACd,MAAM,IAAIC,mBAAAA,qBAAqB,OAAO,UAAU,MAAM;CAExD;CAEA,MAAc,IACb,OACA,OACwB;EACxB,KAAK,SAAS;EAGd,MAAM,KAAK,SAAS,MAAM,OAAO,KAAK;EACtC,OAAO,UAAU,KAAK;CACvB;CAEA,WAAyB;EACxB,IAAI,KAAK,QAAQ;EACjB,KAAK,SAAS;EACd,KAAK,KACJ,IAAIC,mBAAAA,yBACH,KAAK,SAAS,aAAa,QAAQ,WACpC,CACD;CACD;AACD;;AAGA,SAAS,UAAU,OAAoC;CACtD,OAAOC,mBAAAA,eAAe,KAAK,UAAU,KAAK,CAAC;AAC5C"}
1
+ {"version":3,"file":"LegacyStateStore-CidlPT0N.cjs","names":["newLockHolder","StageStateMissing","StateVersionConflict","StateStoreWithoutLocking","contentVersion"],"sources":["../src/deploy/LegacyStateStore.ts"],"sourcesContent":["/**\n * A custom `StateProvider` as a `StateStore`.\n *\n * A provider only reads and writes whole states, so this adapter can check a\n * version before it writes but cannot make the check and the write one step,\n * and cannot lock. It warns once (`StateStoreWithoutLocking`) the first time\n * a caller relies on either, and otherwise behaves as a store. Resource\n * records travel inside the state the provider stores, under `resources`.\n */\n\nimport type { StateProvider } from './StateProvider';\nimport {\n\tcontentVersion,\n\ttype LockHolder,\n\ttype LockOptions,\n\tnewLockHolder,\n\ttype ResourceInput,\n\ttype ResourceRecord,\n\ttype ResourceWriteOptions,\n\tStageStateMissing,\n\ttype StateLock,\n\ttype StateStore,\n\tStateStoreWithoutLocking,\n\ttype StateVersion,\n\tStateVersionConflict,\n\ttype StoredStageState,\n\ttype WriteOptions,\n} from './StateStore';\nimport type { DokployStageState } from './state';\n\ntype ProviderState = DokployStageState & {\n\tresources?: Record<string, ResourceRecord>;\n};\n\nexport interface LegacyStateStoreOptions {\n\t/** Where the warning goes; `process.emitWarning` by default. */\n\twarn?: (warning: StateStoreWithoutLocking) => void;\n}\n\nexport class LegacyStateStore implements StateStore {\n\tprivate warned = false;\n\tprivate readonly warn: (warning: StateStoreWithoutLocking) => void;\n\n\tconstructor(\n\t\treadonly provider: StateProvider,\n\t\toptions: LegacyStateStoreOptions = {},\n\t) {\n\t\tthis.warn = options.warn ?? ((warning) => process.emitWarning(warning));\n\t}\n\n\tasync lock(stage: string, options?: LockOptions): Promise<StateLock> {\n\t\tthis.warnOnce();\n\t\treturn { stage, holder: newLockHolder(options), release: async () => {} };\n\t}\n\n\tasync forceUnlock(_stage: string): Promise<LockHolder | null> {\n\t\treturn null;\n\t}\n\n\tasync read(stage: string): Promise<StoredStageState | null> {\n\t\tconst stored = (await this.provider.read(stage)) as ProviderState | null;\n\t\tif (!stored) return null;\n\t\tconst { resources = {}, ...state } = stored;\n\t\treturn { state, resources, version: versionOf(stored) };\n\t}\n\n\tasync write(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.read(stage);\n\t\tthis.check(stage, options.expectedVersion, current);\n\t\treturn this.put(stage, { ...state, resources: current?.resources ?? {} });\n\t}\n\n\tasync putResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.current(stage, options);\n\t\treturn this.put(stage, {\n\t\t\t...current.state,\n\t\t\tresources: {\n\t\t\t\t...current.resources,\n\t\t\t\t[record.key]: { ...record, updatedAt: new Date().toISOString() },\n\t\t\t},\n\t\t});\n\t}\n\n\tasync deleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.current(stage, options);\n\t\tconst { [key]: _removed, ...resources } = current.resources;\n\t\treturn this.put(stage, { ...current.state, resources });\n\t}\n\n\tprivate async current(\n\t\tstage: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StoredStageState> {\n\t\tconst current = await this.read(stage);\n\t\tif (options?.expectedVersion !== undefined) {\n\t\t\tthis.check(stage, options.expectedVersion, current);\n\t\t}\n\t\tif (!current) throw new StageStateMissing(stage);\n\t\treturn current;\n\t}\n\n\tprivate check(\n\t\tstage: string,\n\t\texpected: StateVersion | null,\n\t\tcurrent: StoredStageState | null,\n\t): void {\n\t\tconst actual = current?.version ?? null;\n\t\tif (actual !== expected) {\n\t\t\tthrow new StateVersionConflict(stage, expected, actual);\n\t\t}\n\t}\n\n\tprivate async put(\n\t\tstage: string,\n\t\tstate: ProviderState,\n\t): Promise<StateVersion> {\n\t\tthis.warnOnce();\n\t\t// Providers stamp `lastDeployedAt` on the object they are given, so the\n\t\t// version is taken after the write, from what was actually stored.\n\t\tawait this.provider.write(stage, state);\n\t\treturn versionOf(state);\n\t}\n\n\tprivate warnOnce(): void {\n\t\tif (this.warned) return;\n\t\tthis.warned = true;\n\t\tthis.warn(\n\t\t\tnew StateStoreWithoutLocking(\n\t\t\t\tthis.provider.constructor?.name || 'anonymous',\n\t\t\t),\n\t\t);\n\t}\n}\n\n/** Key order is the provider's, so this is stable for a given stored state. */\nfunction versionOf(state: ProviderState): StateVersion {\n\treturn contentVersion(JSON.stringify(state));\n}\n"],"mappings":";;AAuCA,IAAa,mBAAb,MAAoD;CAKzC;CAJV,SAAiB;CACjB;CAEA,YACC,UACA,UAAmC,CAAC,GACnC;EAFQ,KAAA,WAAA;EAGT,KAAK,OAAO,QAAQ,UAAU,YAAY,QAAQ,YAAY,OAAO;CACtE;CAEA,MAAM,KAAK,OAAe,SAA2C;EACpE,KAAK,SAAS;EACd,OAAO;GAAE;GAAO,QAAQA,gBAAAA,cAAc,OAAO;GAAG,SAAS,YAAY,CAAC;EAAE;CACzE;CAEA,MAAM,YAAY,QAA4C;EAC7D,OAAO;CACR;CAEA,MAAM,KAAK,OAAiD;EAC3D,MAAM,SAAU,MAAM,KAAK,SAAS,KAAK,KAAK;EAC9C,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,EAAE,YAAY,CAAC,GAAG,GAAG,UAAU;EACrC,OAAO;GAAE;GAAO;GAAW,SAAS,UAAU,MAAM;EAAE;CACvD;CAEA,MAAM,MACL,OACA,OACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,KAAK,KAAK;EACrC,KAAK,MAAM,OAAO,QAAQ,iBAAiB,OAAO;EAClD,OAAO,KAAK,IAAI,OAAO;GAAE,GAAG;GAAO,WAAW,SAAS,aAAa,CAAC;EAAE,CAAC;CACzE;CAEA,MAAM,YACL,OACA,QACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,OAAO;EACjD,OAAO,KAAK,IAAI,OAAO;GACtB,GAAG,QAAQ;GACX,WAAW;IACV,GAAG,QAAQ;KACV,OAAO,MAAM;KAAE,GAAG;KAAQ,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;IAAE;GAChE;EACD,CAAC;CACF;CAEA,MAAM,eACL,OACA,KACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,OAAO;EACjD,MAAM,GAAG,MAAM,UAAU,GAAG,cAAc,QAAQ;EAClD,OAAO,KAAK,IAAI,OAAO;GAAE,GAAG,QAAQ;GAAO;EAAU,CAAC;CACvD;CAEA,MAAc,QACb,OACA,SAC4B;EAC5B,MAAM,UAAU,MAAM,KAAK,KAAK,KAAK;EACrC,IAAI,SAAS,oBAAoB,KAAA,GAChC,KAAK,MAAM,OAAO,QAAQ,iBAAiB,OAAO;EAEnD,IAAI,CAAC,SAAS,MAAM,IAAIC,gBAAAA,kBAAkB,KAAK;EAC/C,OAAO;CACR;CAEA,MACC,OACA,UACA,SACO;EACP,MAAM,SAAS,SAAS,WAAW;EACnC,IAAI,WAAW,UACd,MAAM,IAAIC,gBAAAA,qBAAqB,OAAO,UAAU,MAAM;CAExD;CAEA,MAAc,IACb,OACA,OACwB;EACxB,KAAK,SAAS;EAGd,MAAM,KAAK,SAAS,MAAM,OAAO,KAAK;EACtC,OAAO,UAAU,KAAK;CACvB;CAEA,WAAyB;EACxB,IAAI,KAAK,QAAQ;EACjB,KAAK,SAAS;EACd,KAAK,KACJ,IAAIC,gBAAAA,yBACH,KAAK,SAAS,aAAa,QAAQ,WACpC,CACD;CACD;AACD;;AAGA,SAAS,UAAU,OAAoC;CACtD,OAAOC,gBAAAA,eAAe,KAAK,UAAU,KAAK,CAAC;AAC5C"}
@@ -0,0 +1,165 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
2
+ import { d as StateLocked, f as StateStoreBusy, h as contentVersion, l as DocumentStateStore, m as StateVersionConflict } from "./sniffer-BM8fpCas.mjs";
3
+ import { join } from "node:path";
4
+ import { mkdir, open, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
5
+ import { randomUUID } from "node:crypto";
6
+ //#region src/deploy/LocalStateStore.ts
7
+ /**
8
+ * Local filesystem state store.
9
+ *
10
+ * `.gkm/deploy-<stage>.json` holds the state, `.gkm/deploy-<stage>.lock` the
11
+ * lock. Every file is mode 0600: state holds database passwords and IAM keys.
12
+ */
13
+ var LocalStateStore_exports = /* @__PURE__ */ __exportAll({ LocalStateStore: () => LocalStateStore });
14
+ const FILE_MODE = 384;
15
+ /** How long a write waits for another process's write to finish. */
16
+ const MUTEX_TIMEOUT_MS = 5e3;
17
+ /**
18
+ * A write holds the mutex for milliseconds, so one this old was left by a
19
+ * process that died mid-write and is safe to take over.
20
+ */
21
+ const MUTEX_STALE_MS = 3e4;
22
+ function isCode(error, code) {
23
+ return error?.code === code;
24
+ }
25
+ var LocalStateStore = class extends DocumentStateStore {
26
+ workspaceRoot;
27
+ constructor(workspaceRoot) {
28
+ super();
29
+ this.workspaceRoot = workspaceRoot;
30
+ }
31
+ get dir() {
32
+ return join(this.workspaceRoot, ".gkm");
33
+ }
34
+ statePath(stage) {
35
+ return join(this.dir, `deploy-${stage}.json`);
36
+ }
37
+ lockPath(stage) {
38
+ return join(this.dir, `deploy-${stage}.lock`);
39
+ }
40
+ backupPath(stage) {
41
+ return join(this.dir, `deploy-${stage}.v1.json`);
42
+ }
43
+ mutexPath(stage) {
44
+ return join(this.dir, `deploy-${stage}.json.mutex`);
45
+ }
46
+ location(stage) {
47
+ return this.statePath(stage);
48
+ }
49
+ async readRaw(stage) {
50
+ try {
51
+ const body = await readFile(this.statePath(stage), "utf-8");
52
+ return {
53
+ body,
54
+ version: contentVersion(body)
55
+ };
56
+ } catch (error) {
57
+ if (isCode(error, "ENOENT")) return null;
58
+ throw error;
59
+ }
60
+ }
61
+ async writeRaw(stage, body, expectedVersion) {
62
+ await this.ensureDir();
63
+ return this.withMutex(stage, async () => {
64
+ const actual = (await this.readRaw(stage))?.version ?? null;
65
+ if (actual !== expectedVersion) throw new StateVersionConflict(stage, expectedVersion, actual);
66
+ const target = this.statePath(stage);
67
+ const temp = `${target}.${randomUUID()}.tmp`;
68
+ try {
69
+ const handle = await open(temp, "wx", FILE_MODE);
70
+ try {
71
+ await handle.writeFile(body);
72
+ await handle.sync();
73
+ } finally {
74
+ await handle.close();
75
+ }
76
+ await rename(temp, target);
77
+ } catch (error) {
78
+ await rm(temp, { force: true });
79
+ throw error;
80
+ }
81
+ return contentVersion(body);
82
+ });
83
+ }
84
+ async writeV1Backup(stage, body) {
85
+ try {
86
+ await writeFile(this.backupPath(stage), body, {
87
+ flag: "wx",
88
+ mode: FILE_MODE
89
+ });
90
+ } catch (error) {
91
+ if (!isCode(error, "EEXIST")) throw error;
92
+ }
93
+ }
94
+ async createLock(stage, holder) {
95
+ await this.ensureDir();
96
+ let handle;
97
+ try {
98
+ handle = await open(this.lockPath(stage), "wx", FILE_MODE);
99
+ } catch (error) {
100
+ if (isCode(error, "EEXIST")) throw new StateLocked(stage, await this.readLock(stage), this.lockPath(stage));
101
+ throw error;
102
+ }
103
+ try {
104
+ await handle.writeFile(JSON.stringify(holder, null, 2));
105
+ } finally {
106
+ await handle.close();
107
+ }
108
+ }
109
+ async readLock(stage) {
110
+ try {
111
+ return JSON.parse(await readFile(this.lockPath(stage), "utf-8"));
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+ async removeLock(stage, holder) {
117
+ if (holder) {
118
+ if ((await this.readLock(stage))?.id !== holder.id) return;
119
+ }
120
+ await rm(this.lockPath(stage), { force: true });
121
+ }
122
+ async ensureDir() {
123
+ await mkdir(this.dir, {
124
+ recursive: true,
125
+ mode: 448
126
+ });
127
+ }
128
+ /**
129
+ * Serialises the check-then-rename of a conditional write across
130
+ * processes. The stage lock is for whole runs; this is for the instant of
131
+ * a single write, so even writers that skip the lock never interleave.
132
+ */
133
+ async withMutex(stage, fn) {
134
+ const path = this.mutexPath(stage);
135
+ const deadline = Date.now() + MUTEX_TIMEOUT_MS;
136
+ for (;;) try {
137
+ await (await open(path, "wx", FILE_MODE)).close();
138
+ break;
139
+ } catch (error) {
140
+ if (!isCode(error, "EEXIST")) throw error;
141
+ if (await this.isStale(path)) {
142
+ await unlink(path).catch(() => {});
143
+ continue;
144
+ }
145
+ if (Date.now() > deadline) throw new StateStoreBusy(stage, path);
146
+ await new Promise((resolve) => setTimeout(resolve, 10));
147
+ }
148
+ try {
149
+ return await fn();
150
+ } finally {
151
+ await rm(path, { force: true });
152
+ }
153
+ }
154
+ async isStale(path) {
155
+ try {
156
+ return Date.now() - (await stat(path)).mtimeMs > MUTEX_STALE_MS;
157
+ } catch {
158
+ return false;
159
+ }
160
+ }
161
+ };
162
+ //#endregion
163
+ export { LocalStateStore_exports as n, LocalStateStore as t };
164
+
165
+ //# sourceMappingURL=LocalStateStore-BFLiOocU.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalStateStore-BFLiOocU.mjs","names":[],"sources":["../src/deploy/LocalStateStore.ts"],"sourcesContent":["/**\n * Local filesystem state store.\n *\n * `.gkm/deploy-<stage>.json` holds the state, `.gkm/deploy-<stage>.lock` the\n * lock. Every file is mode 0600: state holds database passwords and IAM keys.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport {\n\tmkdir,\n\topen,\n\treadFile,\n\trename,\n\trm,\n\tstat,\n\tunlink,\n\twriteFile,\n} from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n\tcontentVersion,\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\tStateStoreBusy,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nconst FILE_MODE = 0o600;\n\n/** How long a write waits for another process's write to finish. */\nconst MUTEX_TIMEOUT_MS = 5_000;\n/**\n * A write holds the mutex for milliseconds, so one this old was left by a\n * process that died mid-write and is safe to take over.\n */\nconst MUTEX_STALE_MS = 30_000;\n\nfunction isCode(error: unknown, code: string): boolean {\n\treturn (error as NodeJS.ErrnoException)?.code === code;\n}\n\nexport class LocalStateStore extends DocumentStateStore {\n\tconstructor(readonly workspaceRoot: string) {\n\t\tsuper();\n\t}\n\n\tprivate get dir(): string {\n\t\treturn join(this.workspaceRoot, '.gkm');\n\t}\n\n\tstatePath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.json`);\n\t}\n\n\tlockPath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.lock`);\n\t}\n\n\tbackupPath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.v1.json`);\n\t}\n\n\tprivate mutexPath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.json.mutex`);\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn this.statePath(stage);\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\ttry {\n\t\t\tconst body = await readFile(this.statePath(stage), 'utf-8');\n\t\t\treturn { body, version: contentVersion(body) };\n\t\t} catch (error) {\n\t\t\tif (isCode(error, 'ENOENT')) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tawait this.ensureDir();\n\t\treturn this.withMutex(stage, async () => {\n\t\t\tconst current = await this.readRaw(stage);\n\t\t\tconst actual = current?.version ?? null;\n\t\t\tif (actual !== expectedVersion) {\n\t\t\t\tthrow new StateVersionConflict(stage, expectedVersion, actual);\n\t\t\t}\n\n\t\t\t// Written beside the target and renamed over it: a reader sees the\n\t\t\t// old file or the new one, never half of either.\n\t\t\tconst target = this.statePath(stage);\n\t\t\tconst temp = `${target}.${randomUUID()}.tmp`;\n\t\t\ttry {\n\t\t\t\tconst handle = await open(temp, 'wx', FILE_MODE);\n\t\t\t\ttry {\n\t\t\t\t\tawait handle.writeFile(body);\n\t\t\t\t\tawait handle.sync();\n\t\t\t\t} finally {\n\t\t\t\t\tawait handle.close();\n\t\t\t\t}\n\t\t\t\tawait rename(temp, target);\n\t\t\t} catch (error) {\n\t\t\t\tawait rm(temp, { force: true });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\treturn contentVersion(body);\n\t\t});\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait writeFile(this.backupPath(stage), body, {\n\t\t\t\tflag: 'wx',\n\t\t\t\tmode: FILE_MODE,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!isCode(error, 'EEXIST')) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tawait this.ensureDir();\n\t\tlet handle: Awaited<ReturnType<typeof open>>;\n\t\ttry {\n\t\t\thandle = await open(this.lockPath(stage), 'wx', FILE_MODE);\n\t\t} catch (error) {\n\t\t\tif (isCode(error, 'EEXIST')) {\n\t\t\t\tthrow new StateLocked(\n\t\t\t\t\tstage,\n\t\t\t\t\tawait this.readLock(stage),\n\t\t\t\t\tthis.lockPath(stage),\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\ttry {\n\t\t\tawait handle.writeFile(JSON.stringify(holder, null, 2));\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\ttry {\n\t\t\treturn JSON.parse(\n\t\t\t\tawait readFile(this.lockPath(stage), 'utf-8'),\n\t\t\t) as LockHolder;\n\t\t} catch {\n\t\t\t// Missing, or created by a process that died before writing to it.\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tif (holder) {\n\t\t\tconst current = await this.readLock(stage);\n\t\t\tif (current?.id !== holder.id) return;\n\t\t}\n\t\tawait rm(this.lockPath(stage), { force: true });\n\t}\n\n\tprivate async ensureDir(): Promise<void> {\n\t\tawait mkdir(this.dir, { recursive: true, mode: 0o700 });\n\t}\n\n\t/**\n\t * Serialises the check-then-rename of a conditional write across\n\t * processes. The stage lock is for whole runs; this is for the instant of\n\t * a single write, so even writers that skip the lock never interleave.\n\t */\n\tprivate async withMutex<T>(stage: string, fn: () => Promise<T>): Promise<T> {\n\t\tconst path = this.mutexPath(stage);\n\t\tconst deadline = Date.now() + MUTEX_TIMEOUT_MS;\n\n\t\tfor (;;) {\n\t\t\ttry {\n\t\t\t\tconst handle = await open(path, 'wx', FILE_MODE);\n\t\t\t\tawait handle.close();\n\t\t\t\tbreak;\n\t\t\t} catch (error) {\n\t\t\t\tif (!isCode(error, 'EEXIST')) throw error;\n\t\t\t\tif (await this.isStale(path)) {\n\t\t\t\t\tawait unlink(path).catch(() => {});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (Date.now() > deadline) throw new StateStoreBusy(stage, path);\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 10));\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} finally {\n\t\t\tawait rm(path, { force: true });\n\t\t}\n\t}\n\n\tprivate async isStale(path: string): Promise<boolean> {\n\t\ttry {\n\t\t\treturn Date.now() - (await stat(path)).mtimeMs > MUTEX_STALE_MS;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"],"mappings":"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"}