@geekmidas/cli 0.53.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +26 -5
  3. package/dist/CachedStateProvider-D73dCqfH.cjs +60 -0
  4. package/dist/CachedStateProvider-D73dCqfH.cjs.map +1 -0
  5. package/dist/CachedStateProvider-DVyKfaMm.mjs +54 -0
  6. package/dist/CachedStateProvider-DVyKfaMm.mjs.map +1 -0
  7. package/dist/CachedStateProvider-D_uISMmJ.cjs +3 -0
  8. package/dist/CachedStateProvider-OiFUGr7p.mjs +3 -0
  9. package/dist/HostingerProvider-DUV9-Tzg.cjs +210 -0
  10. package/dist/HostingerProvider-DUV9-Tzg.cjs.map +1 -0
  11. package/dist/HostingerProvider-DqUq6e9i.mjs +210 -0
  12. package/dist/HostingerProvider-DqUq6e9i.mjs.map +1 -0
  13. package/dist/LocalStateProvider-CdspeSVL.cjs +43 -0
  14. package/dist/LocalStateProvider-CdspeSVL.cjs.map +1 -0
  15. package/dist/LocalStateProvider-DxoSaWUV.mjs +42 -0
  16. package/dist/LocalStateProvider-DxoSaWUV.mjs.map +1 -0
  17. package/dist/Route53Provider-CpRIqu69.cjs +157 -0
  18. package/dist/Route53Provider-CpRIqu69.cjs.map +1 -0
  19. package/dist/Route53Provider-KUAX3vz9.mjs +156 -0
  20. package/dist/Route53Provider-KUAX3vz9.mjs.map +1 -0
  21. package/dist/SSMStateProvider-BxAPU99a.cjs +53 -0
  22. package/dist/SSMStateProvider-BxAPU99a.cjs.map +1 -0
  23. package/dist/SSMStateProvider-C4wp4AZe.mjs +52 -0
  24. package/dist/SSMStateProvider-C4wp4AZe.mjs.map +1 -0
  25. package/dist/{bundler-DGry2vaR.mjs → bundler-BqTN5Dj5.mjs} +3 -3
  26. package/dist/{bundler-DGry2vaR.mjs.map → bundler-BqTN5Dj5.mjs.map} +1 -1
  27. package/dist/{bundler-BB-kETMd.cjs → bundler-tHLLwYuU.cjs} +3 -3
  28. package/dist/{bundler-BB-kETMd.cjs.map → bundler-tHLLwYuU.cjs.map} +1 -1
  29. package/dist/{config-HYiM3iQJ.cjs → config-BGeJsW1r.cjs} +2 -2
  30. package/dist/{config-HYiM3iQJ.cjs.map → config-BGeJsW1r.cjs.map} +1 -1
  31. package/dist/{config-C3LSBNSl.mjs → config-C6awcFBx.mjs} +2 -2
  32. package/dist/{config-C3LSBNSl.mjs.map → config-C6awcFBx.mjs.map} +1 -1
  33. package/dist/config.cjs +2 -2
  34. package/dist/config.d.cts +1 -1
  35. package/dist/config.d.mts +2 -2
  36. package/dist/config.mjs +2 -2
  37. package/dist/credentials-C8DWtnMY.cjs +174 -0
  38. package/dist/credentials-C8DWtnMY.cjs.map +1 -0
  39. package/dist/credentials-DT1dSxIx.mjs +126 -0
  40. package/dist/credentials-DT1dSxIx.mjs.map +1 -0
  41. package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
  42. package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
  43. package/dist/deploy/sniffer-loader.cjs +1 -1
  44. package/dist/{dokploy-api-94KzmTVf.mjs → dokploy-api-7k3t7_zd.mjs} +1 -1
  45. package/dist/{dokploy-api-94KzmTVf.mjs.map → dokploy-api-7k3t7_zd.mjs.map} +1 -1
  46. package/dist/dokploy-api-CHa8G51l.mjs +3 -0
  47. package/dist/{dokploy-api-YD8WCQfW.cjs → dokploy-api-CQvhV6Hd.cjs} +1 -1
  48. package/dist/{dokploy-api-YD8WCQfW.cjs.map → dokploy-api-CQvhV6Hd.cjs.map} +1 -1
  49. package/dist/dokploy-api-CWc02yyg.cjs +3 -0
  50. package/dist/{encryption-DaCB_NmS.cjs → encryption-BE0UOb8j.cjs} +1 -1
  51. package/dist/{encryption-DaCB_NmS.cjs.map → encryption-BE0UOb8j.cjs.map} +1 -1
  52. package/dist/{encryption-Biq0EZ4m.cjs → encryption-Cv3zips0.cjs} +1 -1
  53. package/dist/{encryption-BC4MAODn.mjs → encryption-JtMsiGNp.mjs} +1 -1
  54. package/dist/{encryption-BC4MAODn.mjs.map → encryption-JtMsiGNp.mjs.map} +1 -1
  55. package/dist/encryption-UUmaWAmz.mjs +3 -0
  56. package/dist/{index-pOA56MWT.d.cts → index-B5rGIc4g.d.cts} +553 -196
  57. package/dist/index-B5rGIc4g.d.cts.map +1 -0
  58. package/dist/{index-A70abJ1m.d.mts → index-KFEbMIRa.d.mts} +554 -197
  59. package/dist/index-KFEbMIRa.d.mts.map +1 -0
  60. package/dist/index.cjs +2242 -568
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.mjs +2219 -545
  63. package/dist/index.mjs.map +1 -1
  64. package/dist/{openapi-C3C-BzIZ.mjs → openapi-BMFmLnX6.mjs} +51 -7
  65. package/dist/openapi-BMFmLnX6.mjs.map +1 -0
  66. package/dist/{openapi-D7WwlpPF.cjs → openapi-D1KXv2Ml.cjs} +51 -7
  67. package/dist/openapi-D1KXv2Ml.cjs.map +1 -0
  68. package/dist/{openapi-react-query-C_MxpBgF.cjs → openapi-react-query-BeXvk-wa.cjs} +1 -1
  69. package/dist/{openapi-react-query-C_MxpBgF.cjs.map → openapi-react-query-BeXvk-wa.cjs.map} +1 -1
  70. package/dist/{openapi-react-query-ZoP9DPbY.mjs → openapi-react-query-DGEkD39r.mjs} +1 -1
  71. package/dist/{openapi-react-query-ZoP9DPbY.mjs.map → openapi-react-query-DGEkD39r.mjs.map} +1 -1
  72. package/dist/openapi-react-query.cjs +1 -1
  73. package/dist/openapi-react-query.mjs +1 -1
  74. package/dist/openapi.cjs +3 -3
  75. package/dist/openapi.d.cts +1 -1
  76. package/dist/openapi.d.mts +2 -2
  77. package/dist/openapi.mjs +3 -3
  78. package/dist/{storage-Dhst7BhI.mjs → storage-BMW6yLu3.mjs} +1 -1
  79. package/dist/{storage-Dhst7BhI.mjs.map → storage-BMW6yLu3.mjs.map} +1 -1
  80. package/dist/{storage-fOR8dMu5.cjs → storage-C7pmBq1u.cjs} +1 -1
  81. package/dist/{storage-BPRgh3DU.cjs → storage-CoCNe0Pt.cjs} +1 -1
  82. package/dist/{storage-BPRgh3DU.cjs.map → storage-CoCNe0Pt.cjs.map} +1 -1
  83. package/dist/{storage-DNj_I11J.mjs → storage-D8XzjVaO.mjs} +1 -1
  84. package/dist/{types-BtGL-8QS.d.mts → types-BldpmqQX.d.mts} +1 -1
  85. package/dist/{types-BtGL-8QS.d.mts.map → types-BldpmqQX.d.mts.map} +1 -1
  86. package/dist/workspace/index.cjs +1 -1
  87. package/dist/workspace/index.d.cts +1 -1
  88. package/dist/workspace/index.d.mts +2 -2
  89. package/dist/workspace/index.mjs +1 -1
  90. package/dist/{workspace-CaVW6j2q.cjs → workspace-BFRUOOrh.cjs} +309 -25
  91. package/dist/workspace-BFRUOOrh.cjs.map +1 -0
  92. package/dist/{workspace-DLFRaDc-.mjs → workspace-DAxG3_H2.mjs} +309 -25
  93. package/dist/workspace-DAxG3_H2.mjs.map +1 -0
  94. package/package.json +12 -8
  95. package/src/build/__tests__/handler-templates.spec.ts +115 -47
  96. package/src/deploy/CachedStateProvider.ts +86 -0
  97. package/src/deploy/LocalStateProvider.ts +57 -0
  98. package/src/deploy/SSMStateProvider.ts +93 -0
  99. package/src/deploy/StateProvider.ts +171 -0
  100. package/src/deploy/__tests__/CachedStateProvider.spec.ts +228 -0
  101. package/src/deploy/__tests__/HostingerProvider.spec.ts +347 -0
  102. package/src/deploy/__tests__/LocalStateProvider.spec.ts +126 -0
  103. package/src/deploy/__tests__/Route53Provider.spec.ts +402 -0
  104. package/src/deploy/__tests__/SSMStateProvider.spec.ts +177 -0
  105. package/src/deploy/__tests__/__fixtures__/env-parsers/throwing-env-parser.ts +1 -3
  106. package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/auth.ts +16 -0
  107. package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/health.ts +13 -0
  108. package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/users.ts +15 -0
  109. package/src/deploy/__tests__/__fixtures__/route-apps/services.ts +55 -0
  110. package/src/deploy/__tests__/createDnsProvider.spec.ts +172 -0
  111. package/src/deploy/__tests__/createStateProvider.spec.ts +116 -0
  112. package/src/deploy/__tests__/dns-orchestration.spec.ts +192 -0
  113. package/src/deploy/__tests__/dns-verification.spec.ts +2 -2
  114. package/src/deploy/__tests__/env-resolver.spec.ts +41 -17
  115. package/src/deploy/__tests__/sniffer.spec.ts +168 -10
  116. package/src/deploy/__tests__/state.spec.ts +13 -5
  117. package/src/deploy/dns/DnsProvider.ts +163 -0
  118. package/src/deploy/dns/HostingerProvider.ts +100 -0
  119. package/src/deploy/dns/Route53Provider.ts +256 -0
  120. package/src/deploy/dns/index.ts +257 -165
  121. package/src/deploy/env-resolver.ts +12 -5
  122. package/src/deploy/index.ts +16 -13
  123. package/src/deploy/sniffer-envkit-patch.ts +3 -1
  124. package/src/deploy/sniffer-routes-worker.ts +104 -0
  125. package/src/deploy/sniffer.ts +130 -5
  126. package/src/deploy/state-commands.ts +274 -0
  127. package/src/dev/__tests__/entry.spec.ts +8 -2
  128. package/src/dev/__tests__/index.spec.ts +1 -3
  129. package/src/dev/index.ts +9 -3
  130. package/src/docker/__tests__/templates.spec.ts +3 -1
  131. package/src/docker/templates.ts +3 -3
  132. package/src/index.ts +88 -0
  133. package/src/init/__tests__/generators.spec.ts +273 -0
  134. package/src/init/__tests__/init.spec.ts +3 -3
  135. package/src/init/generators/auth.ts +1 -0
  136. package/src/init/generators/config.ts +2 -0
  137. package/src/init/generators/models.ts +6 -1
  138. package/src/init/generators/monorepo.ts +3 -0
  139. package/src/init/generators/ui.ts +1472 -0
  140. package/src/init/generators/web.ts +134 -87
  141. package/src/init/index.ts +22 -3
  142. package/src/init/templates/api.ts +109 -3
  143. package/src/openapi.ts +99 -13
  144. package/src/workspace/__tests__/schema.spec.ts +107 -0
  145. package/src/workspace/schema.ts +314 -4
  146. package/src/workspace/types.ts +22 -36
  147. package/dist/dokploy-api-CItuaWTq.mjs +0 -3
  148. package/dist/dokploy-api-DBNE8MDt.cjs +0 -3
  149. package/dist/encryption-CQXBZGkt.mjs +0 -3
  150. package/dist/index-A70abJ1m.d.mts.map +0 -1
  151. package/dist/index-pOA56MWT.d.cts.map +0 -1
  152. package/dist/openapi-C3C-BzIZ.mjs.map +0 -1
  153. package/dist/openapi-D7WwlpPF.cjs.map +0 -1
  154. package/dist/workspace-CaVW6j2q.cjs.map +0 -1
  155. package/dist/workspace-DLFRaDc-.mjs.map +0 -1
  156. package/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # @geekmidas/cli
2
+
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [`ff7b115`](https://github.com/geekmidas/toolbox/commit/ff7b11599f60f84ac6cdc73714c853ecf786b2e8) Thanks [@geekmidas](https://github.com/geekmidas)! - Version 1 Stable release
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`ff7b115`](https://github.com/geekmidas/toolbox/commit/ff7b11599f60f84ac6cdc73714c853ecf786b2e8)]:
12
+ - @geekmidas/constructs@1.0.0
13
+ - @geekmidas/envkit@1.0.0
14
+ - @geekmidas/errors@1.0.0
15
+ - @geekmidas/logger@1.0.0
16
+ - @geekmidas/schema@1.0.0
17
+ - @geekmidas/telescope@1.0.0
package/README.md CHANGED
@@ -840,11 +840,32 @@ gkm deploy --provider docker --stage production --tag v1.0.0
840
840
  ```
841
841
 
842
842
  **Workflow:**
843
- 1. Builds production bundle with `gkm build --provider server --production --stage <stage>`
844
- 2. Encrypts secrets from `.gkm/secrets/<stage>.json` into the bundle
845
- 3. Generates Docker files with `gkm docker`
846
- 4. Builds and pushes Docker image
847
- 5. (Dokploy) Triggers deployment via API with `GKM_MASTER_KEY`
843
+ 1. **Sniffs environment variables** - Automatically detects which env vars each app needs
844
+ 2. Builds production bundle with `gkm build --provider server --production --stage <stage>`
845
+ 3. Encrypts secrets from `.gkm/secrets/<stage>.json` into the bundle
846
+ 4. Generates Docker files with `gkm docker`
847
+ 5. Builds and pushes Docker image
848
+ 6. (Dokploy) Triggers deployment via API with `GKM_MASTER_KEY`
849
+
850
+ **Environment Variable Detection:**
851
+
852
+ The deploy command automatically detects required environment variables for each app using different strategies based on app configuration:
853
+
854
+ | App Type | Detection Strategy |
855
+ |----------|-------------------|
856
+ | Frontend apps | Returns empty (no server secrets) |
857
+ | Apps with `requiredEnv` | Uses explicit list from config |
858
+ | Entry-based apps | Imports entry file in subprocess to capture `config.parse()` calls |
859
+ | Route-based apps | Loads routes and calls `getEnvironment()` on each construct |
860
+ | Apps with `envParser` only | Runs SnifferEnvironmentParser to detect usage |
861
+
862
+ For route-based apps, the sniffer loads each endpoint/function/cron/subscriber and collects environment variables from:
863
+ - All services attached to the construct (via `service.register()`)
864
+ - Publisher service (if any)
865
+ - Auditor storage service (if any)
866
+ - Database service (if any)
867
+
868
+ This allows the CLI to validate that all required secrets are configured before deployment, preventing runtime errors from missing environment variables.
848
869
 
849
870
  **Configuration:**
850
871
 
@@ -0,0 +1,60 @@
1
+
2
+ //#region src/deploy/CachedStateProvider.ts
3
+ /**
4
+ * Cached state provider that wraps a remote provider with local cache.
5
+ */
6
+ var CachedStateProvider = class {
7
+ constructor(remote, local) {
8
+ this.remote = remote;
9
+ this.local = local;
10
+ }
11
+ async read(stage) {
12
+ const localState = await this.local.read(stage);
13
+ if (localState) return localState;
14
+ const remoteState = await this.remote.read(stage);
15
+ if (remoteState) await this.local.write(stage, remoteState);
16
+ return remoteState;
17
+ }
18
+ async write(stage, state) {
19
+ await this.remote.write(stage, state);
20
+ await this.local.write(stage, state);
21
+ }
22
+ /**
23
+ * Force pull from remote to local.
24
+ * Used by `gkm state pull` command.
25
+ */
26
+ async pull(stage) {
27
+ const remoteState = await this.remote.read(stage);
28
+ if (remoteState) await this.local.write(stage, remoteState);
29
+ return remoteState;
30
+ }
31
+ /**
32
+ * Force push from local to remote.
33
+ * Used by `gkm state push` command.
34
+ */
35
+ async push(stage) {
36
+ const localState = await this.local.read(stage);
37
+ if (localState) await this.remote.write(stage, localState);
38
+ return localState;
39
+ }
40
+ /**
41
+ * Get both local and remote state for comparison.
42
+ * Used by `gkm state diff` command.
43
+ */
44
+ async diff(stage) {
45
+ const [local, remote] = await Promise.all([this.local.read(stage), this.remote.read(stage)]);
46
+ return {
47
+ local,
48
+ remote
49
+ };
50
+ }
51
+ };
52
+
53
+ //#endregion
54
+ Object.defineProperty(exports, 'CachedStateProvider', {
55
+ enumerable: true,
56
+ get: function () {
57
+ return CachedStateProvider;
58
+ }
59
+ });
60
+ //# sourceMappingURL=CachedStateProvider-D73dCqfH.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CachedStateProvider-D73dCqfH.cjs","names":["remote: StateProvider","local: StateProvider","stage: string","state: DokployStageState"],"sources":["../src/deploy/CachedStateProvider.ts"],"sourcesContent":["/**\n * Cached State Provider\n *\n * Wraps a remote state provider (SSM) with local filesystem cache.\n * - Read: Local first, then remote if missing\n * - Write: Remote first, then update local cache\n *\n * This enables fast local development while keeping SSM as source of truth.\n */\n\nimport type { StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/**\n * Cached state provider that wraps a remote provider with local cache.\n */\nexport class CachedStateProvider implements StateProvider {\n\tconstructor(\n\t\tprivate readonly remote: StateProvider,\n\t\tprivate readonly local: StateProvider,\n\t) {}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\t// Try local cache first\n\t\tconst localState = await this.local.read(stage);\n\t\tif (localState) {\n\t\t\treturn localState;\n\t\t}\n\n\t\t// Fall back to remote\n\t\tconst remoteState = await this.remote.read(stage);\n\t\tif (remoteState) {\n\t\t\t// Update local cache\n\t\t\tawait this.local.write(stage, remoteState);\n\t\t}\n\n\t\treturn remoteState;\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\t// Write to remote first (source of truth)\n\t\tawait this.remote.write(stage, state);\n\n\t\t// Update local cache\n\t\tawait this.local.write(stage, state);\n\t}\n\n\t/**\n\t * Force pull from remote to local.\n\t * Used by `gkm state pull` command.\n\t */\n\tasync pull(stage: string): Promise<DokployStageState | null> {\n\t\tconst remoteState = await this.remote.read(stage);\n\t\tif (remoteState) {\n\t\t\tawait this.local.write(stage, remoteState);\n\t\t}\n\t\treturn remoteState;\n\t}\n\n\t/**\n\t * Force push from local to remote.\n\t * Used by `gkm state push` command.\n\t */\n\tasync push(stage: string): Promise<DokployStageState | null> {\n\t\tconst localState = await this.local.read(stage);\n\t\tif (localState) {\n\t\t\tawait this.remote.write(stage, localState);\n\t\t}\n\t\treturn localState;\n\t}\n\n\t/**\n\t * Get both local and remote state for comparison.\n\t * Used by `gkm state diff` command.\n\t */\n\tasync diff(stage: string): Promise<{\n\t\tlocal: DokployStageState | null;\n\t\tremote: DokployStageState | null;\n\t}> {\n\t\tconst [local, remote] = await Promise.all([\n\t\t\tthis.local.read(stage),\n\t\t\tthis.remote.read(stage),\n\t\t]);\n\t\treturn { local, remote };\n\t}\n}\n"],"mappings":";;;;;AAgBA,IAAa,sBAAb,MAA0D;CACzD,YACkBA,QACAC,OAChB;EAFgB;EACA;CACd;CAEJ,MAAM,KAAKC,OAAkD;EAE5D,MAAM,aAAa,MAAM,KAAK,MAAM,KAAK,MAAM;AAC/C,MAAI,WACH,QAAO;EAIR,MAAM,cAAc,MAAM,KAAK,OAAO,KAAK,MAAM;AACjD,MAAI,YAEH,OAAM,KAAK,MAAM,MAAM,OAAO,YAAY;AAG3C,SAAO;CACP;CAED,MAAM,MAAMA,OAAeC,OAAyC;AAEnE,QAAM,KAAK,OAAO,MAAM,OAAO,MAAM;AAGrC,QAAM,KAAK,MAAM,MAAM,OAAO,MAAM;CACpC;;;;;CAMD,MAAM,KAAKD,OAAkD;EAC5D,MAAM,cAAc,MAAM,KAAK,OAAO,KAAK,MAAM;AACjD,MAAI,YACH,OAAM,KAAK,MAAM,MAAM,OAAO,YAAY;AAE3C,SAAO;CACP;;;;;CAMD,MAAM,KAAKA,OAAkD;EAC5D,MAAM,aAAa,MAAM,KAAK,MAAM,KAAK,MAAM;AAC/C,MAAI,WACH,OAAM,KAAK,OAAO,MAAM,OAAO,WAAW;AAE3C,SAAO;CACP;;;;;CAMD,MAAM,KAAKA,OAGR;EACF,MAAM,CAAC,OAAO,OAAO,GAAG,MAAM,QAAQ,IAAI,CACzC,KAAK,MAAM,KAAK,MAAM,EACtB,KAAK,OAAO,KAAK,MAAM,AACvB,EAAC;AACF,SAAO;GAAE;GAAO;EAAQ;CACxB;AACD"}
@@ -0,0 +1,54 @@
1
+ //#region src/deploy/CachedStateProvider.ts
2
+ /**
3
+ * Cached state provider that wraps a remote provider with local cache.
4
+ */
5
+ var CachedStateProvider = class {
6
+ constructor(remote, local) {
7
+ this.remote = remote;
8
+ this.local = local;
9
+ }
10
+ async read(stage) {
11
+ const localState = await this.local.read(stage);
12
+ if (localState) return localState;
13
+ const remoteState = await this.remote.read(stage);
14
+ if (remoteState) await this.local.write(stage, remoteState);
15
+ return remoteState;
16
+ }
17
+ async write(stage, state) {
18
+ await this.remote.write(stage, state);
19
+ await this.local.write(stage, state);
20
+ }
21
+ /**
22
+ * Force pull from remote to local.
23
+ * Used by `gkm state pull` command.
24
+ */
25
+ async pull(stage) {
26
+ const remoteState = await this.remote.read(stage);
27
+ if (remoteState) await this.local.write(stage, remoteState);
28
+ return remoteState;
29
+ }
30
+ /**
31
+ * Force push from local to remote.
32
+ * Used by `gkm state push` command.
33
+ */
34
+ async push(stage) {
35
+ const localState = await this.local.read(stage);
36
+ if (localState) await this.remote.write(stage, localState);
37
+ return localState;
38
+ }
39
+ /**
40
+ * Get both local and remote state for comparison.
41
+ * Used by `gkm state diff` command.
42
+ */
43
+ async diff(stage) {
44
+ const [local, remote] = await Promise.all([this.local.read(stage), this.remote.read(stage)]);
45
+ return {
46
+ local,
47
+ remote
48
+ };
49
+ }
50
+ };
51
+
52
+ //#endregion
53
+ export { CachedStateProvider };
54
+ //# sourceMappingURL=CachedStateProvider-DVyKfaMm.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CachedStateProvider-DVyKfaMm.mjs","names":["remote: StateProvider","local: StateProvider","stage: string","state: DokployStageState"],"sources":["../src/deploy/CachedStateProvider.ts"],"sourcesContent":["/**\n * Cached State Provider\n *\n * Wraps a remote state provider (SSM) with local filesystem cache.\n * - Read: Local first, then remote if missing\n * - Write: Remote first, then update local cache\n *\n * This enables fast local development while keeping SSM as source of truth.\n */\n\nimport type { StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/**\n * Cached state provider that wraps a remote provider with local cache.\n */\nexport class CachedStateProvider implements StateProvider {\n\tconstructor(\n\t\tprivate readonly remote: StateProvider,\n\t\tprivate readonly local: StateProvider,\n\t) {}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\t// Try local cache first\n\t\tconst localState = await this.local.read(stage);\n\t\tif (localState) {\n\t\t\treturn localState;\n\t\t}\n\n\t\t// Fall back to remote\n\t\tconst remoteState = await this.remote.read(stage);\n\t\tif (remoteState) {\n\t\t\t// Update local cache\n\t\t\tawait this.local.write(stage, remoteState);\n\t\t}\n\n\t\treturn remoteState;\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\t// Write to remote first (source of truth)\n\t\tawait this.remote.write(stage, state);\n\n\t\t// Update local cache\n\t\tawait this.local.write(stage, state);\n\t}\n\n\t/**\n\t * Force pull from remote to local.\n\t * Used by `gkm state pull` command.\n\t */\n\tasync pull(stage: string): Promise<DokployStageState | null> {\n\t\tconst remoteState = await this.remote.read(stage);\n\t\tif (remoteState) {\n\t\t\tawait this.local.write(stage, remoteState);\n\t\t}\n\t\treturn remoteState;\n\t}\n\n\t/**\n\t * Force push from local to remote.\n\t * Used by `gkm state push` command.\n\t */\n\tasync push(stage: string): Promise<DokployStageState | null> {\n\t\tconst localState = await this.local.read(stage);\n\t\tif (localState) {\n\t\t\tawait this.remote.write(stage, localState);\n\t\t}\n\t\treturn localState;\n\t}\n\n\t/**\n\t * Get both local and remote state for comparison.\n\t * Used by `gkm state diff` command.\n\t */\n\tasync diff(stage: string): Promise<{\n\t\tlocal: DokployStageState | null;\n\t\tremote: DokployStageState | null;\n\t}> {\n\t\tconst [local, remote] = await Promise.all([\n\t\t\tthis.local.read(stage),\n\t\t\tthis.remote.read(stage),\n\t\t]);\n\t\treturn { local, remote };\n\t}\n}\n"],"mappings":";;;;AAgBA,IAAa,sBAAb,MAA0D;CACzD,YACkBA,QACAC,OAChB;EAFgB;EACA;CACd;CAEJ,MAAM,KAAKC,OAAkD;EAE5D,MAAM,aAAa,MAAM,KAAK,MAAM,KAAK,MAAM;AAC/C,MAAI,WACH,QAAO;EAIR,MAAM,cAAc,MAAM,KAAK,OAAO,KAAK,MAAM;AACjD,MAAI,YAEH,OAAM,KAAK,MAAM,MAAM,OAAO,YAAY;AAG3C,SAAO;CACP;CAED,MAAM,MAAMA,OAAeC,OAAyC;AAEnE,QAAM,KAAK,OAAO,MAAM,OAAO,MAAM;AAGrC,QAAM,KAAK,MAAM,MAAM,OAAO,MAAM;CACpC;;;;;CAMD,MAAM,KAAKD,OAAkD;EAC5D,MAAM,cAAc,MAAM,KAAK,OAAO,KAAK,MAAM;AACjD,MAAI,YACH,OAAM,KAAK,MAAM,MAAM,OAAO,YAAY;AAE3C,SAAO;CACP;;;;;CAMD,MAAM,KAAKA,OAAkD;EAC5D,MAAM,aAAa,MAAM,KAAK,MAAM,KAAK,MAAM;AAC/C,MAAI,WACH,OAAM,KAAK,OAAO,MAAM,OAAO,WAAW;AAE3C,SAAO;CACP;;;;;CAMD,MAAM,KAAKA,OAGR;EACF,MAAM,CAAC,OAAO,OAAO,GAAG,MAAM,QAAQ,IAAI,CACzC,KAAK,MAAM,KAAK,MAAM,EACtB,KAAK,OAAO,KAAK,MAAM,AACvB,EAAC;AACF,SAAO;GAAE;GAAO;EAAQ;CACxB;AACD"}
@@ -0,0 +1,3 @@
1
+ const require_CachedStateProvider = require('./CachedStateProvider-D73dCqfH.cjs');
2
+
3
+ exports.CachedStateProvider = require_CachedStateProvider.CachedStateProvider;
@@ -0,0 +1,3 @@
1
+ import { CachedStateProvider } from "./CachedStateProvider-DVyKfaMm.mjs";
2
+
3
+ export { CachedStateProvider };
@@ -0,0 +1,210 @@
1
+ const require_credentials = require('./credentials-C8DWtnMY.cjs');
2
+
3
+ //#region src/deploy/dns/hostinger-api.ts
4
+ /**
5
+ * Hostinger DNS API client
6
+ *
7
+ * API Documentation: https://developers.hostinger.com/
8
+ * Authentication: Bearer token from hpanel.hostinger.com/profile/api
9
+ */
10
+ const HOSTINGER_API_BASE = "https://developers.hostinger.com";
11
+ /**
12
+ * Hostinger API error
13
+ */
14
+ var HostingerApiError = class extends Error {
15
+ constructor(message, status, statusText, errors) {
16
+ super(message);
17
+ this.status = status;
18
+ this.statusText = statusText;
19
+ this.errors = errors;
20
+ this.name = "HostingerApiError";
21
+ }
22
+ };
23
+ /**
24
+ * Hostinger DNS API client
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const api = new HostingerApi(token);
29
+ *
30
+ * // Get all records for a domain
31
+ * const records = await api.getRecords('traflabs.io');
32
+ *
33
+ * // Create/update records
34
+ * await api.upsertRecords('traflabs.io', [
35
+ * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }
36
+ * ]);
37
+ * ```
38
+ */
39
+ var HostingerApi = class {
40
+ token;
41
+ constructor(token) {
42
+ this.token = token;
43
+ }
44
+ /**
45
+ * Make a request to the Hostinger API
46
+ */
47
+ async request(method, endpoint, body) {
48
+ const url = `${HOSTINGER_API_BASE}${endpoint}`;
49
+ const response = await fetch(url, {
50
+ method,
51
+ headers: {
52
+ "Content-Type": "application/json",
53
+ Authorization: `Bearer ${this.token}`
54
+ },
55
+ body: body ? JSON.stringify(body) : void 0
56
+ });
57
+ if (!response.ok) {
58
+ let errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;
59
+ let errors;
60
+ try {
61
+ const errorBody = await response.json();
62
+ if (errorBody.message) errorMessage = `Hostinger API error: ${errorBody.message}`;
63
+ errors = errorBody.errors;
64
+ } catch {}
65
+ throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
66
+ }
67
+ const text = await response.text();
68
+ if (!text || text.trim() === "") return void 0;
69
+ return JSON.parse(text);
70
+ }
71
+ /**
72
+ * Get all DNS records for a domain
73
+ *
74
+ * @param domain - Root domain (e.g., 'traflabs.io')
75
+ */
76
+ async getRecords(domain) {
77
+ const response = await this.request("GET", `/api/dns/v1/zones/${domain}`);
78
+ return response.data || [];
79
+ }
80
+ /**
81
+ * Create or update DNS records
82
+ *
83
+ * @param domain - Root domain (e.g., 'traflabs.io')
84
+ * @param records - Records to create/update
85
+ * @param overwrite - If true, replaces all existing records. If false, merges with existing.
86
+ */
87
+ async upsertRecords(domain, records, overwrite = false) {
88
+ await this.request("PUT", `/api/dns/v1/zones/${domain}`, {
89
+ overwrite,
90
+ zone: records
91
+ });
92
+ }
93
+ /**
94
+ * Validate DNS records before applying
95
+ *
96
+ * @param domain - Root domain (e.g., 'traflabs.io')
97
+ * @param records - Records to validate
98
+ * @returns true if valid, throws if invalid
99
+ */
100
+ async validateRecords(domain, records) {
101
+ await this.request("POST", `/api/dns/v1/zones/${domain}/validate`, {
102
+ overwrite: false,
103
+ zone: records
104
+ });
105
+ return true;
106
+ }
107
+ /**
108
+ * Delete specific DNS records
109
+ *
110
+ * @param domain - Root domain (e.g., 'traflabs.io')
111
+ * @param filters - Filters to match records for deletion
112
+ */
113
+ async deleteRecords(domain, filters) {
114
+ await this.request("DELETE", `/api/dns/v1/zones/${domain}`, { filters });
115
+ }
116
+ /**
117
+ * Check if a specific record exists
118
+ *
119
+ * @param domain - Root domain (e.g., 'traflabs.io')
120
+ * @param name - Subdomain name (e.g., 'api.joemoer')
121
+ * @param type - Record type (e.g., 'A')
122
+ */
123
+ async recordExists(domain, name, type = "A") {
124
+ const records = await this.getRecords(domain);
125
+ return records.some((r) => r.name === name && r.type === type);
126
+ }
127
+ /**
128
+ * Create a single A record if it doesn't exist
129
+ *
130
+ * @param domain - Root domain (e.g., 'traflabs.io')
131
+ * @param subdomain - Subdomain name (e.g., 'api.joemoer')
132
+ * @param ip - IP address to point to
133
+ * @param ttl - TTL in seconds (default: 300)
134
+ * @returns true if created, false if already exists
135
+ */
136
+ async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
137
+ const exists = await this.recordExists(domain, subdomain, "A");
138
+ if (exists) return false;
139
+ await this.upsertRecords(domain, [{
140
+ name: subdomain,
141
+ type: "A",
142
+ ttl,
143
+ records: [{ content: ip }]
144
+ }]);
145
+ return true;
146
+ }
147
+ };
148
+
149
+ //#endregion
150
+ //#region src/deploy/dns/HostingerProvider.ts
151
+ /**
152
+ * Hostinger DNS provider implementation.
153
+ */
154
+ var HostingerProvider = class {
155
+ name = "hostinger";
156
+ api = null;
157
+ /**
158
+ * Get or create the Hostinger API client.
159
+ */
160
+ async getApi() {
161
+ if (this.api) return this.api;
162
+ const token = await require_credentials.getHostingerToken();
163
+ 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");
164
+ this.api = new HostingerApi(token);
165
+ return this.api;
166
+ }
167
+ async getRecords(domain) {
168
+ const api = await this.getApi();
169
+ const records = await api.getRecords(domain);
170
+ return records.map((r) => ({
171
+ name: r.name,
172
+ type: r.type,
173
+ ttl: r.ttl,
174
+ values: r.records.map((rec) => rec.content)
175
+ }));
176
+ }
177
+ async upsertRecords(domain, records) {
178
+ const api = await this.getApi();
179
+ const results = [];
180
+ const existingRecords = await api.getRecords(domain);
181
+ for (const record of records) {
182
+ const existing = existingRecords.find((r) => r.name === record.name && r.type === record.type);
183
+ const existingValue = existing?.records?.[0]?.content;
184
+ if (existing && existingValue === record.value) {
185
+ results.push({
186
+ record,
187
+ created: false,
188
+ unchanged: true
189
+ });
190
+ continue;
191
+ }
192
+ await api.upsertRecords(domain, [{
193
+ name: record.name,
194
+ type: record.type,
195
+ ttl: record.ttl,
196
+ records: [{ content: record.value }]
197
+ }]);
198
+ results.push({
199
+ record,
200
+ created: !existing,
201
+ unchanged: false
202
+ });
203
+ }
204
+ return results;
205
+ }
206
+ };
207
+
208
+ //#endregion
209
+ exports.HostingerProvider = HostingerProvider;
210
+ //# sourceMappingURL=HostingerProvider-DUV9-Tzg.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HostingerProvider-DUV9-Tzg.cjs","names":["message: string","status: number","statusText: string","errors?: Record<string, string[]>","token: string","method: 'GET' | 'POST' | 'PUT' | 'DELETE'","endpoint: string","body?: unknown","errors: Record<string, string[]> | undefined","domain: string","records: DnsRecord[]","filters: DnsRecordFilter[]","name: string","type: DnsRecordType","subdomain: string","ip: string","domain: string","records: UpsertDnsRecord[]","results: UpsertResult[]"],"sources":["../src/deploy/dns/hostinger-api.ts","../src/deploy/dns/HostingerProvider.ts"],"sourcesContent":["/**\n * Hostinger DNS API client\n *\n * API Documentation: https://developers.hostinger.com/\n * Authentication: Bearer token from hpanel.hostinger.com/profile/api\n */\n\nconst HOSTINGER_API_BASE = 'https://developers.hostinger.com';\n\n/**\n * DNS record types supported by Hostinger\n */\nexport type DnsRecordType =\n\t| 'A'\n\t| 'AAAA'\n\t| 'CNAME'\n\t| 'MX'\n\t| 'TXT'\n\t| 'NS'\n\t| 'SRV'\n\t| 'CAA';\n\n/**\n * A single DNS record\n */\nexport interface DnsRecord {\n\t/** Subdomain name (e.g., 'api.joemoer' for api.joemoer.traflabs.io) */\n\tname: string;\n\t/** Record type */\n\ttype: DnsRecordType;\n\t/** TTL in seconds */\n\tttl: number;\n\t/** Record values */\n\trecords: Array<{ content: string }>;\n}\n\n/**\n * Filter for deleting specific records\n */\nexport interface DnsRecordFilter {\n\tname: string;\n\ttype: DnsRecordType;\n}\n\n/**\n * API error response\n */\nexport interface HostingerErrorResponse {\n\tmessage?: string;\n\terrors?: Record<string, string[]>;\n}\n\n/**\n * Hostinger API error\n */\nexport class HostingerApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic errors?: Record<string, string[]>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'HostingerApiError';\n\t}\n}\n\n/**\n * Hostinger DNS API client\n *\n * @example\n * ```ts\n * const api = new HostingerApi(token);\n *\n * // Get all records for a domain\n * const records = await api.getRecords('traflabs.io');\n *\n * // Create/update records\n * await api.upsertRecords('traflabs.io', [\n * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }\n * ]);\n * ```\n */\nexport class HostingerApi {\n\tprivate token: string;\n\n\tconstructor(token: string) {\n\t\tthis.token = token;\n\t}\n\n\t/**\n\t * Make a request to the Hostinger API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${HOSTINGER_API_BASE}${endpoint}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\tAuthorization: `Bearer ${this.token}`,\n\t\t\t},\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;\n\t\t\tlet errors: Record<string, string[]> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as HostingerErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Hostinger API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\terrors = errorBody.errors;\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new HostingerApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\terrors,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Get all DNS records for a domain\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t */\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tinterface RecordResponse {\n\t\t\tdata: Array<{\n\t\t\t\tname: string;\n\t\t\t\ttype: DnsRecordType;\n\t\t\t\tttl: number;\n\t\t\t\trecords: Array<{ content: string }>;\n\t\t\t}>;\n\t\t}\n\n\t\tconst response = await this.request<RecordResponse>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\treturn response.data || [];\n\t}\n\n\t/**\n\t * Create or update DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to create/update\n\t * @param overwrite - If true, replaces all existing records. If false, merges with existing.\n\t */\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t\toverwrite = false,\n\t): Promise<void> {\n\t\tawait this.request('PUT', `/api/dns/v1/zones/${domain}`, {\n\t\t\toverwrite,\n\t\t\tzone: records,\n\t\t});\n\t}\n\n\t/**\n\t * Validate DNS records before applying\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to validate\n\t * @returns true if valid, throws if invalid\n\t */\n\tasync validateRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t): Promise<boolean> {\n\t\tawait this.request('POST', `/api/dns/v1/zones/${domain}/validate`, {\n\t\t\toverwrite: false,\n\t\t\tzone: records,\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * Delete specific DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param filters - Filters to match records for deletion\n\t */\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\tfilters: DnsRecordFilter[],\n\t): Promise<void> {\n\t\tawait this.request('DELETE', `/api/dns/v1/zones/${domain}`, {\n\t\t\tfilters,\n\t\t});\n\t}\n\n\t/**\n\t * Check if a specific record exists\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param name - Subdomain name (e.g., 'api.joemoer')\n\t * @param type - Record type (e.g., 'A')\n\t */\n\tasync recordExists(\n\t\tdomain: string,\n\t\tname: string,\n\t\ttype: DnsRecordType = 'A',\n\t): Promise<boolean> {\n\t\tconst records = await this.getRecords(domain);\n\t\treturn records.some((r) => r.name === name && r.type === type);\n\t}\n\n\t/**\n\t * Create a single A record if it doesn't exist\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param subdomain - Subdomain name (e.g., 'api.joemoer')\n\t * @param ip - IP address to point to\n\t * @param ttl - TTL in seconds (default: 300)\n\t * @returns true if created, false if already exists\n\t */\n\tasync createARecordIfNotExists(\n\t\tdomain: string,\n\t\tsubdomain: string,\n\t\tip: string,\n\t\tttl = 300,\n\t): Promise<boolean> {\n\t\tconst exists = await this.recordExists(domain, subdomain, 'A');\n\t\tif (exists) {\n\t\t\treturn false;\n\t\t}\n\n\t\tawait this.upsertRecords(domain, [\n\t\t\t{\n\t\t\t\tname: subdomain,\n\t\t\t\ttype: 'A',\n\t\t\t\tttl,\n\t\t\t\trecords: [{ content: ip }],\n\t\t\t},\n\t\t]);\n\n\t\treturn true;\n\t}\n}\n","/**\n * Hostinger DNS Provider\n *\n * Implements DnsProvider interface using the Hostinger DNS API.\n */\n\nimport { getHostingerToken } from '../../auth/credentials';\nimport type {\n\tDnsProvider,\n\tDnsRecord,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\nimport { HostingerApi } from './hostinger-api';\n\n/**\n * Hostinger DNS provider implementation.\n */\nexport class HostingerProvider implements DnsProvider {\n\treadonly name = 'hostinger';\n\tprivate api: HostingerApi | null = null;\n\n\t/**\n\t * Get or create the Hostinger API client.\n\t */\n\tprivate async getApi(): Promise<HostingerApi> {\n\t\tif (this.api) {\n\t\t\treturn this.api;\n\t\t}\n\n\t\tconst token = await getHostingerToken();\n\t\tif (!token) {\n\t\t\tthrow new Error(\n\t\t\t\t'Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api',\n\t\t\t);\n\t\t}\n\n\t\tthis.api = new HostingerApi(token);\n\t\treturn this.api;\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst api = await this.getApi();\n\t\tconst records = await api.getRecords(domain);\n\n\t\treturn records.map((r) => ({\n\t\t\tname: r.name,\n\t\t\ttype: r.type,\n\t\t\tttl: r.ttl,\n\t\t\tvalues: r.records.map((rec) => rec.content),\n\t\t}));\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to check what already exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\n\t\t\tconst existingValue = existing?.records?.[0]?.content;\n\n\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: false,\n\t\t\t\t\tunchanged: true,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Create or update the record\n\t\t\tawait api.upsertRecords(domain, [\n\t\t\t\t{\n\t\t\t\t\tname: record.name,\n\t\t\t\t\ttype: record.type,\n\t\t\t\t\tttl: record.ttl,\n\t\t\t\t\trecords: [{ content: record.value }],\n\t\t\t\t},\n\t\t\t]);\n\n\t\t\tresults.push({\n\t\t\t\trecord,\n\t\t\t\tcreated: !existing,\n\t\t\t\tunchanged: false,\n\t\t\t});\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,210 @@
1
+ import { getHostingerToken } from "./credentials-DT1dSxIx.mjs";
2
+
3
+ //#region src/deploy/dns/hostinger-api.ts
4
+ /**
5
+ * Hostinger DNS API client
6
+ *
7
+ * API Documentation: https://developers.hostinger.com/
8
+ * Authentication: Bearer token from hpanel.hostinger.com/profile/api
9
+ */
10
+ const HOSTINGER_API_BASE = "https://developers.hostinger.com";
11
+ /**
12
+ * Hostinger API error
13
+ */
14
+ var HostingerApiError = class extends Error {
15
+ constructor(message, status, statusText, errors) {
16
+ super(message);
17
+ this.status = status;
18
+ this.statusText = statusText;
19
+ this.errors = errors;
20
+ this.name = "HostingerApiError";
21
+ }
22
+ };
23
+ /**
24
+ * Hostinger DNS API client
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const api = new HostingerApi(token);
29
+ *
30
+ * // Get all records for a domain
31
+ * const records = await api.getRecords('traflabs.io');
32
+ *
33
+ * // Create/update records
34
+ * await api.upsertRecords('traflabs.io', [
35
+ * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }
36
+ * ]);
37
+ * ```
38
+ */
39
+ var HostingerApi = class {
40
+ token;
41
+ constructor(token) {
42
+ this.token = token;
43
+ }
44
+ /**
45
+ * Make a request to the Hostinger API
46
+ */
47
+ async request(method, endpoint, body) {
48
+ const url = `${HOSTINGER_API_BASE}${endpoint}`;
49
+ const response = await fetch(url, {
50
+ method,
51
+ headers: {
52
+ "Content-Type": "application/json",
53
+ Authorization: `Bearer ${this.token}`
54
+ },
55
+ body: body ? JSON.stringify(body) : void 0
56
+ });
57
+ if (!response.ok) {
58
+ let errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;
59
+ let errors;
60
+ try {
61
+ const errorBody = await response.json();
62
+ if (errorBody.message) errorMessage = `Hostinger API error: ${errorBody.message}`;
63
+ errors = errorBody.errors;
64
+ } catch {}
65
+ throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
66
+ }
67
+ const text = await response.text();
68
+ if (!text || text.trim() === "") return void 0;
69
+ return JSON.parse(text);
70
+ }
71
+ /**
72
+ * Get all DNS records for a domain
73
+ *
74
+ * @param domain - Root domain (e.g., 'traflabs.io')
75
+ */
76
+ async getRecords(domain) {
77
+ const response = await this.request("GET", `/api/dns/v1/zones/${domain}`);
78
+ return response.data || [];
79
+ }
80
+ /**
81
+ * Create or update DNS records
82
+ *
83
+ * @param domain - Root domain (e.g., 'traflabs.io')
84
+ * @param records - Records to create/update
85
+ * @param overwrite - If true, replaces all existing records. If false, merges with existing.
86
+ */
87
+ async upsertRecords(domain, records, overwrite = false) {
88
+ await this.request("PUT", `/api/dns/v1/zones/${domain}`, {
89
+ overwrite,
90
+ zone: records
91
+ });
92
+ }
93
+ /**
94
+ * Validate DNS records before applying
95
+ *
96
+ * @param domain - Root domain (e.g., 'traflabs.io')
97
+ * @param records - Records to validate
98
+ * @returns true if valid, throws if invalid
99
+ */
100
+ async validateRecords(domain, records) {
101
+ await this.request("POST", `/api/dns/v1/zones/${domain}/validate`, {
102
+ overwrite: false,
103
+ zone: records
104
+ });
105
+ return true;
106
+ }
107
+ /**
108
+ * Delete specific DNS records
109
+ *
110
+ * @param domain - Root domain (e.g., 'traflabs.io')
111
+ * @param filters - Filters to match records for deletion
112
+ */
113
+ async deleteRecords(domain, filters) {
114
+ await this.request("DELETE", `/api/dns/v1/zones/${domain}`, { filters });
115
+ }
116
+ /**
117
+ * Check if a specific record exists
118
+ *
119
+ * @param domain - Root domain (e.g., 'traflabs.io')
120
+ * @param name - Subdomain name (e.g., 'api.joemoer')
121
+ * @param type - Record type (e.g., 'A')
122
+ */
123
+ async recordExists(domain, name, type = "A") {
124
+ const records = await this.getRecords(domain);
125
+ return records.some((r) => r.name === name && r.type === type);
126
+ }
127
+ /**
128
+ * Create a single A record if it doesn't exist
129
+ *
130
+ * @param domain - Root domain (e.g., 'traflabs.io')
131
+ * @param subdomain - Subdomain name (e.g., 'api.joemoer')
132
+ * @param ip - IP address to point to
133
+ * @param ttl - TTL in seconds (default: 300)
134
+ * @returns true if created, false if already exists
135
+ */
136
+ async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
137
+ const exists = await this.recordExists(domain, subdomain, "A");
138
+ if (exists) return false;
139
+ await this.upsertRecords(domain, [{
140
+ name: subdomain,
141
+ type: "A",
142
+ ttl,
143
+ records: [{ content: ip }]
144
+ }]);
145
+ return true;
146
+ }
147
+ };
148
+
149
+ //#endregion
150
+ //#region src/deploy/dns/HostingerProvider.ts
151
+ /**
152
+ * Hostinger DNS provider implementation.
153
+ */
154
+ var HostingerProvider = class {
155
+ name = "hostinger";
156
+ api = null;
157
+ /**
158
+ * Get or create the Hostinger API client.
159
+ */
160
+ async getApi() {
161
+ if (this.api) return this.api;
162
+ const token = await getHostingerToken();
163
+ 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");
164
+ this.api = new HostingerApi(token);
165
+ return this.api;
166
+ }
167
+ async getRecords(domain) {
168
+ const api = await this.getApi();
169
+ const records = await api.getRecords(domain);
170
+ return records.map((r) => ({
171
+ name: r.name,
172
+ type: r.type,
173
+ ttl: r.ttl,
174
+ values: r.records.map((rec) => rec.content)
175
+ }));
176
+ }
177
+ async upsertRecords(domain, records) {
178
+ const api = await this.getApi();
179
+ const results = [];
180
+ const existingRecords = await api.getRecords(domain);
181
+ for (const record of records) {
182
+ const existing = existingRecords.find((r) => r.name === record.name && r.type === record.type);
183
+ const existingValue = existing?.records?.[0]?.content;
184
+ if (existing && existingValue === record.value) {
185
+ results.push({
186
+ record,
187
+ created: false,
188
+ unchanged: true
189
+ });
190
+ continue;
191
+ }
192
+ await api.upsertRecords(domain, [{
193
+ name: record.name,
194
+ type: record.type,
195
+ ttl: record.ttl,
196
+ records: [{ content: record.value }]
197
+ }]);
198
+ results.push({
199
+ record,
200
+ created: !existing,
201
+ unchanged: false
202
+ });
203
+ }
204
+ return results;
205
+ }
206
+ };
207
+
208
+ //#endregion
209
+ export { HostingerProvider };
210
+ //# sourceMappingURL=HostingerProvider-DqUq6e9i.mjs.map